update: dtd2ftl
This commit is contained in:
parent
8540d1b4cf
commit
25081bf450
@ -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:
|
||||
|
@ -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>
|
||||
|
@ -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
|
@ -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__">
|
@ -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=阅读器标签
|
@ -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__">
|
11
addon/locale/en-US/addon.ftl
Normal file
11
addon/locale/en-US/addon.ftl
Normal 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
|
4
addon/locale/en-US/preferences.ftl
Normal file
4
addon/locale/en-US/preferences.ftl
Normal file
@ -0,0 +1,4 @@
|
||||
pref-title = Addon Template Example
|
||||
pref-enable =
|
||||
.label = Enable
|
||||
pref-help = { $name } Build { $version } { $time }
|
11
addon/locale/zh-CN/addon.ftl
Normal file
11
addon/locale/zh-CN/addon.ftl
Normal 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 = 阅读器标签
|
5
addon/locale/zh-CN/preferences.ftl
Normal file
5
addon/locale/zh-CN/preferences.ftl
Normal file
@ -0,0 +1,5 @@
|
||||
pref-title = 插件模板设置示例
|
||||
pref-enable =
|
||||
.label = 开启
|
||||
pref-input = 输入
|
||||
pref-help = { $name } Build { $version } { $time }
|
@ -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(
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user