update: dtd2ftl

This commit is contained in:
windingwind 2023-06-16 00:19:09 +08:00
parent 8540d1b4cf
commit 25081bf450
16 changed files with 133 additions and 147 deletions

View File

@ -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.
<details style="text-indent: 2em">
<summary>💡 Steps to add this feature to an existing plugin</summary>
@ -232,8 +233,6 @@ When file changes are detected in `src` or `addon`, the plugin will be automatic
</details>
### Debug in Zotero
You can also:

View File

@ -1,18 +1,22 @@
<linkset>
<html:link rel="localization" href="__addonRef__-preferences.ftl" />
</linkset>
<vbox
id="zotero-prefpane-__addonRef__"
onload="Zotero.__addonInstance__.hooks.onPrefsEvent('load', {window})"
>
<groupbox>
<label><html:h2>&zotero.__addonRef__.pref.title;</html:h2></label>
<label><html:h2 data-l10n-id="pref-title"></html:h2></label>
<checkbox
id="zotero-prefpane-__addonRef__-enable"
label="&zotero.__addonRef__.pref.enable.label;"
preference="extensions.zotero.__addonRef__.enable"
data-l10n-id="pref-enable"
/>
<hbox>
<html:label for="zotero-prefpane-__addonRef__-input"
>&zotero.__addonRef__.pref.input.label;</html:label
>
<html:label
for="zotero-prefpane-__addonRef__-input"
data-l10n-id="pref-input"
></html:label>
<html:input
type="text"
id="zotero-prefpane-__addonRef__-input"
@ -25,7 +29,8 @@
</groupbox>
</vbox>
<vbox>
<label
value="&zotero.__addonRef__.help.version.label; &zotero.__addonRef__.help.releasetime.label;"
></label>
<html:label
data-l10n-id="pref-help"
data-l10n-args='{"time": "__buildTime__","name": "__addonName__","version":"__buildVersion__"}'
></html:label>
</vbox>

View File

@ -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

View File

@ -1,7 +0,0 @@
<!ENTITY zotero.__addonRef__.pref.title "Addon Template Example">
<!ENTITY zotero.__addonRef__.itemmenu.test.label "addon template">
<!ENTITY zotero.__addonRef__.pref.enable.label "Enable">
<!ENTITY zotero.__addonRef__.pref.input.label "Input">
<!ENTITY zotero.__addonRef__.help.version.label "__addonName__ VERSION __buildVersion__">
<!ENTITY zotero.__addonRef__.help.releasetime.label "Build __buildTime__">

View File

@ -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=阅读器标签

View File

@ -1,7 +0,0 @@
<!ENTITY zotero.__addonRef__.pref.title "插件模板设置示例">
<!ENTITY zotero.__addonRef__.itemmenu.test.label "插件模板">
<!ENTITY zotero.__addonRef__.pref.enable.label "开启">
<!ENTITY zotero.__addonRef__.pref.input.label "输入">
<!ENTITY zotero.__addonRef__.help.version.label "__addonName__ 版本 __buildVersion__">
<!ENTITY zotero.__addonRef__.help.releasetime.label "Build __buildTime__">

View File

@ -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

View File

@ -0,0 +1,4 @@
pref-title = Addon Template Example
pref-enable =
.label = Enable
pref-help = { $name } Build { $version } { $time }

View File

@ -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 = 阅读器标签

View File

@ -0,0 +1,5 @@
pref-title = 插件模板设置示例
pref-enable =
.label = 开启
pref-input = 输入
pref-help = { $name } Build { $version } { $time }

View File

@ -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(

View File

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

View File

@ -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,

View File

@ -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,

View File

@ -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);
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 localString;
return result;
}
/**
* Get locale string async
* @param localString
*/
export async function getStringAsync(localString: string): Promise<string> {
// @ts-ignore
return (await document.l10n.formatValue(localString)) || localString;
}

View File

@ -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
* <div locale-target="innerHTML,title" title="elem.title">elem.text</div>
* ```
* 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
* <div locale-target="innerHTML,title" title="Locale example">Hello World</div>
* ```
* else if locale is "zh-CN"
* ```html
* <div locale-target="innerHTML,title" title="多语言样例"></div>
* ```
* @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);
}
});
}
);
}