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);
- }
- });
- }
- );
-}