From 25081bf45047a8fff83fa2d27806c83fe5fc76cb Mon Sep 17 00:00:00 2001 From: windingwind <33902321+windingwind@users.noreply.github.com> Date: Fri, 16 Jun 2023 00:19:09 +0800 Subject: [PATCH] update: dtd2ftl --- README.md | 7 ++- addon/chrome/content/preferences.xhtml | 21 +++++--- addon/chrome/locale/en-US/addon.properties | 11 ---- addon/chrome/locale/en-US/overlay.dtd | 7 --- addon/chrome/locale/zh-CN/addon.properties | 11 ---- addon/chrome/locale/zh-CN/overlay.dtd | 7 --- addon/locale/en-US/addon.ftl | 11 ++++ addon/locale/en-US/preferences.ftl | 4 ++ addon/locale/zh-CN/addon.ftl | 11 ++++ addon/locale/zh-CN/preferences.ftl | 5 ++ scripts/build.mjs | 25 ++++++++++ src/hooks.ts | 8 +-- src/modules/examples.ts | 27 +++++----- src/modules/preferenceScript.ts | 14 ++---- src/utils/locale.ts | 53 ++++++++++++++------ src/utils/window.ts | 58 +--------------------- 16 files changed, 133 insertions(+), 147 deletions(-) delete mode 100644 addon/chrome/locale/en-US/addon.properties delete mode 100644 addon/chrome/locale/en-US/overlay.dtd delete mode 100644 addon/chrome/locale/zh-CN/addon.properties delete mode 100644 addon/chrome/locale/zh-CN/overlay.dtd create mode 100644 addon/locale/en-US/addon.ftl create mode 100644 addon/locale/en-US/preferences.ftl create mode 100644 addon/locale/zh-CN/addon.ftl create mode 100644 addon/locale/zh-CN/preferences.ftl diff --git a/README.md b/README.md index 216e7f5..43dcf97 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ This is a plugin template for [Zotero](https://www.zotero.org/). Plugins using t 📌[Zotero Plugin Template](https://github.com/windingwind/zotero-plugin-template)(This repo) -> 👍You are currently in `bootstrap` extension mode. To use `overlay` mode, plsase switch to `overlay` branch in git. +> 👍You are currently in `bootstrap` extension mode. To use `overlay` mode, please switch to `overlay` branch in git. > 👁 Watch this repo so that you can be notified whenever there are fixes & updates. @@ -40,6 +40,8 @@ If you are using this repo, I recommended that you put this badge ([![Using Zote ## Features +> ❗The localization system is upgraded (dtd is deprecated and we do not use .properties anymore). Only supports Zotero 7.0.0-beta.12 or higher now. If you want to support Zotero 6, you may need to use `dtd`, `properties`, and `ftl` at the same time. + - Event-driven, functional programming, under extensive skeleton; - Simple and user-friendly, works out-of-the-box. - ⭐[New!]Auto hot reload! Whenever the source code is modified, automatically compile and reload. [See here→](#auto-hot-reload) @@ -221,7 +223,6 @@ Tired of endless restarting? Forget about it! When file changes are detected in `src` or `addon`, the plugin will be automatically compiled and reloaded. -
💡 Steps to add this feature to an existing plugin @@ -232,8 +233,6 @@ When file changes are detected in `src` or `addon`, the plugin will be automatic
- - ### Debug in Zotero You can also: diff --git a/addon/chrome/content/preferences.xhtml b/addon/chrome/content/preferences.xhtml index 9f55711..af67264 100644 --- a/addon/chrome/content/preferences.xhtml +++ b/addon/chrome/content/preferences.xhtml @@ -1,18 +1,22 @@ + + + - + - &zotero.__addonRef__.pref.input.label; + - + diff --git a/addon/chrome/locale/en-US/addon.properties b/addon/chrome/locale/en-US/addon.properties deleted file mode 100644 index 515590b..0000000 --- a/addon/chrome/locale/en-US/addon.properties +++ /dev/null @@ -1,11 +0,0 @@ -startup.begin=Addon is loading -startup.finish=Addon is ready -menuitem.label=Addon Template: Helper Examples -menupopup.label=Addon Template: Menupopup -menuitem.submenulabel=Addon Template -menuitem.filemenulabel=Addon Template: File Menuitem -prefs.title=Template -prefs.table.title=Title -prefs.table.detail=Detail -tabpanel.lib.tab.label=Lib Tab -tabpanel.reader.tab.label=Reader Tab \ No newline at end of file diff --git a/addon/chrome/locale/en-US/overlay.dtd b/addon/chrome/locale/en-US/overlay.dtd deleted file mode 100644 index 6cbfffb..0000000 --- a/addon/chrome/locale/en-US/overlay.dtd +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/addon/chrome/locale/zh-CN/addon.properties b/addon/chrome/locale/zh-CN/addon.properties deleted file mode 100644 index 281d1b2..0000000 --- a/addon/chrome/locale/zh-CN/addon.properties +++ /dev/null @@ -1,11 +0,0 @@ -startup.begin=插件加载中 -startup.finish=插件已就绪 -menuitem.label=插件模板: 帮助工具样例 -menupopup.label=插件模板: 弹出菜单 -menuitem.submenulabel=插件模板:子菜单 -menuitem.filemenulabel=插件模板: 文件菜单 -prefs.title=插件模板 -prefs.table.title=标题 -prefs.table.detail=详情 -tabpanel.lib.tab.label=库标签 -tabpanel.reader.tab.label=阅读器标签 \ No newline at end of file diff --git a/addon/chrome/locale/zh-CN/overlay.dtd b/addon/chrome/locale/zh-CN/overlay.dtd deleted file mode 100644 index 5539400..0000000 --- a/addon/chrome/locale/zh-CN/overlay.dtd +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/addon/locale/en-US/addon.ftl b/addon/locale/en-US/addon.ftl new file mode 100644 index 0000000..892d267 --- /dev/null +++ b/addon/locale/en-US/addon.ftl @@ -0,0 +1,11 @@ +startup-begin = Addon is loading +startup-finish = Addon is ready +menuitem-label = Addon Template: Helper Examples +menupopup-label = Addon Template: Menupopup +menuitem-submenulabel = Addon Template +menuitem-filemenulabel = Addon Template: File Menuitem +prefs-title = Template +prefs-table-title = Title +prefs-table-detail = Detail +tabpanel-lib-tab-label = Lib Tab +tabpanel-reader-tab-label = Reader Tab \ No newline at end of file diff --git a/addon/locale/en-US/preferences.ftl b/addon/locale/en-US/preferences.ftl new file mode 100644 index 0000000..41100f9 --- /dev/null +++ b/addon/locale/en-US/preferences.ftl @@ -0,0 +1,4 @@ +pref-title = Addon Template Example +pref-enable = + .label = Enable +pref-help = { $name } Build { $version } { $time } \ No newline at end of file diff --git a/addon/locale/zh-CN/addon.ftl b/addon/locale/zh-CN/addon.ftl new file mode 100644 index 0000000..16d97d3 --- /dev/null +++ b/addon/locale/zh-CN/addon.ftl @@ -0,0 +1,11 @@ +startup-begin = 插件加载中 +startup-finish = 插件已就绪 +menuitem-label = 插件模板: 帮助工具样例 +menupopup-label = 插件模板: 弹出菜单 +menuitem-submenulabel = 插件模板:子菜单 +menuitem-filemenulabel = 插件模板: 文件菜单 +prefs-title = 插件模板 +prefs-table-title = 标题 +prefs-table-detail = 详情 +tabpanel-lib-tab-label = 库标签 +tabpanel-reader-tab-label = 阅读器标签 \ No newline at end of file diff --git a/addon/locale/zh-CN/preferences.ftl b/addon/locale/zh-CN/preferences.ftl new file mode 100644 index 0000000..b20243b --- /dev/null +++ b/addon/locale/zh-CN/preferences.ftl @@ -0,0 +1,5 @@ +pref-title = 插件模板设置示例 +pref-enable = + .label = 开启 +pref-input = 输入 +pref-help = { $name } Build { $version } { $time } \ No newline at end of file diff --git a/scripts/build.mjs b/scripts/build.mjs index c1a766e..1f1dca3 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -9,6 +9,7 @@ import { mkdirSync, readdirSync, rmSync, + renameSync, } from "fs"; import { env, exit } from "process"; import replaceInFile from "replace-in-file"; @@ -158,6 +159,30 @@ async function main() { console.log("[Build] Replace OK"); + // Walk the builds/addon/locale folder's sub folders and rename *.ftl to addonRef-*.ftl + const localeDir = join(buildDir, "addon/locale"); + const localeFolders = readdirSync(localeDir, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name); + + for (const localeSubFolder of localeFolders) { + const localeSubDir = join(localeDir, localeSubFolder); + const localeSubFiles = readdirSync(localeSubDir, { + withFileTypes: true, + }) + .filter((dirent) => dirent.isFile()) + .map((dirent) => dirent.name); + + for (const localeSubFile of localeSubFiles) { + if (localeSubFile.endsWith(".ftl")) { + renameSync( + join(localeSubDir, localeSubFile), + join(localeSubDir, `${config.addonRef}-${localeSubFile}`) + ); + } + } + } + console.log("[Build] Addon prepare OK"); await zip.compressDir( diff --git a/src/hooks.ts b/src/hooks.ts index d70224d..67a8a52 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -6,7 +6,7 @@ import { UIExampleFactory, } from "./modules/examples"; import { config } from "../package.json"; -import { getString, initLocale } from "./utils/locale"; +import { getStringAsync, initLocale } from "./utils/locale"; import { registerPrefsScripts } from "./modules/preferenceScript"; async function onStartup() { @@ -26,7 +26,7 @@ async function onStartup() { closeTime: -1, }) .createLine({ - text: getString("startup.begin"), + text: await getStringAsync("startup-begin"), type: "default", progress: 0, }) @@ -41,7 +41,7 @@ async function onStartup() { await Zotero.Promise.delay(1000); popupWin.changeLine({ progress: 30, - text: `[30%] ${getString("startup.begin")}`, + text: `[30%] ${await getStringAsync("startup-begin")}`, }); UIExampleFactory.registerStyleSheet(); @@ -74,7 +74,7 @@ async function onStartup() { popupWin.changeLine({ progress: 100, - text: `[100%] ${getString("startup.finish")}`, + text: `[100%] ${await getStringAsync("startup-finish")}`, }); popupWin.startCloseTimer(5000); diff --git a/src/modules/examples.ts b/src/modules/examples.ts index a8f9703..b549fe9 100644 --- a/src/modules/examples.ts +++ b/src/modules/examples.ts @@ -1,5 +1,5 @@ import { config } from "../../package.json"; -import { getString } from "../utils/locale"; +import { getStringAsync } from "../utils/locale"; function example( target: any, @@ -71,13 +71,12 @@ export class BasicExampleFactory { } @example - static registerPrefs() { + static async registerPrefs() { const prefOptions = { pluginID: config.addonID, src: rootURI + "chrome/content/preferences.xhtml", - label: getString("prefs.title"), + label: await getStringAsync("prefs-title"), image: `chrome://${config.addonRef}/content/icons/favicon.png`, - extraDTD: [`chrome://${config.addonRef}/locale/overlay.dtd`], defaultXUL: true, }; ztoolkit.PreferencePane.register(prefOptions); @@ -198,29 +197,29 @@ export class UIExampleFactory { } @example - static registerRightClickMenuItem() { + static async registerRightClickMenuItem() { const menuIcon = `chrome://${config.addonRef}/content/icons/favicon@0.5x.png`; // item menuitem with icon ztoolkit.Menu.register("item", { tag: "menuitem", id: "zotero-itemmenu-addontemplate-test", - label: getString("menuitem.label"), + label: await getStringAsync("menuitem-label"), commandListener: (ev) => addon.hooks.onDialogEvents("dialogExample"), icon: menuIcon, }); } @example - static registerRightClickMenuPopup() { + static async registerRightClickMenuPopup() { ztoolkit.Menu.register( "item", { tag: "menu", - label: getString("menupopup.label"), + label: await getStringAsync("menupopup-label"), children: [ { tag: "menuitem", - label: getString("menuitem.submenulabel"), + label: await getStringAsync("menuitem-submenulabel"), oncommand: "alert('Hello World! Sub Menuitem.')", }, ], @@ -233,14 +232,14 @@ export class UIExampleFactory { } @example - static registerWindowMenuWithSeparator() { + static async registerWindowMenuWithSeparator() { ztoolkit.Menu.register("menuFile", { tag: "menuseparator", }); // menu->File menuitem ztoolkit.Menu.register("menuFile", { tag: "menuitem", - label: getString("menuitem.filemenulabel"), + label: await getStringAsync("menuitem-filemenulabel"), oncommand: "alert('Hello World! File Menuitem.')", }); } @@ -346,9 +345,9 @@ export class UIExampleFactory { } @example - static registerLibraryTabPanel() { + static async registerLibraryTabPanel() { const tabId = ztoolkit.LibraryTabPanel.register( - getString("tabpanel.lib.tab.label"), + await getStringAsync("tabpanel-lib-tab-label"), (panel: XUL.Element, win: Window) => { const elem = ztoolkit.UI.createElement(win.document, "vbox", { children: [ @@ -392,7 +391,7 @@ export class UIExampleFactory { @example static async registerReaderTabPanel() { const tabId = await ztoolkit.ReaderTabPanel.register( - getString("tabpanel.reader.tab.label"), + await getStringAsync("tabpanel-reader-tab-label"), ( panel: XUL.TabPanel | undefined, deck: XUL.Deck, diff --git a/src/modules/preferenceScript.ts b/src/modules/preferenceScript.ts index 709d45e..c2e5c48 100644 --- a/src/modules/preferenceScript.ts +++ b/src/modules/preferenceScript.ts @@ -1,7 +1,7 @@ import { config } from "../../package.json"; -import { getString } from "../utils/locale"; +import { getStringAsync } from "../utils/locale"; -export function registerPrefsScripts(_window: Window) { +export async function registerPrefsScripts(_window: Window) { // This function is called when the prefs window is opened // See addon/chrome/content/preferences.xul onpaneload if (!addon.data.prefs) { @@ -10,13 +10,13 @@ export function registerPrefsScripts(_window: Window) { columns: [ { dataKey: "title", - label: "prefs.table.title", + label: await getStringAsync("prefs-table-title"), fixedWidth: true, width: 100, }, { dataKey: "detail", - label: "prefs.table.detail", + label: await getStringAsync("prefs-table-detail"), }, ], rows: [ @@ -53,11 +53,7 @@ async function updatePrefsUI() { id: `${config.addonRef}-prefs-table`, // Do not use setLocale, as it modifies the Zotero.Intl.strings // Set locales directly to columns - columns: addon.data.prefs?.columns.map((column) => - Object.assign(column, { - label: getString(column.label) || column.label, - }) - ), + columns: addon.data.prefs?.columns, showHeader: true, multiSelect: true, staticColumns: true, diff --git a/src/utils/locale.ts b/src/utils/locale.ts index 2b83153..e4f2003 100644 --- a/src/utils/locale.ts +++ b/src/utils/locale.ts @@ -1,29 +1,52 @@ import { config } from "../../package.json"; +import { waitUntil } from "./wait"; /** * Initialize locale data */ export function initLocale() { - addon.data.locale = { - stringBundle: Components.classes["@mozilla.org/intl/stringbundle;1"] - .getService(Components.interfaces.nsIStringBundleService) - .createBundle(`chrome://${config.addonRef}/locale/addon.properties`), - }; + ztoolkit.UI.appendElement( + { + tag: "link", + namespace: "html", + properties: { + rel: "localization", + href: `${config.addonRef}-addon.ftl`, + }, + }, + document.querySelector("linkset")! + ); } /** * Get locale string * @param localString - * @param noReload + * @deprecated */ -export function getString(localString: string, noReload = false): string { - try { - return addon.data.locale?.stringBundle.GetStringFromName(localString); - } catch (e) { - if (!noReload) { - initLocale(); - return getString(localString, true); - } - return localString; +export function getString(localString: string): string { + let result = ""; + let flag = false; + getStringAsync(localString) + .then((value) => { + result = value; + flag = true; + }) + .catch((e) => { + ztoolkit.log(e); + flag = true; + }); + const t = new Date().getTime(); + while (!flag && t < new Date().getTime() - 3000) { + // wait until the string is loaded } + return result; +} + +/** + * Get locale string async + * @param localString + */ +export async function getStringAsync(localString: string): Promise { + // @ts-ignore + return (await document.l10n.formatValue(localString)) || localString; } diff --git a/src/utils/window.ts b/src/utils/window.ts index 4b072fa..270631f 100644 --- a/src/utils/window.ts +++ b/src/utils/window.ts @@ -1,6 +1,4 @@ -import { getString } from "./locale"; - -export { isWindowAlive, localeWindow }; +export { isWindowAlive }; /** * Check if the window is alive. @@ -10,57 +8,3 @@ export { isWindowAlive, localeWindow }; function isWindowAlive(win?: Window) { return win && !Components.utils.isDeadWrapper(win) && !win.closed; } - -/** - * Locale the elements in window with the locale-target attribute. - * Useful when the window is created dynamically. - * @example - * In HTML: - * ```html - *
elem.text
- * ``` - * In `addon/chrome/locale/en-US/addon.properties`: - * ```properties - * elem.text=Hello World - * elem.title=Locale example - * ``` - * In `addon/chrome/locale/zh-CN/addon.properties`: - * ```properties - * elem.text=你好世界 - * elem.title=多语言样例 - * ``` - * After locale: - * - * if locale is "en-US" - * ```html - *
Hello World
- * ``` - * else if locale is "zh-CN" - * ```html - *
你好世界
- * ``` - * @param win - */ -function localeWindow(win: Window) { - Array.from(win.document.querySelectorAll("*[locale-target]")).forEach( - (elem) => { - const errorInfo = "Locale Error"; - const locales = elem.getAttribute("locale-target")?.split(","); - locales?.forEach((key) => { - const isProp = key in elem; - try { - const localeString = getString( - (isProp ? (elem as any)[key] : elem.getAttribute(key)).trim() || "" - ); - isProp - ? ((elem as any)[key] = localeString) - : elem.setAttribute(key, localeString); - } catch (error) { - isProp - ? ((elem as any)[key] = errorInfo) - : elem.setAttribute(key, errorInfo); - } - }); - } - ); -}