Implement own locale loader
Why? * Because i18n sucks multi-browser, especially on Chrome. * What's more, this will allow overlaying "incomplete" locales with missing strings over the base locale and get a semi-translated one, which probably is better than nothing. * Additionally we kinda need that implementation anyway for node-based tests. * Also, while not currently implemented, it could allow (hot) reloading of locales, or loading external ones, which would help translators, or providing an option to the user to choose a locale. * And finally, not calling i18n will avoid the "context switch" into browserland. Some implementation details: * Before code can use a locale, it has to be loaded. Sadly sync loading is not really supported. So `await locale` or `await localize`. * Background force reloads locales right now, and caches them in localStorage. Windows will look into localStorage for that cache. * The locale loader will not verify locales other than some rudimentary checks. It is assumed that shipped locales where verified before check-in.
This commit is contained in:
parent
2bfb3d5363
commit
8235af22db
@ -5,7 +5,7 @@ import { ALLOWED_SCHEMES, TRANSFERABLE_PROPERTIES } from "./constants";
|
||||
import { API } from "./api";
|
||||
import { Finisher, makeUniqueItems } from "./item";
|
||||
import { Prefs } from "./prefs";
|
||||
import { _ } from "./i18n";
|
||||
import { _, locale } from "./i18n";
|
||||
import { openPrefs, openManager } from "./windowutils";
|
||||
import { filters } from "./filters";
|
||||
import { getManager } from "./manager/man";
|
||||
@ -106,409 +106,411 @@ class Handler {
|
||||
}
|
||||
}
|
||||
|
||||
new class Action extends Handler {
|
||||
constructor() {
|
||||
super();
|
||||
this.onClicked = this.onClicked.bind(this);
|
||||
action.onClicked.addListener(this.onClicked);
|
||||
}
|
||||
|
||||
async onClicked(tab: {id: number}) {
|
||||
if (!tab.id) {
|
||||
return;
|
||||
locale.then(() => {
|
||||
new class Action extends Handler {
|
||||
constructor() {
|
||||
super();
|
||||
this.onClicked = this.onClicked.bind(this);
|
||||
action.onClicked.addListener(this.onClicked);
|
||||
}
|
||||
try {
|
||||
await this.processResults(
|
||||
true,
|
||||
await runContentJob(
|
||||
tab, "/bundles/content-gather.js", {
|
||||
type: "DTA:gather",
|
||||
selectionOnly: false,
|
||||
textLinks: await Prefs.get("text-links", true),
|
||||
schemes: Array.from(ALLOWED_SCHEMES.values()),
|
||||
transferable: TRANSFERABLE_PROPERTIES,
|
||||
}));
|
||||
}
|
||||
catch (ex) {
|
||||
console.error(ex);
|
||||
}
|
||||
}
|
||||
}();
|
||||
|
||||
const menuHandler = new class Menus extends Handler {
|
||||
constructor() {
|
||||
super();
|
||||
this.onClicked = this.onClicked.bind(this);
|
||||
const alls = new Map<string, string[]>();
|
||||
const mcreate = (options: any) => {
|
||||
if (options.contexts.includes("all")) {
|
||||
alls.set(options.id, options.contexts);
|
||||
}
|
||||
return menus.create(options);
|
||||
};
|
||||
mcreate({
|
||||
id: "DTARegular",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
icons: {
|
||||
16: "/style/button-regular.png",
|
||||
32: "/style/button-regular@2x.png",
|
||||
},
|
||||
title: _("dta.regular"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTATurbo",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
icons: {
|
||||
16: "/style/button-turbo.png",
|
||||
32: "/style/button-turbo@2x.png",
|
||||
},
|
||||
title: _("dta.turbo"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTARegularLink",
|
||||
contexts: ["link"],
|
||||
icons: {
|
||||
16: "/style/button-regular.png",
|
||||
32: "/style/button-regular@2x.png",
|
||||
},
|
||||
title: _("dta.regular.link"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTATurboLink",
|
||||
contexts: ["link"],
|
||||
icons: {
|
||||
16: "/style/button-turbo.png",
|
||||
32: "/style/button-turbo@2x.png",
|
||||
},
|
||||
title: _("dta.turbo.link"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTARegularImage",
|
||||
contexts: ["image"],
|
||||
icons: {
|
||||
16: "/style/button-regular.png",
|
||||
32: "/style/button-regular@2x.png",
|
||||
},
|
||||
title: _("dta.regular.image"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTATurboImage",
|
||||
contexts: ["image"],
|
||||
icons: {
|
||||
16: "/style/button-turbo.png",
|
||||
32: "/style/button-turbo@2x.png",
|
||||
},
|
||||
title: _("dta.turbo.image"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTARegularMedia",
|
||||
contexts: ["video", "audio"],
|
||||
icons: {
|
||||
16: "/style/button-regular.png",
|
||||
32: "/style/button-regular@2x.png",
|
||||
},
|
||||
title: _("dta.regular.media"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTATurboMedia",
|
||||
contexts: ["video", "audio"],
|
||||
icons: {
|
||||
16: "/style/button-turbo.png",
|
||||
32: "/style/button-turbo@2x.png",
|
||||
},
|
||||
title: _("dta.turbo.media"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTARegularSelection",
|
||||
contexts: ["selection"],
|
||||
icons: {
|
||||
16: "/style/button-regular.png",
|
||||
32: "/style/button-regular@2x.png",
|
||||
},
|
||||
title: _("dta.regular.selection"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTATurboSelection",
|
||||
contexts: ["selection"],
|
||||
icons: {
|
||||
16: "/style/button-turbo.png",
|
||||
32: "/style/button-turbo@2x.png",
|
||||
},
|
||||
title: _("dta.turbo.selection"),
|
||||
});
|
||||
mcreate({
|
||||
id: "sep-1",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
type: "separator"
|
||||
});
|
||||
mcreate({
|
||||
id: "DTARegularAll",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
icons: {
|
||||
16: "/style/button-regular.png",
|
||||
32: "/style/button-regular@2x.png",
|
||||
},
|
||||
title: _("dta-regular-all"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTATurboAll",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
icons: {
|
||||
16: "/style/button-turbo.png",
|
||||
32: "/style/button-turbo@2x.png",
|
||||
},
|
||||
title: _("dta-turbo-all"),
|
||||
});
|
||||
const sep2ctx = menus.ACTION_MENU_TOP_LEVEL_LIMIT === 6 ?
|
||||
["all", "tools_menu"] :
|
||||
["all", "browser_action", "tools_menu"];
|
||||
mcreate({
|
||||
id: "sep-2",
|
||||
contexts: sep2ctx,
|
||||
type: "separator"
|
||||
});
|
||||
mcreate({
|
||||
id: "DTAAdd",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
icons: {
|
||||
16: "/style/add.svg",
|
||||
32: "/style/add.svg",
|
||||
64: "/style/add.svg",
|
||||
128: "/style/add.svg",
|
||||
},
|
||||
title: _("add-download"),
|
||||
});
|
||||
mcreate({
|
||||
id: "sep-3",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
type: "separator"
|
||||
});
|
||||
mcreate({
|
||||
id: "DTAManager",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
icons: {
|
||||
16: "/style/button-manager.png",
|
||||
32: "/style/button-manager@2x.png",
|
||||
},
|
||||
title: _("manager.short"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTAPrefs",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
icons: {
|
||||
16: "/style/settings.svg",
|
||||
32: "/style/settings.svg",
|
||||
64: "/style/settings.svg",
|
||||
128: "/style/settings.svg",
|
||||
},
|
||||
title: _("prefs.short"),
|
||||
});
|
||||
Object.freeze(alls);
|
||||
|
||||
const adjustMenus = (v: boolean) => {
|
||||
for (const [id, contexts] of alls.entries()) {
|
||||
const adjusted = v ?
|
||||
contexts.filter(e => e !== "all") :
|
||||
contexts;
|
||||
menus.update(id, {
|
||||
contexts: adjusted
|
||||
});
|
||||
}
|
||||
};
|
||||
Prefs.get("hide-context", false).then((v: boolean) => {
|
||||
// This is the initial load, so no need to adjust when visible already
|
||||
if (!v) {
|
||||
async onClicked(tab: {id: number}) {
|
||||
if (!tab.id) {
|
||||
return;
|
||||
}
|
||||
adjustMenus(v);
|
||||
});
|
||||
Prefs.on("hide-context", (prefs, key, value: boolean) => {
|
||||
adjustMenus(value);
|
||||
});
|
||||
try {
|
||||
await this.processResults(
|
||||
true,
|
||||
await runContentJob(
|
||||
tab, "/bundles/content-gather.js", {
|
||||
type: "DTA:gather",
|
||||
selectionOnly: false,
|
||||
textLinks: await Prefs.get("text-links", true),
|
||||
schemes: Array.from(ALLOWED_SCHEMES.values()),
|
||||
transferable: TRANSFERABLE_PROPERTIES,
|
||||
}));
|
||||
}
|
||||
catch (ex) {
|
||||
console.error(ex);
|
||||
}
|
||||
}
|
||||
}();
|
||||
|
||||
menus.onClicked.addListener(this.onClicked);
|
||||
}
|
||||
const menuHandler = new class Menus extends Handler {
|
||||
constructor() {
|
||||
super();
|
||||
this.onClicked = this.onClicked.bind(this);
|
||||
const alls = new Map<string, string[]>();
|
||||
const mcreate = (options: any) => {
|
||||
if (options.contexts.includes("all")) {
|
||||
alls.set(options.id, options.contexts);
|
||||
}
|
||||
return menus.create(options);
|
||||
};
|
||||
mcreate({
|
||||
id: "DTARegular",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
icons: {
|
||||
16: "/style/button-regular.png",
|
||||
32: "/style/button-regular@2x.png",
|
||||
},
|
||||
title: _("dta.regular"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTATurbo",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
icons: {
|
||||
16: "/style/button-turbo.png",
|
||||
32: "/style/button-turbo@2x.png",
|
||||
},
|
||||
title: _("dta.turbo"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTARegularLink",
|
||||
contexts: ["link"],
|
||||
icons: {
|
||||
16: "/style/button-regular.png",
|
||||
32: "/style/button-regular@2x.png",
|
||||
},
|
||||
title: _("dta.regular.link"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTATurboLink",
|
||||
contexts: ["link"],
|
||||
icons: {
|
||||
16: "/style/button-turbo.png",
|
||||
32: "/style/button-turbo@2x.png",
|
||||
},
|
||||
title: _("dta.turbo.link"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTARegularImage",
|
||||
contexts: ["image"],
|
||||
icons: {
|
||||
16: "/style/button-regular.png",
|
||||
32: "/style/button-regular@2x.png",
|
||||
},
|
||||
title: _("dta.regular.image"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTATurboImage",
|
||||
contexts: ["image"],
|
||||
icons: {
|
||||
16: "/style/button-turbo.png",
|
||||
32: "/style/button-turbo@2x.png",
|
||||
},
|
||||
title: _("dta.turbo.image"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTARegularMedia",
|
||||
contexts: ["video", "audio"],
|
||||
icons: {
|
||||
16: "/style/button-regular.png",
|
||||
32: "/style/button-regular@2x.png",
|
||||
},
|
||||
title: _("dta.regular.media"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTATurboMedia",
|
||||
contexts: ["video", "audio"],
|
||||
icons: {
|
||||
16: "/style/button-turbo.png",
|
||||
32: "/style/button-turbo@2x.png",
|
||||
},
|
||||
title: _("dta.turbo.media"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTARegularSelection",
|
||||
contexts: ["selection"],
|
||||
icons: {
|
||||
16: "/style/button-regular.png",
|
||||
32: "/style/button-regular@2x.png",
|
||||
},
|
||||
title: _("dta.regular.selection"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTATurboSelection",
|
||||
contexts: ["selection"],
|
||||
icons: {
|
||||
16: "/style/button-turbo.png",
|
||||
32: "/style/button-turbo@2x.png",
|
||||
},
|
||||
title: _("dta.turbo.selection"),
|
||||
});
|
||||
mcreate({
|
||||
id: "sep-1",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
type: "separator"
|
||||
});
|
||||
mcreate({
|
||||
id: "DTARegularAll",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
icons: {
|
||||
16: "/style/button-regular.png",
|
||||
32: "/style/button-regular@2x.png",
|
||||
},
|
||||
title: _("dta-regular-all"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTATurboAll",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
icons: {
|
||||
16: "/style/button-turbo.png",
|
||||
32: "/style/button-turbo@2x.png",
|
||||
},
|
||||
title: _("dta-turbo-all"),
|
||||
});
|
||||
const sep2ctx = menus.ACTION_MENU_TOP_LEVEL_LIMIT === 6 ?
|
||||
["all", "tools_menu"] :
|
||||
["all", "browser_action", "tools_menu"];
|
||||
mcreate({
|
||||
id: "sep-2",
|
||||
contexts: sep2ctx,
|
||||
type: "separator"
|
||||
});
|
||||
mcreate({
|
||||
id: "DTAAdd",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
icons: {
|
||||
16: "/style/add.svg",
|
||||
32: "/style/add.svg",
|
||||
64: "/style/add.svg",
|
||||
128: "/style/add.svg",
|
||||
},
|
||||
title: _("add-download"),
|
||||
});
|
||||
mcreate({
|
||||
id: "sep-3",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
type: "separator"
|
||||
});
|
||||
mcreate({
|
||||
id: "DTAManager",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
icons: {
|
||||
16: "/style/button-manager.png",
|
||||
32: "/style/button-manager@2x.png",
|
||||
},
|
||||
title: _("manager.short"),
|
||||
});
|
||||
mcreate({
|
||||
id: "DTAPrefs",
|
||||
contexts: ["all", "browser_action", "tools_menu"],
|
||||
icons: {
|
||||
16: "/style/settings.svg",
|
||||
32: "/style/settings.svg",
|
||||
64: "/style/settings.svg",
|
||||
128: "/style/settings.svg",
|
||||
},
|
||||
title: _("prefs.short"),
|
||||
});
|
||||
Object.freeze(alls);
|
||||
|
||||
*makeSingleItemList(url: string, results: any[]) {
|
||||
for (const result of results) {
|
||||
const finisher = new Finisher(result);
|
||||
for (const list of [result.links, result.media]) {
|
||||
for (const e of list) {
|
||||
if (e.url !== url) {
|
||||
continue;
|
||||
const adjustMenus = (v: boolean) => {
|
||||
for (const [id, contexts] of alls.entries()) {
|
||||
const adjusted = v ?
|
||||
contexts.filter(e => e !== "all") :
|
||||
contexts;
|
||||
menus.update(id, {
|
||||
contexts: adjusted
|
||||
});
|
||||
}
|
||||
};
|
||||
Prefs.get("hide-context", false).then((v: boolean) => {
|
||||
// This is the initial load, so no need to adjust when visible already
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
adjustMenus(v);
|
||||
});
|
||||
Prefs.on("hide-context", (prefs, key, value: boolean) => {
|
||||
adjustMenus(value);
|
||||
});
|
||||
|
||||
menus.onClicked.addListener(this.onClicked);
|
||||
}
|
||||
|
||||
*makeSingleItemList(url: string, results: any[]) {
|
||||
for (const result of results) {
|
||||
const finisher = new Finisher(result);
|
||||
for (const list of [result.links, result.media]) {
|
||||
for (const e of list) {
|
||||
if (e.url !== url) {
|
||||
continue;
|
||||
}
|
||||
const finished = finisher.finish(e);
|
||||
if (!finished) {
|
||||
continue;
|
||||
}
|
||||
yield finished;
|
||||
}
|
||||
const finished = finisher.finish(e);
|
||||
if (!finished) {
|
||||
continue;
|
||||
}
|
||||
yield finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async findSingleItem(tab: any, url: string, turbo = false) {
|
||||
if (!url) {
|
||||
return;
|
||||
async findSingleItem(tab: any, url: string, turbo = false) {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
const results = await runContentJob(
|
||||
tab, "/bundles/content-gather.js", {
|
||||
type: "DTA:gather",
|
||||
selectionOnly: false,
|
||||
schemes: Array.from(ALLOWED_SCHEMES.values()),
|
||||
transferable: TRANSFERABLE_PROPERTIES,
|
||||
});
|
||||
const found = Array.from(this.makeSingleItemList(url, results));
|
||||
const unique = makeUniqueItems([found]);
|
||||
if (!unique.length) {
|
||||
return;
|
||||
}
|
||||
const [item] = unique;
|
||||
API[turbo ? "singleTurbo" : "singleRegular"](item);
|
||||
}
|
||||
const results = await runContentJob(
|
||||
tab, "/bundles/content-gather.js", {
|
||||
type: "DTA:gather",
|
||||
|
||||
onClicked(info: any, tab: any) {
|
||||
if (!tab.id) {
|
||||
return;
|
||||
}
|
||||
const {menuItemId} = info;
|
||||
const {[`onClicked${menuItemId}`]: handler}: any = this;
|
||||
if (!handler) {
|
||||
console.error("Invalid Handler for", menuItemId);
|
||||
return;
|
||||
}
|
||||
handler.call(this, info, tab).catch(console.error);
|
||||
}
|
||||
|
||||
async enumulate(action: string) {
|
||||
const tab = await tabs.query({active: true});
|
||||
if (!tab || !tab.length) {
|
||||
return;
|
||||
}
|
||||
this.onClicked({
|
||||
menuItemId: action
|
||||
}, tab[0]);
|
||||
}
|
||||
|
||||
async onClickedDTARegular(info: any, tab: any) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: false,
|
||||
schemes: Array.from(ALLOWED_SCHEMES.values()),
|
||||
transferable: TRANSFERABLE_PROPERTIES,
|
||||
allTabs: false,
|
||||
turbo: false,
|
||||
tab,
|
||||
});
|
||||
const found = Array.from(this.makeSingleItemList(url, results));
|
||||
const unique = makeUniqueItems([found]);
|
||||
if (!unique.length) {
|
||||
return;
|
||||
}
|
||||
const [item] = unique;
|
||||
API[turbo ? "singleTurbo" : "singleRegular"](item);
|
||||
}
|
||||
|
||||
onClicked(info: any, tab: any) {
|
||||
if (!tab.id) {
|
||||
return;
|
||||
async onClickedDTARegularAll(info: any, tab: any) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: false,
|
||||
allTabs: true,
|
||||
turbo: false,
|
||||
tab,
|
||||
});
|
||||
}
|
||||
const {menuItemId} = info;
|
||||
const {[`onClicked${menuItemId}`]: handler}: any = this;
|
||||
if (!handler) {
|
||||
console.error("Invalid Handler for", menuItemId);
|
||||
return;
|
||||
|
||||
async onClickedDTARegularSelection(info: any, tab: any) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: true,
|
||||
allTabs: false,
|
||||
turbo: false,
|
||||
tab,
|
||||
});
|
||||
}
|
||||
handler.call(this, info, tab).catch(console.error);
|
||||
}
|
||||
|
||||
async enumulate(action: string) {
|
||||
const tab = await tabs.query({active: true});
|
||||
if (!tab || !tab.length) {
|
||||
return;
|
||||
async onClickedDTATurbo(info: any, tab: any) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: false,
|
||||
allTabs: false,
|
||||
turbo: true,
|
||||
tab,
|
||||
});
|
||||
}
|
||||
this.onClicked({
|
||||
menuItemId: action
|
||||
}, tab[0]);
|
||||
}
|
||||
|
||||
async onClickedDTARegular(info: any, tab: any) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: false,
|
||||
allTabs: false,
|
||||
turbo: false,
|
||||
tab,
|
||||
async onClickedDTATurboAll(info: any, tab: any) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: false,
|
||||
allTabs: true,
|
||||
turbo: true,
|
||||
tab,
|
||||
});
|
||||
}
|
||||
|
||||
async onClickedDTATurboSelection(info: any, tab: any) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: true,
|
||||
allTabs: false,
|
||||
turbo: true,
|
||||
tab,
|
||||
});
|
||||
}
|
||||
|
||||
async onClickedDTARegularLink(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.linkUrl, false);
|
||||
}
|
||||
|
||||
async onClickedDTATurboLink(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.linkUrl, true);
|
||||
}
|
||||
|
||||
async onClickedDTARegularImage(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.srcUrl, false);
|
||||
}
|
||||
|
||||
async onClickedDTATurboImage(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.srcUrl, true);
|
||||
}
|
||||
|
||||
async onClickedDTARegularMedia(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.srcUrl, false);
|
||||
}
|
||||
|
||||
async onClickedDTATurboMedia(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.srcUrl, true);
|
||||
}
|
||||
|
||||
onClickedDTAAdd() {
|
||||
API.singleRegular(null);
|
||||
}
|
||||
|
||||
async onClickedDTAManager() {
|
||||
await openManager();
|
||||
}
|
||||
|
||||
async onClickedDTAPrefs() {
|
||||
await openPrefs();
|
||||
}
|
||||
}();
|
||||
|
||||
Bus.on("do-regular", () => menuHandler.enumulate("DTARegular"));
|
||||
Bus.on("do-regular-all", () => menuHandler.enumulate("DTARegularAll"));
|
||||
Bus.on("do-turbo", () => menuHandler.enumulate("DTATurbo"));
|
||||
Bus.on("do-turbo-all", () => menuHandler.enumulate("DTATurboAll"));
|
||||
Bus.on("do-single", () => API.singleRegular(null));
|
||||
Bus.on("open-manager", () => openManager(true));
|
||||
Bus.on("open-prefs", () => openPrefs());
|
||||
|
||||
function adjustAction(globalTurbo: boolean) {
|
||||
action.setPopup({
|
||||
popup: globalTurbo ? "" : null
|
||||
});
|
||||
action.setIcon({
|
||||
path: globalTurbo ? {
|
||||
16: "/style/button-turbo.png",
|
||||
32: "/style/button-turbo@2x.png",
|
||||
} : null
|
||||
});
|
||||
}
|
||||
|
||||
async onClickedDTARegularAll(info: any, tab: any) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: false,
|
||||
allTabs: true,
|
||||
turbo: false,
|
||||
tab,
|
||||
(async function init() {
|
||||
await Prefs.set("last-run", new Date());
|
||||
Prefs.get("global-turbo", false).then(v => adjustAction(v));
|
||||
Prefs.on("global-turbo", (prefs, key, value) => {
|
||||
adjustAction(value);
|
||||
});
|
||||
}
|
||||
|
||||
async onClickedDTARegularSelection(info: any, tab: any) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: true,
|
||||
allTabs: false,
|
||||
turbo: false,
|
||||
tab,
|
||||
});
|
||||
}
|
||||
|
||||
async onClickedDTATurbo(info: any, tab: any) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: false,
|
||||
allTabs: false,
|
||||
turbo: true,
|
||||
tab,
|
||||
});
|
||||
}
|
||||
|
||||
async onClickedDTATurboAll(info: any, tab: any) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: false,
|
||||
allTabs: true,
|
||||
turbo: true,
|
||||
tab,
|
||||
});
|
||||
}
|
||||
|
||||
async onClickedDTATurboSelection(info: any, tab: any) {
|
||||
return await this.performSelection({
|
||||
selectionOnly: true,
|
||||
allTabs: false,
|
||||
turbo: true,
|
||||
tab,
|
||||
});
|
||||
}
|
||||
|
||||
async onClickedDTARegularLink(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.linkUrl, false);
|
||||
}
|
||||
|
||||
async onClickedDTATurboLink(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.linkUrl, true);
|
||||
}
|
||||
|
||||
async onClickedDTARegularImage(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.srcUrl, false);
|
||||
}
|
||||
|
||||
async onClickedDTATurboImage(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.srcUrl, true);
|
||||
}
|
||||
|
||||
async onClickedDTARegularMedia(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.srcUrl, false);
|
||||
}
|
||||
|
||||
async onClickedDTATurboMedia(info: any, tab: any) {
|
||||
return await this.findSingleItem(tab, info.srcUrl, true);
|
||||
}
|
||||
|
||||
onClickedDTAAdd() {
|
||||
API.singleRegular(null);
|
||||
}
|
||||
|
||||
async onClickedDTAManager() {
|
||||
await openManager();
|
||||
}
|
||||
|
||||
async onClickedDTAPrefs() {
|
||||
await openPrefs();
|
||||
}
|
||||
}();
|
||||
|
||||
Bus.on("do-regular", () => menuHandler.enumulate("DTARegular"));
|
||||
Bus.on("do-regular-all", () => menuHandler.enumulate("DTARegularAll"));
|
||||
Bus.on("do-turbo", () => menuHandler.enumulate("DTATurbo"));
|
||||
Bus.on("do-turbo-all", () => menuHandler.enumulate("DTATurboAll"));
|
||||
Bus.on("do-single", () => API.singleRegular(null));
|
||||
Bus.on("open-manager", () => openManager(true));
|
||||
Bus.on("open-prefs", () => openPrefs());
|
||||
|
||||
function adjustAction(globalTurbo: boolean) {
|
||||
action.setPopup({
|
||||
popup: globalTurbo ? "" : null
|
||||
await filters();
|
||||
await getManager();
|
||||
})().catch(ex => {
|
||||
console.error("Failed to init components", ex.toString(), ex.stack, ex);
|
||||
});
|
||||
action.setIcon({
|
||||
path: globalTurbo ? {
|
||||
16: "/style/button-turbo.png",
|
||||
32: "/style/button-turbo@2x.png",
|
||||
} : null
|
||||
});
|
||||
}
|
||||
|
||||
(async function init() {
|
||||
await Prefs.set("last-run", new Date());
|
||||
Prefs.get("global-turbo", false).then(v => adjustAction(v));
|
||||
Prefs.on("global-turbo", (prefs, key, value) => {
|
||||
adjustAction(value);
|
||||
});
|
||||
await filters();
|
||||
await getManager();
|
||||
})().catch(ex => {
|
||||
console.error("Failed to init components", ex.toString(), ex.stack, ex);
|
||||
});
|
||||
|
@ -3,7 +3,6 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const polyfill = require("webextension-polyfill");
|
||||
|
||||
export const {i18n} = polyfill;
|
||||
export const {extension} = polyfill;
|
||||
export const {notifications} = polyfill;
|
||||
export const {browserAction} = polyfill;
|
||||
|
@ -4,13 +4,14 @@
|
||||
import uuid from "./uuid";
|
||||
|
||||
import "./objectoverlay";
|
||||
import { storage, i18n } from "./browser";
|
||||
import { storage } from "./browser";
|
||||
import { EventEmitter } from "./events";
|
||||
import { TYPE_LINK, TYPE_MEDIA, TYPE_ALL } from "./constants";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { Overlayable } from "./objectoverlay";
|
||||
import * as DEFAULT_FILTERS from "../data/filters.json";
|
||||
import { FASTFILTER } from "./recentlist";
|
||||
import { _, locale } from "./i18n";
|
||||
|
||||
const REG_ESCAPE = /[{}()[\]\\^$.]/g;
|
||||
const REG_FNMATCH = /[*?]/;
|
||||
@ -205,7 +206,7 @@ export class Filter {
|
||||
this._label = this.raw.label;
|
||||
if (this.id !== FAST && this.id.startsWith("deffilter-") &&
|
||||
!this.raw.isOverridden("label")) {
|
||||
this._label = i18n.getMessage(this.id) || this._label;
|
||||
this._label = _(this.id) || this._label;
|
||||
}
|
||||
this._reg = Matcher.fromExpression(this.expr);
|
||||
Object.seal(this);
|
||||
@ -475,6 +476,7 @@ class Filters extends EventEmitter {
|
||||
}
|
||||
|
||||
async load() {
|
||||
await locale;
|
||||
const defaultFilters = DEFAULT_FILTERS as any;
|
||||
let savedFilters = (await storage.local.get("userFilters"));
|
||||
if (savedFilters && "userFilters" in savedFilters) {
|
||||
|
220
lib/i18n.ts
220
lib/i18n.ts
@ -3,57 +3,170 @@
|
||||
|
||||
import {memoize} from "./memoize";
|
||||
|
||||
|
||||
declare let browser: any;
|
||||
declare let chrome: any;
|
||||
|
||||
function load() {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
if (typeof browser !== "undefined" && browser.i18n) {
|
||||
return browser.i18n;
|
||||
interface JSONEntry {
|
||||
message: string;
|
||||
placeholders: any;
|
||||
}
|
||||
|
||||
class Entry {
|
||||
private message: string;
|
||||
|
||||
constructor(entry: JSONEntry) {
|
||||
if (!entry.message.includes("$")) {
|
||||
throw new Error("Not entry-able");
|
||||
}
|
||||
if (typeof chrome !== "undefined" && chrome.i18n) {
|
||||
return chrome.i18n;
|
||||
let hit = false;
|
||||
this.message = entry.message.replace(/\$[A-Z0-9]+\$/g, (r: string) => {
|
||||
hit = true;
|
||||
const id = r.substr(1, r.length - 2).toLocaleLowerCase();
|
||||
const pholder = entry.placeholders[id];
|
||||
if (!pholder || !pholder.content) {
|
||||
throw new Error(`Invalid placeholder: ${id}`);
|
||||
}
|
||||
return `${pholder.content}$`;
|
||||
});
|
||||
if (!hit) {
|
||||
throw new Error("Not entry-able");
|
||||
}
|
||||
throw new Error("not in a webext");
|
||||
}
|
||||
catch (ex) {
|
||||
|
||||
localize(args: any[]) {
|
||||
return this.message.replace(/\$\d+\$/g, (r: string) => {
|
||||
const idx = parseInt(r.substr(1, r.length - 2), 10) - 1;
|
||||
return args[idx] || "";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Localization {
|
||||
private strings: Map<string, Entry | string>;
|
||||
|
||||
constructor(baseLanguage: any, ...overlayLanguages: any) {
|
||||
this.strings = new Map();
|
||||
const mapLanguage = (lang: any) => {
|
||||
for (const [id, entry] of Object.entries<JSONEntry>(lang)) {
|
||||
if (!entry.message) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
this.strings.set(id, new Entry(entry));
|
||||
}
|
||||
catch (ex) {
|
||||
this.strings.set(id, entry.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
mapLanguage(baseLanguage);
|
||||
overlayLanguages.forEach(() => {});
|
||||
}
|
||||
|
||||
localize(id: string, ...args: any[]) {
|
||||
const entry = this.strings.get(id);
|
||||
if (!entry) {
|
||||
return "";
|
||||
}
|
||||
if (typeof entry === "string") {
|
||||
return entry;
|
||||
}
|
||||
if (args.length === 1 && Array.isArray(args)) {
|
||||
[args] = args;
|
||||
}
|
||||
return entry.localize(args);
|
||||
}
|
||||
}
|
||||
|
||||
function checkBrowser() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
if (typeof browser !== "undefined" && browser.i18n) {
|
||||
return;
|
||||
}
|
||||
if (typeof chrome !== "undefined" && chrome.i18n) {
|
||||
return;
|
||||
}
|
||||
throw new Error("not in a webext");
|
||||
}
|
||||
|
||||
async function fetchLanguage(code: string) {
|
||||
try {
|
||||
const resp = await fetch(`/_locales/${code}/messages.json`);
|
||||
return await resp.json();
|
||||
}
|
||||
catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const CACHE_KEY = "_cached_locales";
|
||||
|
||||
function loadCached() {
|
||||
if (document.location.pathname.includes("/windows/")) {
|
||||
const cached = localStorage.getItem(CACHE_KEY);
|
||||
if (cached) {
|
||||
return JSON.parse(cached) as any[];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function loadRawLocales() {
|
||||
// en is the base locale
|
||||
const langs = new Set<string>(["en"]);
|
||||
const ui = (browser.i18n || chrome.i18n).getUILanguage();
|
||||
langs.add(ui);
|
||||
if (ui.includes("-")) {
|
||||
// Try the base too
|
||||
langs.add(ui.split(/[_-]+/)[0]);
|
||||
}
|
||||
|
||||
const fetched = await Promise.all(Array.from(langs, fetchLanguage));
|
||||
return fetched.filter(e => !!e);
|
||||
}
|
||||
|
||||
async function load(): Promise<Localization> {
|
||||
try {
|
||||
checkBrowser();
|
||||
try {
|
||||
// en is the base locale
|
||||
let valid = loadCached();
|
||||
if (!valid) {
|
||||
valid = await loadRawLocales();
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify(valid));
|
||||
}
|
||||
if (!valid.length) {
|
||||
throw new Error("Could not lood ANY of these locales");
|
||||
}
|
||||
|
||||
const base = valid.shift();
|
||||
const rv = new Localization(base, ...valid);
|
||||
return rv;
|
||||
}
|
||||
catch (ex) {
|
||||
console.error("Failed to load locale", ex.toString(), ex.stack, ex);
|
||||
return new Localization({});
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// We might be running under node for tests
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const messages = require("../_locales/en/messages.json");
|
||||
|
||||
const map = new Map();
|
||||
for (const [k, v] of Object.entries<any>(messages)) {
|
||||
const {placeholders = {}} = v;
|
||||
let {message = ""} = v;
|
||||
for (const [pname, pval] of Object.entries<any>(placeholders)) {
|
||||
message = message.replace(`$${pname.toUpperCase()}$`, `${pval.content}$`);
|
||||
}
|
||||
map.set(k, message);
|
||||
}
|
||||
|
||||
return {
|
||||
getMessage(id: string, subst: string[]) {
|
||||
const m = map.get(id);
|
||||
if (typeof subst === undefined) {
|
||||
return m;
|
||||
}
|
||||
if (!Array.isArray(subst)) {
|
||||
subst = [subst];
|
||||
}
|
||||
return m.replace(/\$\d+\$/g, (r: string) => {
|
||||
const idx = parseInt(r.substr(1, r.length - 2), 10) - 1;
|
||||
return subst[idx] || "";
|
||||
});
|
||||
}
|
||||
};
|
||||
return new Localization(messages);
|
||||
}
|
||||
}
|
||||
|
||||
const i18n = load();
|
||||
const memoGetMessage = memoize(i18n.getMessage, 10 * 1000, 10);
|
||||
type MemoLocalize = (id: string, ...args: any[]) => string;
|
||||
|
||||
export const locale = load();
|
||||
let loc: Localization | null;
|
||||
let memoLocalize: MemoLocalize | null = null;
|
||||
locale.then(l => {
|
||||
loc = l;
|
||||
memoLocalize = memoize(loc.localize.bind(loc), 10 * 1000, 10);
|
||||
});
|
||||
|
||||
/**
|
||||
* Localize a message
|
||||
@ -62,21 +175,21 @@ const memoGetMessage = memoize(i18n.getMessage, 10 * 1000, 10);
|
||||
* @returns {string} Localized message
|
||||
*/
|
||||
export function _(id: string, ...subst: any[]) {
|
||||
if (!loc || !memoLocalize) {
|
||||
console.trace("TOO SOON");
|
||||
throw new Error("Called too soon");
|
||||
}
|
||||
if (!subst.length) {
|
||||
return memoGetMessage(id);
|
||||
return memoLocalize(id);
|
||||
}
|
||||
if (subst.length === 1 && Array.isArray(subst[0])) {
|
||||
subst = subst.pop();
|
||||
}
|
||||
return i18n.getMessage(id, subst);
|
||||
return loc.localize(id, subst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Localize a DOM
|
||||
* @param {Element} elem DOM to localize
|
||||
* @returns {Element} Passed in element (fluent)
|
||||
*/
|
||||
export function localize<T extends HTMLElement | DocumentFragment>(elem: T): T {
|
||||
function localize_<T extends HTMLElement | DocumentFragment>(elem: T): T {
|
||||
for (const tmpl of elem.querySelectorAll<HTMLTemplateElement>("template")) {
|
||||
localize_(tmpl.content);
|
||||
}
|
||||
|
||||
for (const el of elem.querySelectorAll<HTMLElement>("*[data-i18n]")) {
|
||||
const {i18n: i} = el.dataset;
|
||||
if (!i) {
|
||||
@ -109,3 +222,14 @@ export function localize<T extends HTMLElement | DocumentFragment>(elem: T): T {
|
||||
}
|
||||
return elem as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Localize a DOM
|
||||
* @param {Element} elem DOM to localize
|
||||
* @returns {Element} Passed in element (fluent)
|
||||
*/
|
||||
export async function localize<T extends HTMLElement | DocumentFragment>(
|
||||
elem: T): Promise<T> {
|
||||
await locale;
|
||||
return localize_(elem);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ export default class ModalDialog {
|
||||
this._default = null;
|
||||
}
|
||||
|
||||
_makeEl() {
|
||||
async _makeEl() {
|
||||
this._dismiss = null;
|
||||
this._default = null;
|
||||
|
||||
@ -35,7 +35,7 @@ export default class ModalDialog {
|
||||
|
||||
const body = document.createElement("article");
|
||||
body.classList.add("modal-body");
|
||||
body.appendChild(this.content);
|
||||
body.appendChild(await this.getContent());
|
||||
cont.appendChild(body);
|
||||
|
||||
const footer = document.createElement("footer");
|
||||
@ -87,7 +87,7 @@ export default class ModalDialog {
|
||||
return el;
|
||||
}
|
||||
|
||||
get content(): DocumentFragment | HTMLElement {
|
||||
getContent(): Promise<DocumentFragment | HTMLElement> {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
@ -160,7 +160,7 @@ export default class ModalDialog {
|
||||
return;
|
||||
};
|
||||
|
||||
document.body.appendChild(this.el = this._makeEl());
|
||||
document.body.appendChild(this.el = await this._makeEl());
|
||||
this.shown();
|
||||
addEventListener("keydown", escapeHandler);
|
||||
addEventListener("keydown", enterHandler);
|
||||
|
@ -20,37 +20,6 @@ const LOADED = new Promise(resolve => {
|
||||
});
|
||||
});
|
||||
|
||||
LOADED.then(async () => {
|
||||
const nag = await Prefs.get("nagging", 0);
|
||||
const nagnext = await Prefs.get("nagging-next", 6);
|
||||
const next = Math.ceil(Math.log2(Math.max(1, nag)));
|
||||
const el = $("#nagging");
|
||||
const remove = () => {
|
||||
el.parentElement.removeChild(el);
|
||||
};
|
||||
if (next <= nagnext) {
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
$("#nagging-donate").addEventListener("click", () => {
|
||||
PORT.post("donate");
|
||||
Prefs.set("nagging-next", next);
|
||||
remove();
|
||||
});
|
||||
$("#nagging-later").addEventListener("click", () => {
|
||||
Prefs.set("nagging-next", next);
|
||||
remove();
|
||||
});
|
||||
$("#nagging-never").addEventListener("click", () => {
|
||||
Prefs.set("nagging-next", Number.MAX_SAFE_INTEGER);
|
||||
remove();
|
||||
});
|
||||
$("#nagging-message").textContent = _(
|
||||
"nagging-message", nag.toLocaleString());
|
||||
$("#nagging").classList.remove("hidden");
|
||||
}, 2 * 1000);
|
||||
});
|
||||
|
||||
addEventListener("DOMContentLoaded", function dom() {
|
||||
removeEventListener("DOMContentLoaded", dom);
|
||||
|
||||
@ -70,10 +39,40 @@ addEventListener("DOMContentLoaded", function dom() {
|
||||
})();
|
||||
|
||||
const tabled = new Promised();
|
||||
const loaded = Promise.all([LOADED, platformed]);
|
||||
const fullyloaded = Promise.all([LOADED, platformed, tabled]);
|
||||
const localized = localize(document.documentElement);
|
||||
const loaded = Promise.all([LOADED, platformed, localized]);
|
||||
const fullyloaded = Promise.all([LOADED, platformed, tabled, localized]);
|
||||
fullyloaded.then(async () => {
|
||||
const nag = await Prefs.get("nagging", 0);
|
||||
const nagnext = await Prefs.get("nagging-next", 6);
|
||||
const next = Math.ceil(Math.log2(Math.max(1, nag)));
|
||||
const el = $("#nagging");
|
||||
const remove = () => {
|
||||
el.parentElement.removeChild(el);
|
||||
};
|
||||
if (next <= nagnext) {
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
$("#nagging-donate").addEventListener("click", () => {
|
||||
PORT.post("donate");
|
||||
Prefs.set("nagging-next", next);
|
||||
remove();
|
||||
});
|
||||
$("#nagging-later").addEventListener("click", () => {
|
||||
Prefs.set("nagging-next", next);
|
||||
remove();
|
||||
});
|
||||
$("#nagging-never").addEventListener("click", () => {
|
||||
Prefs.set("nagging-next", Number.MAX_SAFE_INTEGER);
|
||||
remove();
|
||||
});
|
||||
$("#nagging-message").textContent = _(
|
||||
"nagging-message", nag.toLocaleString());
|
||||
$("#nagging").classList.remove("hidden");
|
||||
}, 2 * 1000);
|
||||
});
|
||||
|
||||
localize(document.documentElement);
|
||||
$("#donate").addEventListener("click", () => {
|
||||
PORT.post("donate");
|
||||
});
|
||||
@ -110,7 +109,8 @@ addEventListener("DOMContentLoaded", function dom() {
|
||||
statusNetwork.addEventListener("click", () => {
|
||||
PORT.post("toggle-active");
|
||||
});
|
||||
PORT.on("active", active => {
|
||||
PORT.on("active", async (active: boolean) => {
|
||||
await loaded;
|
||||
if (active) {
|
||||
statusNetwork.className = "icon-network-on";
|
||||
statusNetwork.setAttribute("title", _("statusNetwork-active.title"));
|
||||
|
@ -20,7 +20,6 @@ import {DownloadItem, DownloadTable} from "./table";
|
||||
import {formatSize} from "../../lib/formatters";
|
||||
import {_} from "../../lib/i18n";
|
||||
import {$} from "../winutil";
|
||||
import {StateTexts} from "./state";
|
||||
|
||||
const TIMEOUT_SEARCH = 750;
|
||||
|
||||
@ -260,7 +259,9 @@ class FixedMenuFilter extends MenuFilter {
|
||||
}
|
||||
|
||||
export class StateMenuFilter extends FixedMenuFilter {
|
||||
constructor(collection: FilteredCollection) {
|
||||
constructor(
|
||||
collection: FilteredCollection,
|
||||
StateTexts: Readonly<Map<number, string>>) {
|
||||
const items = Array.from(StateTexts.entries()).map(([state, text]) => {
|
||||
return {
|
||||
state,
|
||||
|
@ -21,10 +21,10 @@ export default class RemovalModalDialog extends ModalDialog {
|
||||
this.check = null;
|
||||
}
|
||||
|
||||
get content() {
|
||||
async getContent() {
|
||||
const content = $<HTMLTemplateElement>("#removal-template").
|
||||
content.cloneNode(true) as DocumentFragment;
|
||||
localize(content);
|
||||
await localize(content);
|
||||
this.check = content.querySelector(".removal-remember");
|
||||
$(".removal-text", content).textContent = this.text;
|
||||
return content;
|
||||
|
@ -2,11 +2,11 @@
|
||||
// License: MIT
|
||||
|
||||
import * as _DownloadState from "../../lib/manager/state";
|
||||
import { _ } from "../../lib/i18n";
|
||||
import { _, locale } from "../../lib/i18n";
|
||||
|
||||
export const DownloadState = _DownloadState;
|
||||
|
||||
export const StateTexts = Object.freeze(new Map([
|
||||
export const StateTexts = locale.then(() => Object.freeze(new Map([
|
||||
[DownloadState.QUEUED, _("queued")],
|
||||
[DownloadState.RUNNING, _("running")],
|
||||
[DownloadState.FINISHING, _("finishing")],
|
||||
@ -14,7 +14,7 @@ export const StateTexts = Object.freeze(new Map([
|
||||
[DownloadState.DONE, _("done")],
|
||||
[DownloadState.CANCELED, _("canceled")],
|
||||
[DownloadState.MISSING, _("missing")],
|
||||
]));
|
||||
])));
|
||||
|
||||
export const StateClasses = Object.freeze(new Map([
|
||||
[DownloadState.QUEUED, "queued"],
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
import { iconForPath } from "../../lib/windowutils";
|
||||
import { formatSpeed, formatSize, formatTimeDelta } from "../../lib/formatters";
|
||||
import { filters } from "../../lib/filters";
|
||||
import { _, localize } from "../../lib/i18n";
|
||||
import { _ } from "../../lib/i18n";
|
||||
import { EventEmitter } from "../../lib/events";
|
||||
import { Prefs, PrefWatcher } from "../../lib/prefs";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
@ -52,7 +52,11 @@ const COL_SEGS = 8;
|
||||
|
||||
const ICON_BASE_SIZE = 16;
|
||||
|
||||
const TEXT_SIZE_UNKNOWM = _("size-unknown");
|
||||
let TEXT_SIZE_UNKNOWM = "unknown";
|
||||
let REAL_STATE_TEXTS = Object.freeze(new Map<number, string>());
|
||||
StateTexts.then(v => {
|
||||
REAL_STATE_TEXTS = v;
|
||||
});
|
||||
|
||||
const prettyNumber = (function() {
|
||||
const rv = new Intl.NumberFormat(undefined, {
|
||||
@ -190,7 +194,7 @@ export class DownloadItem extends EventEmitter {
|
||||
if (this.error) {
|
||||
return _(this.error) || this.error;
|
||||
}
|
||||
return StateTexts.get(this.state);
|
||||
return REAL_STATE_TEXTS.get(this.state) || "";
|
||||
}
|
||||
|
||||
get fmtSpeed() {
|
||||
@ -342,6 +346,8 @@ export class DownloadTable extends VirtualTable {
|
||||
constructor(treeConfig: any) {
|
||||
super("#items", treeConfig, TREE_CONFIG_VERSION);
|
||||
|
||||
TEXT_SIZE_UNKNOWM = _("size-unknown");
|
||||
|
||||
this.finished = 0;
|
||||
this.running = new Set();
|
||||
this.runningTimer = null;
|
||||
@ -362,7 +368,7 @@ export class DownloadTable extends VirtualTable {
|
||||
new TextFilter(this.downloads);
|
||||
const menufilters = new Map<string, MenuFilter>([
|
||||
["colURL", new UrlMenuFilter(this.downloads)],
|
||||
["colETA", new StateMenuFilter(this.downloads)],
|
||||
["colETA", new StateMenuFilter(this.downloads, REAL_STATE_TEXTS)],
|
||||
["colSize", new SizeMenuFilter(this.downloads)],
|
||||
]);
|
||||
this.on("column-clicked", (id, evt, col) => {
|
||||
@ -402,7 +408,6 @@ export class DownloadTable extends VirtualTable {
|
||||
this.sids = new Map<number, DownloadItem>();
|
||||
this.icons = new Icons($("#icons"));
|
||||
|
||||
localize($<HTMLTemplateElement>("#table-context").content);
|
||||
const ctx = this.contextMenu = new ContextMenu("#table-context");
|
||||
Keys.adoptContext(ctx);
|
||||
Keys.adoptButtons($("#toolbar"));
|
||||
|
@ -2,7 +2,7 @@
|
||||
"use strict";
|
||||
// License: MIT
|
||||
|
||||
import { _, localize } from "../../lib/i18n";
|
||||
import { _ } from "../../lib/i18n";
|
||||
import { formatSpeed } from "../../lib/formatters";
|
||||
import { DownloadState } from "./state";
|
||||
import { Rect } from "../../uikit/lib/rect";
|
||||
@ -155,7 +155,7 @@ export class Tooltip {
|
||||
if (!el) {
|
||||
throw new Error("invalid template");
|
||||
}
|
||||
this.elem = localize(el.cloneNode(true) as HTMLElement);
|
||||
this.elem = el.cloneNode(true) as HTMLElement;
|
||||
this.adjust(pos);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
|
@ -109,15 +109,14 @@ class CreateFilterDialog extends ModalDialog {
|
||||
|
||||
media: HTMLInputElement;
|
||||
|
||||
get content() {
|
||||
const tmpl = $<HTMLTemplateElement>("#create-filter-template").
|
||||
getContent() {
|
||||
const rv = $<HTMLTemplateElement>("#create-filter-template").
|
||||
content.cloneNode(true) as DocumentFragment;
|
||||
const rv = localize(tmpl);
|
||||
this.label = $("#filter-create-label", rv);
|
||||
this.expr = $("#filter-create-expr", rv);
|
||||
this.link = $("#filter-create-type-link", rv);
|
||||
this.media = $("#filter-create-type-media", rv);
|
||||
return rv;
|
||||
return Promise.resolve(rv);
|
||||
}
|
||||
|
||||
get buttons() {
|
||||
|
@ -198,15 +198,15 @@ function cancel() {
|
||||
}
|
||||
|
||||
async function init() {
|
||||
await localize(document.documentElement);
|
||||
await Promise.all([MASK.init()]);
|
||||
Mask = new Dropdown("#mask", MASK.values);
|
||||
}
|
||||
|
||||
addEventListener("DOMContentLoaded", function dom() {
|
||||
addEventListener("DOMContentLoaded", async function dom() {
|
||||
removeEventListener("DOMContentLoaded", dom);
|
||||
init().catch(console.error);
|
||||
await init();
|
||||
|
||||
localize(document.documentElement);
|
||||
$("#btnDownload").addEventListener("click", () => download(false));
|
||||
$("#btnPaused").addEventListener("click", () => download(true));
|
||||
$("#btnCancel").addEventListener(
|
||||
|
Loading…
x
Reference in New Issue
Block a user