Register all plugin locales with best-fit aliasing (#4606)
This commit is contained in:
parent
f54becf32e
commit
ed53f4b756
1 changed files with 151 additions and 22 deletions
|
@ -30,9 +30,16 @@ Zotero.Plugins = new function () {
|
||||||
XPCOMUtils.defineLazyModuleGetters(lazy, {
|
XPCOMUtils.defineLazyModuleGetters(lazy, {
|
||||||
XPIDatabase: "resource://gre/modules/addons/XPIDatabase.jsm",
|
XPIDatabase: "resource://gre/modules/addons/XPIDatabase.jsm",
|
||||||
});
|
});
|
||||||
|
XPCOMUtils.defineLazyServiceGetters(lazy, {
|
||||||
|
aomStartup: [
|
||||||
|
"@mozilla.org/addons/addon-manager-startup;1",
|
||||||
|
"amIAddonManagerStartup",
|
||||||
|
],
|
||||||
|
});
|
||||||
var scopes = new Map();
|
var scopes = new Map();
|
||||||
var observers = new Set();
|
var observers = new Set();
|
||||||
var addonVersions = new Map();
|
var addonVersions = new Map();
|
||||||
|
var addonL10nSources = new Map();
|
||||||
|
|
||||||
const REASONS = {
|
const REASONS = {
|
||||||
APP_STARTUP: 1,
|
APP_STARTUP: 1,
|
||||||
|
@ -63,7 +70,7 @@ Zotero.Plugins = new function () {
|
||||||
addonVersions.set(addon.id, addon.version);
|
addonVersions.set(addon.id, addon.version);
|
||||||
_loadScope(addon);
|
_loadScope(addon);
|
||||||
setDefaultPrefs(addon);
|
setDefaultPrefs(addon);
|
||||||
registerLocales(addon);
|
await registerLocales(addon);
|
||||||
await _callMethod(addon, 'startup', REASONS.APP_STARTUP);
|
await _callMethod(addon, 'startup', REASONS.APP_STARTUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,28 +373,150 @@ Zotero.Plugins = new function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Automatically register l10n sources for enabled plugins.
|
/**
|
||||||
//
|
* Automatically register l10n sources for a plugin.
|
||||||
// A Fluent file located at
|
*
|
||||||
// [plugin root]/locale/en-US/make-it-red.ftl
|
* A Fluent file located at
|
||||||
// could be included in an XHTML file as
|
* [plugin root]/locale/en-US/make-it-red.ftl
|
||||||
// <link rel="localization" href="make-it-red.ftl"/>
|
* could be included in an XHTML file as
|
||||||
//
|
* <link rel="localization" href="make-it-red.ftl"/>
|
||||||
// If a plugin doesn't have a subdirectory for the active locale, en-US strings
|
*
|
||||||
// will be used as a fallback.
|
* Locale subdirectories that match Zotero locales (Services.locale.availableLocales)
|
||||||
function registerLocales(addon) {
|
* are registered as is. Other locales are aliased to a best-fit Zotero locale.
|
||||||
let source = new L10nFileSource(
|
* For example, Zotero has an 'eu-ES' locale but no 'eu-FR' locale. If a plugin
|
||||||
|
* included an 'eu-FR' locale instead, 'eu-FR' would be aliased to 'eu-ES',
|
||||||
|
* and 'eu-FR' strings would show if the user's Zotero locale is 'eu-ES'.
|
||||||
|
*
|
||||||
|
* If a plugin doesn't have a locale matching the current Zotero locale, 'en-US'
|
||||||
|
* is used as a fallback. If it doesn't have an 'en-*' locale, Fluent chooses a
|
||||||
|
* fallback arbitrarily. For instance, a plugin with only a 'de' locale would
|
||||||
|
* show German strings even if the user's Zotero locale is 'en-US'.
|
||||||
|
*
|
||||||
|
* @param addon
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function registerLocales(addon) {
|
||||||
|
let rootURI = addon.getResourceURI();
|
||||||
|
let zoteroLocales = Services.locale.availableLocales;
|
||||||
|
let pluginLocales;
|
||||||
|
try {
|
||||||
|
pluginLocales = await readDirectory(rootURI, 'locale', true);
|
||||||
|
if (!pluginLocales.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
Zotero.logError(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let matchedLocales = [];
|
||||||
|
let unmatchedLocales = [];
|
||||||
|
for (let pluginLocale of pluginLocales) {
|
||||||
|
(zoteroLocales.includes(pluginLocale) ? matchedLocales : unmatchedLocales)
|
||||||
|
.push(pluginLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sources = [];
|
||||||
|
// All locales that exactly match a Zotero locale can be registered at once
|
||||||
|
if (matchedLocales.length) {
|
||||||
|
sources.push(new L10nFileSource(
|
||||||
addon.id,
|
addon.id,
|
||||||
'app',
|
'app',
|
||||||
Services.locale.availableLocales,
|
matchedLocales,
|
||||||
addon.getResourceURI().spec + 'locale/{locale}/',
|
// {locale} is replaced with the locale code
|
||||||
);
|
rootURI.spec + 'locale/{locale}/',
|
||||||
L10nRegistry.getInstance().registerSources([source]);
|
));
|
||||||
|
}
|
||||||
|
// Other locales need to be registered individually to create aliases
|
||||||
|
for (let unmatchedLocale of unmatchedLocales) {
|
||||||
|
let resolvedLocale = Zotero.Utilities.Internal.resolveLocale(unmatchedLocale, zoteroLocales);
|
||||||
|
// resolveLocale() returns en-US as a fallback; don't use it unless
|
||||||
|
// the unmatched plugin locale is en-*
|
||||||
|
if (resolvedLocale === 'en-US' && !unmatchedLocale.startsWith('en')) {
|
||||||
|
Zotero.debug(`${addon.id}: No matching locale for ${unmatchedLocale}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Zotero.debug(`${addon.id}: Aliasing ${unmatchedLocale} to ${resolvedLocale}`);
|
||||||
|
if (sources.some(source => source.locales.includes(resolvedLocale))) {
|
||||||
|
Zotero.debug(`${addon.id}: ${resolvedLocale} already registered`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sources.push(new L10nFileSource(
|
||||||
|
addon.id + '-' + unmatchedLocale,
|
||||||
|
'app',
|
||||||
|
[resolvedLocale],
|
||||||
|
// Don't use the {locale} placeholder here - manually specify the aliased locale code
|
||||||
|
rootURI.spec + `locale/${unmatchedLocale}/`,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
L10nRegistry.getInstance().registerSources(sources);
|
||||||
|
addonL10nSources.set(addon.id, sources.map(source => source.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function unregisterLocales(addon) {
|
function unregisterLocales(addon) {
|
||||||
L10nRegistry.getInstance().removeSources([addon.id]);
|
let sources = addonL10nSources.get(addon.id);
|
||||||
|
if (sources) {
|
||||||
|
L10nRegistry.getInstance().removeSources(sources);
|
||||||
|
addonL10nSources.delete(addon.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the contents of a directory in a plugin.
|
||||||
|
* https://searchfox.org/mozilla-esr115/rev/7a83be92b8356ea63559bc3623b2b91a43f2ae05/toolkit/components/extensions/Extension.sys.mjs#882
|
||||||
|
*
|
||||||
|
* @param {nsIURI} rootURI
|
||||||
|
* @param {string} path
|
||||||
|
* @param {boolean} [directoriesOnly=false]
|
||||||
|
* @returns {Promise<string[]>}
|
||||||
|
*/
|
||||||
|
async function readDirectory(rootURI, path, directoriesOnly = false) {
|
||||||
|
if (rootURI instanceof Ci.nsIFileURL) {
|
||||||
|
let uri = Services.io.newURI("./" + path, null, rootURI);
|
||||||
|
let fullPath = uri.QueryInterface(Ci.nsIFileURL).file.path;
|
||||||
|
|
||||||
|
let results = [];
|
||||||
|
try {
|
||||||
|
let children = await IOUtils.getChildren(fullPath);
|
||||||
|
for (let child of children) {
|
||||||
|
if (!directoriesOnly || (await IOUtils.stat(child)).type == "directory") {
|
||||||
|
results.push(PathUtils.filename(child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
// Fall-through, return what we have.
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
rootURI = rootURI.QueryInterface(Ci.nsIJARURI);
|
||||||
|
|
||||||
|
// Append the sub-directory path to the base JAR URI and normalize the
|
||||||
|
// result.
|
||||||
|
let entry = `${rootURI.JAREntry}/${path}/`
|
||||||
|
.replace(/\/\/+/g, "/")
|
||||||
|
.replace(/^\//, "");
|
||||||
|
rootURI = Services.io.newURI(`jar:${rootURI.JARFile.spec}!/${entry}`);
|
||||||
|
|
||||||
|
let results = [];
|
||||||
|
for (let name of lazy.aomStartup.enumerateJARSubtree(rootURI)) {
|
||||||
|
if (!name.startsWith(entry)) {
|
||||||
|
throw new Error("Unexpected ZipReader entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The enumerator returns the full path of all entries.
|
||||||
|
// Trim off the leading path, and filter out entries from
|
||||||
|
// subdirectories.
|
||||||
|
name = name.slice(entry.length);
|
||||||
|
if (name && !/\/./.test(name) && (!directoriesOnly || name.endsWith("/"))) {
|
||||||
|
results.push(name.replace("/", ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -521,7 +650,7 @@ Zotero.Plugins = new function () {
|
||||||
|
|
||||||
_loadScope(addon);
|
_loadScope(addon);
|
||||||
setDefaultPrefs(addon);
|
setDefaultPrefs(addon);
|
||||||
registerLocales(addon);
|
await registerLocales(addon);
|
||||||
await _callMethod(addon, 'install', reason);
|
await _callMethod(addon, 'install', reason);
|
||||||
if (addon.isActive) {
|
if (addon.isActive) {
|
||||||
await _callMethod(addon, 'startup', reason);
|
await _callMethod(addon, 'startup', reason);
|
||||||
|
@ -534,7 +663,7 @@ Zotero.Plugins = new function () {
|
||||||
}
|
}
|
||||||
Zotero.debug("Enabling plugin " + addon.id);
|
Zotero.debug("Enabling plugin " + addon.id);
|
||||||
setDefaultPrefs(addon);
|
setDefaultPrefs(addon);
|
||||||
registerLocales(addon);
|
await registerLocales(addon);
|
||||||
await _callMethod(addon, 'startup', REASONS.ADDON_ENABLE);
|
await _callMethod(addon, 'startup', REASONS.ADDON_ENABLE);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -581,7 +710,7 @@ Zotero.Plugins = new function () {
|
||||||
await _callMethod(addon, 'install', REASONS.ADDON_INSTALL);
|
await _callMethod(addon, 'install', REASONS.ADDON_INSTALL);
|
||||||
if (addon.isActive) {
|
if (addon.isActive) {
|
||||||
setDefaultPrefs(addon);
|
setDefaultPrefs(addon);
|
||||||
registerLocales(addon);
|
await registerLocales(addon);
|
||||||
await _callMethod(addon, 'startup', REASONS.ADDON_INSTALL);
|
await _callMethod(addon, 'startup', REASONS.ADDON_INSTALL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue