зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1350646: Part 9 - Remove SDK l10n modules. r=Mossop
MozReview-Commit-ID: 8CZq3jjxnv0 --HG-- extra : source : 5a45d9e25a007c6ab18bd8da4a41f0fe3b9318c0
This commit is contained in:
Родитель
3b5d5ab88e
Коммит
8eb28f45a2
|
@ -43,7 +43,6 @@ modules = [
|
|||
'sdk/content/content-worker.js',
|
||||
'sdk/content/content.js',
|
||||
'sdk/content/events.js',
|
||||
'sdk/content/l10n-html.js',
|
||||
'sdk/content/loader.js',
|
||||
'sdk/content/mod.js',
|
||||
'sdk/content/sandbox.js',
|
||||
|
@ -81,15 +80,6 @@ modules = [
|
|||
'sdk/io/fs.js',
|
||||
'sdk/io/stream.js',
|
||||
'sdk/io/text-streams.js',
|
||||
'sdk/l10n.js',
|
||||
'sdk/l10n/core.js',
|
||||
'sdk/l10n/html.js',
|
||||
'sdk/l10n/json/core.js',
|
||||
'sdk/l10n/loader.js',
|
||||
'sdk/l10n/locale.js',
|
||||
'sdk/l10n/plural-rules.js',
|
||||
'sdk/l10n/prefs.js',
|
||||
'sdk/l10n/properties/core.js',
|
||||
'sdk/lang/functional.js',
|
||||
'sdk/lang/functional/concurrent.js',
|
||||
'sdk/lang/functional/core.js',
|
||||
|
@ -107,7 +97,6 @@ modules = [
|
|||
'sdk/passwords/utils.js',
|
||||
'sdk/platform/xpcom.js',
|
||||
'sdk/preferences/event-target.js',
|
||||
'sdk/preferences/native-options.js',
|
||||
'sdk/preferences/service.js',
|
||||
'sdk/preferences/utils.js',
|
||||
'sdk/private-browsing.js',
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { Ci, Cc, Cu } = require("chrome");
|
||||
lazyRequireModule(this, "../l10n/core", "core");
|
||||
lazyRequire(this, "../stylesheet/utils", "loadSheet", "removeSheet");
|
||||
const { process, frames } = require("../remote/child");
|
||||
|
||||
var observerService = Cc["@mozilla.org/observer-service;1"]
|
||||
.getService(Ci.nsIObserverService);
|
||||
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
|
||||
const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
|
||||
const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");
|
||||
|
||||
const assetsURI = require('../self').data.url();
|
||||
|
||||
const hideSheetUri = "data:text/css,:root {visibility: hidden !important;}";
|
||||
|
||||
function translateElementAttributes(element) {
|
||||
// Translateable attributes
|
||||
const attrList = ['title', 'accesskey', 'alt', 'label', 'placeholder'];
|
||||
const ariaAttrMap = {
|
||||
'ariaLabel': 'aria-label',
|
||||
'ariaValueText': 'aria-valuetext',
|
||||
'ariaMozHint': 'aria-moz-hint'
|
||||
};
|
||||
const attrSeparator = '.';
|
||||
|
||||
// Try to translate each of the attributes
|
||||
for (let attribute of attrList) {
|
||||
const data = core.get(element.dataset.l10nId + attrSeparator + attribute);
|
||||
if (data)
|
||||
element.setAttribute(attribute, data);
|
||||
}
|
||||
|
||||
// Look for the aria attribute translations that match fxOS's aliases
|
||||
for (let attrAlias in ariaAttrMap) {
|
||||
const data = core.get(element.dataset.l10nId + attrSeparator + attrAlias);
|
||||
if (data)
|
||||
element.setAttribute(ariaAttrMap[attrAlias], data);
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from Gaia:
|
||||
// https://github.com/andreasgal/gaia/blob/04fde2640a7f40314643016a5a6c98bf3755f5fd/webapi.js#L1470
|
||||
function translateElement(element) {
|
||||
element = element || document;
|
||||
|
||||
// check all translatable children (= w/ a `data-l10n-id' attribute)
|
||||
var children = element.querySelectorAll('*[data-l10n-id]');
|
||||
var elementCount = children.length;
|
||||
for (var i = 0; i < elementCount; i++) {
|
||||
var child = children[i];
|
||||
|
||||
// translate the child
|
||||
var key = child.dataset.l10nId;
|
||||
var data = core.get(key);
|
||||
if (data)
|
||||
child.textContent = data;
|
||||
|
||||
translateElementAttributes(child);
|
||||
}
|
||||
}
|
||||
exports.translateElement = translateElement;
|
||||
|
||||
function onDocumentReady2Translate(event) {
|
||||
let document = event.target;
|
||||
document.removeEventListener("DOMContentLoaded", onDocumentReady2Translate);
|
||||
|
||||
translateElement(document);
|
||||
|
||||
try {
|
||||
// Finally display document when we finished replacing all text content
|
||||
if (document.defaultView)
|
||||
removeSheet(document.defaultView, hideSheetUri, 'user');
|
||||
}
|
||||
catch(e) {
|
||||
console.exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
function onContentWindow(document) {
|
||||
// Accept only HTML documents
|
||||
if (!(document instanceof Ci.nsIDOMHTMLDocument))
|
||||
return;
|
||||
|
||||
// Bug 769483: data:URI documents instanciated with nsIDOMParser
|
||||
// have a null `location` attribute at this time
|
||||
if (!document.location)
|
||||
return;
|
||||
|
||||
// Accept only document from this addon
|
||||
if (document.location.href.indexOf(assetsURI) !== 0)
|
||||
return;
|
||||
|
||||
try {
|
||||
// First hide content of the document in order to have content blinking
|
||||
// between untranslated and translated states
|
||||
loadSheet(document.defaultView, hideSheetUri, 'user');
|
||||
}
|
||||
catch(e) {
|
||||
console.exception(e);
|
||||
}
|
||||
// Wait for DOM tree to be built before applying localization
|
||||
document.addEventListener("DOMContentLoaded", onDocumentReady2Translate);
|
||||
}
|
||||
|
||||
// Listen to creation of content documents in order to translate them as soon
|
||||
// as possible in their loading process
|
||||
const ON_CONTENT = "document-element-inserted";
|
||||
let enabled = false;
|
||||
function enable() {
|
||||
if (enabled)
|
||||
return;
|
||||
addObserver(onContentWindow, ON_CONTENT, false);
|
||||
enabled = true;
|
||||
}
|
||||
process.port.on("sdk/l10n/html/enable", enable);
|
||||
|
||||
function disable() {
|
||||
if (!enabled)
|
||||
return;
|
||||
removeObserver(onContentWindow, ON_CONTENT);
|
||||
enabled = false;
|
||||
}
|
||||
process.port.on("sdk/l10n/html/disable", disable);
|
|
@ -1,94 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "stable"
|
||||
};
|
||||
|
||||
lazyRequireModule(this, "./l10n/json/core", "json");
|
||||
lazyRequire(this, "./l10n/core", {"get": "getKey"});
|
||||
lazyRequireModule(this, "./l10n/properties/core", "properties");
|
||||
lazyRequire(this, "./l10n/plural-rules", "getRulesForLocale");
|
||||
|
||||
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
// Retrieve the plural mapping function
|
||||
XPCOMUtils.defineLazyGetter(this, "pluralMappingFunction",
|
||||
() => getRulesForLocale(json.language()) ||
|
||||
getRulesForLocale("en"));
|
||||
|
||||
exports.get = function get(k) {
|
||||
// For now, we only accept a "string" as first argument
|
||||
// TODO: handle plural forms in gettext pattern
|
||||
if (typeof k !== "string")
|
||||
throw new Error("First argument of localization method should be a string");
|
||||
let n = arguments[1];
|
||||
|
||||
// Get translation from big hashmap or default to hard coded string:
|
||||
let localized = getKey(k, n) || k;
|
||||
|
||||
// # Simplest usecase:
|
||||
// // String hard coded in source code:
|
||||
// _("Hello world")
|
||||
// // Identifier of a key stored in properties file
|
||||
// _("helloString")
|
||||
if (arguments.length <= 1)
|
||||
return localized;
|
||||
|
||||
let args = Array.slice(arguments);
|
||||
let placeholders = [null, ...args.slice(typeof(n) === "number" ? 2 : 1)];
|
||||
|
||||
if (typeof localized == "object" && "other" in localized) {
|
||||
// # Plural form:
|
||||
// // Strings hard coded in source code:
|
||||
// _(["One download", "%d downloads"], 10);
|
||||
// // Identifier of a key stored in properties file
|
||||
// _("downloadNumber", 0);
|
||||
let n = arguments[1];
|
||||
|
||||
// First handle simple universal forms that may not be mandatory
|
||||
// for each language, (i.e. not different than 'other' form,
|
||||
// but still usefull for better phrasing)
|
||||
// For example 0 in english is the same form than 'other'
|
||||
// but we accept 'zero' form if specified in localization file
|
||||
if (n === 0 && "zero" in localized)
|
||||
localized = localized["zero"];
|
||||
else if (n === 1 && "one" in localized)
|
||||
localized = localized["one"];
|
||||
else if (n === 2 && "two" in localized)
|
||||
localized = localized["two"];
|
||||
else {
|
||||
let pluralForm = pluralMappingFunction(n);
|
||||
if (pluralForm in localized)
|
||||
localized = localized[pluralForm];
|
||||
else // Fallback in case of error: missing plural form
|
||||
localized = localized["other"];
|
||||
}
|
||||
|
||||
// Simulate a string with one placeholder:
|
||||
args = [null, n];
|
||||
}
|
||||
|
||||
// # String with placeholders:
|
||||
// // Strings hard coded in source code:
|
||||
// _("Hello %s", username)
|
||||
// // Identifier of a key stored in properties file
|
||||
// _("helloString", username)
|
||||
// * We supports `%1s`, `%2s`, ... pattern in order to change arguments order
|
||||
// in translation.
|
||||
// * In case of plural form, we has `%d` instead of `%s`.
|
||||
let offset = 1;
|
||||
if (placeholders.length > 1) {
|
||||
args = placeholders;
|
||||
}
|
||||
|
||||
localized = localized.replace(/%(\d*)[sd]/g, (v, n) => {
|
||||
let rv = args[n != "" ? n : offset];
|
||||
offset++;
|
||||
return rv;
|
||||
});
|
||||
|
||||
return localized;
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
lazyRequireModule(this, "./json/core", "json");
|
||||
lazyRequireModule(this, "./properties/core", "properties");
|
||||
|
||||
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyGetter(this, "get",
|
||||
() => json.usingJSON ? json.get : properties.get);
|
||||
|
||||
module.exports = Object.freeze({
|
||||
get get() { return get; }, // ... yeah.
|
||||
});
|
|
@ -1,32 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { processes, remoteRequire } = require("../remote/parent");
|
||||
remoteRequire("sdk/content/l10n-html");
|
||||
|
||||
var enabled = false;
|
||||
function enable() {
|
||||
if (!enabled) {
|
||||
processes.port.emit("sdk/l10n/html/enable");
|
||||
enabled = true;
|
||||
}
|
||||
}
|
||||
exports.enable = enable;
|
||||
|
||||
function disable() {
|
||||
if (enabled) {
|
||||
processes.port.emit("sdk/l10n/html/disable");
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
exports.disable = disable;
|
||||
|
||||
processes.forEvery(process => {
|
||||
process.port.emit(enabled ? "sdk/l10n/html/enable" : "sdk/l10n/html/disable");
|
||||
});
|
|
@ -1,36 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
var usingJSON = false;
|
||||
var hash = {}, bestMatchingLocale = null;
|
||||
try {
|
||||
let data = require("@l10n/data");
|
||||
hash = data.hash;
|
||||
bestMatchingLocale = data.bestMatchingLocale;
|
||||
usingJSON = true;
|
||||
}
|
||||
catch(e) {}
|
||||
|
||||
exports.usingJSON = usingJSON;
|
||||
|
||||
// Returns the translation for a given key, if available.
|
||||
exports.get = function get(k) {
|
||||
return k in hash ? hash[k] : null;
|
||||
}
|
||||
|
||||
// Returns the full length locale code: ja-JP-mac, en-US or fr
|
||||
exports.locale = function locale() {
|
||||
return bestMatchingLocale;
|
||||
}
|
||||
|
||||
// Returns the short locale code: ja, en, fr
|
||||
exports.language = function language() {
|
||||
return bestMatchingLocale ? bestMatchingLocale.split("-")[0].toLowerCase()
|
||||
: "en";
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
lazyRequire(this, "./locale", "getPreferedLocales", "findClosestLocale");
|
||||
lazyRequire(this, "../net/url", "readURI");
|
||||
lazyRequire(this, "../core/promise", "resolve");
|
||||
|
||||
function parseJsonURI(uri) {
|
||||
return readURI(uri).
|
||||
then(JSON.parse).
|
||||
catch(function (error) {
|
||||
throw Error("Failed to parse locale file:\n" + uri + "\n" + error);
|
||||
});
|
||||
}
|
||||
|
||||
// Returns the array stored in `locales.json` manifest that list available
|
||||
// locales files
|
||||
function getAvailableLocales(rootURI) {
|
||||
let uri = rootURI + "locales.json";
|
||||
return parseJsonURI(uri).then(function (manifest) {
|
||||
return "locales" in manifest &&
|
||||
Array.isArray(manifest.locales) ?
|
||||
manifest.locales : [];
|
||||
});
|
||||
}
|
||||
|
||||
// Returns URI of the best locales file to use from the XPI
|
||||
function getBestLocale(rootURI) {
|
||||
// Read localization manifest file that contains list of available languages
|
||||
return getAvailableLocales(rootURI).then(function (availableLocales) {
|
||||
// Retrieve list of prefered locales to use
|
||||
let preferedLocales = getPreferedLocales();
|
||||
|
||||
// Compute the most preferable locale to use by using these two lists
|
||||
return findClosestLocale(availableLocales, preferedLocales);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Read localization files and returns a promise of data to put in `@l10n/data`
|
||||
* pseudo module, in order to allow l10n/json/core to fetch it.
|
||||
*/
|
||||
exports.load = function load(rootURI) {
|
||||
// First, search for a locale file:
|
||||
return getBestLocale(rootURI).then(function (bestMatchingLocale) {
|
||||
// It may be null if the addon doesn't have any locale file
|
||||
if (!bestMatchingLocale)
|
||||
return resolve(null);
|
||||
|
||||
let localeURI = rootURI + "locale/" + bestMatchingLocale + ".json";
|
||||
|
||||
// Locale files only contains one big JSON object that is used as
|
||||
// an hashtable of: "key to translate" => "translated key"
|
||||
// TODO: We are likely to change this in order to be able to overload
|
||||
// a specific key translation. For a specific package, module or line?
|
||||
return parseJsonURI(localeURI).then(function (json) {
|
||||
return {
|
||||
hash: json,
|
||||
bestMatchingLocale: bestMatchingLocale
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const prefs = require("../preferences/service");
|
||||
const { Cu, Cc, Ci } = require("chrome");
|
||||
const { Services } = Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function getPreferedLocales(caseSensitve) {
|
||||
const locales = Services.locale.getRequestedLocales();
|
||||
|
||||
// This API expects to always append en-US fallback, so for compatibility
|
||||
// reasons, we're going to inject it here.
|
||||
// See bug 1373061 for details.
|
||||
if (!locales.includes('en-US')) {
|
||||
locales.push('en-US');
|
||||
}
|
||||
return locales;
|
||||
}
|
||||
exports.getPreferedLocales = getPreferedLocales;
|
||||
|
||||
/**
|
||||
* Selects the closest matching locale from a list of locales.
|
||||
*
|
||||
* @param aLocales
|
||||
* An array of available locales
|
||||
* @param aMatchLocales
|
||||
* An array of prefered locales, ordered by priority. Most wanted first.
|
||||
* Locales have to be in lowercase.
|
||||
* If null, uses getPreferedLocales() results
|
||||
* @return the best match for the currently selected locale
|
||||
*
|
||||
* Stolen from http://dxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm
|
||||
*/
|
||||
exports.findClosestLocale = function findClosestLocale(aLocales, aMatchLocales) {
|
||||
aMatchLocales = aMatchLocales || getPreferedLocales();
|
||||
|
||||
// Holds the best matching localized resource
|
||||
let bestmatch = null;
|
||||
// The number of locale parts it matched with
|
||||
let bestmatchcount = 0;
|
||||
// The number of locale parts in the match
|
||||
let bestpartcount = 0;
|
||||
|
||||
for (let locale of aMatchLocales) {
|
||||
let lparts = locale.split("-");
|
||||
for (let localized of aLocales) {
|
||||
let found = localized.toLowerCase();
|
||||
// Exact match is returned immediately
|
||||
if (locale == found)
|
||||
return localized;
|
||||
|
||||
let fparts = found.split("-");
|
||||
/* If we have found a possible match and this one isn't any longer
|
||||
then we dont need to check further. */
|
||||
if (bestmatch && fparts.length < bestmatchcount)
|
||||
continue;
|
||||
|
||||
// Count the number of parts that match
|
||||
let maxmatchcount = Math.min(fparts.length, lparts.length);
|
||||
let matchcount = 0;
|
||||
while (matchcount < maxmatchcount &&
|
||||
fparts[matchcount] == lparts[matchcount])
|
||||
matchcount++;
|
||||
|
||||
/* If we matched more than the last best match or matched the same and
|
||||
this locale is less specific than the last best match. */
|
||||
if (matchcount > bestmatchcount ||
|
||||
(matchcount == bestmatchcount && fparts.length < bestpartcount)) {
|
||||
bestmatch = localized;
|
||||
bestmatchcount = matchcount;
|
||||
bestpartcount = fparts.length;
|
||||
}
|
||||
}
|
||||
// If we found a valid match for this locale return it
|
||||
if (bestmatch)
|
||||
return bestmatch;
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -1,407 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// This file is automatically generated with /python-lib/plural-rules-generator.py
|
||||
// Fetching data from: http://unicode.org/repos/cldr/trunk/common/supplemental/plurals.xml
|
||||
|
||||
// Mapping of short locale name == to == > rule index in following list
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const LOCALES_TO_RULES = {
|
||||
"af": 3,
|
||||
"ak": 4,
|
||||
"am": 4,
|
||||
"ar": 1,
|
||||
"asa": 3,
|
||||
"az": 0,
|
||||
"be": 11,
|
||||
"bem": 3,
|
||||
"bez": 3,
|
||||
"bg": 3,
|
||||
"bh": 4,
|
||||
"bm": 0,
|
||||
"bn": 3,
|
||||
"bo": 0,
|
||||
"br": 20,
|
||||
"brx": 3,
|
||||
"bs": 11,
|
||||
"ca": 3,
|
||||
"cgg": 3,
|
||||
"chr": 3,
|
||||
"cs": 12,
|
||||
"cy": 17,
|
||||
"da": 3,
|
||||
"de": 3,
|
||||
"dv": 3,
|
||||
"dz": 0,
|
||||
"ee": 3,
|
||||
"el": 3,
|
||||
"en": 3,
|
||||
"eo": 3,
|
||||
"es": 3,
|
||||
"et": 3,
|
||||
"eu": 3,
|
||||
"fa": 0,
|
||||
"ff": 5,
|
||||
"fi": 3,
|
||||
"fil": 4,
|
||||
"fo": 3,
|
||||
"fr": 5,
|
||||
"fur": 3,
|
||||
"fy": 3,
|
||||
"ga": 8,
|
||||
"gd": 24,
|
||||
"gl": 3,
|
||||
"gsw": 3,
|
||||
"gu": 3,
|
||||
"guw": 4,
|
||||
"gv": 23,
|
||||
"ha": 3,
|
||||
"haw": 3,
|
||||
"he": 2,
|
||||
"hi": 4,
|
||||
"hr": 11,
|
||||
"hu": 0,
|
||||
"id": 0,
|
||||
"ig": 0,
|
||||
"ii": 0,
|
||||
"is": 3,
|
||||
"it": 3,
|
||||
"iu": 7,
|
||||
"ja": 0,
|
||||
"jmc": 3,
|
||||
"jv": 0,
|
||||
"ka": 0,
|
||||
"kab": 5,
|
||||
"kaj": 3,
|
||||
"kcg": 3,
|
||||
"kde": 0,
|
||||
"kea": 0,
|
||||
"kk": 3,
|
||||
"kl": 3,
|
||||
"km": 0,
|
||||
"kn": 0,
|
||||
"ko": 0,
|
||||
"ksb": 3,
|
||||
"ksh": 21,
|
||||
"ku": 3,
|
||||
"kw": 7,
|
||||
"lag": 18,
|
||||
"lb": 3,
|
||||
"lg": 3,
|
||||
"ln": 4,
|
||||
"lo": 0,
|
||||
"lt": 10,
|
||||
"lv": 6,
|
||||
"mas": 3,
|
||||
"mg": 4,
|
||||
"mk": 16,
|
||||
"ml": 3,
|
||||
"mn": 3,
|
||||
"mo": 9,
|
||||
"mr": 3,
|
||||
"ms": 0,
|
||||
"mt": 15,
|
||||
"my": 0,
|
||||
"nah": 3,
|
||||
"naq": 7,
|
||||
"nb": 3,
|
||||
"nd": 3,
|
||||
"ne": 3,
|
||||
"nl": 3,
|
||||
"nn": 3,
|
||||
"no": 3,
|
||||
"nr": 3,
|
||||
"nso": 4,
|
||||
"ny": 3,
|
||||
"nyn": 3,
|
||||
"om": 3,
|
||||
"or": 3,
|
||||
"pa": 3,
|
||||
"pap": 3,
|
||||
"pl": 13,
|
||||
"ps": 3,
|
||||
"pt": 3,
|
||||
"rm": 3,
|
||||
"ro": 9,
|
||||
"rof": 3,
|
||||
"ru": 11,
|
||||
"rwk": 3,
|
||||
"sah": 0,
|
||||
"saq": 3,
|
||||
"se": 7,
|
||||
"seh": 3,
|
||||
"ses": 0,
|
||||
"sg": 0,
|
||||
"sh": 11,
|
||||
"shi": 19,
|
||||
"sk": 12,
|
||||
"sl": 14,
|
||||
"sma": 7,
|
||||
"smi": 7,
|
||||
"smj": 7,
|
||||
"smn": 7,
|
||||
"sms": 7,
|
||||
"sn": 3,
|
||||
"so": 3,
|
||||
"sq": 3,
|
||||
"sr": 11,
|
||||
"ss": 3,
|
||||
"ssy": 3,
|
||||
"st": 3,
|
||||
"sv": 3,
|
||||
"sw": 3,
|
||||
"syr": 3,
|
||||
"ta": 3,
|
||||
"te": 3,
|
||||
"teo": 3,
|
||||
"th": 0,
|
||||
"ti": 4,
|
||||
"tig": 3,
|
||||
"tk": 3,
|
||||
"tl": 4,
|
||||
"tn": 3,
|
||||
"to": 0,
|
||||
"tr": 0,
|
||||
"ts": 3,
|
||||
"tzm": 22,
|
||||
"uk": 11,
|
||||
"ur": 3,
|
||||
"ve": 3,
|
||||
"vi": 0,
|
||||
"vun": 3,
|
||||
"wa": 4,
|
||||
"wae": 3,
|
||||
"wo": 0,
|
||||
"xh": 3,
|
||||
"xog": 3,
|
||||
"yo": 0,
|
||||
"zh": 0,
|
||||
"zu": 3
|
||||
};
|
||||
|
||||
// Utility functions for plural rules methods
|
||||
function isIn(n, list) {
|
||||
return list.indexOf(n) !== -1;
|
||||
}
|
||||
function isBetween(n, start, end) {
|
||||
return start <= n && n <= end;
|
||||
}
|
||||
|
||||
// List of all plural rules methods, that maps an integer to the plural form name to use
|
||||
const RULES = {
|
||||
"0": function (n) {
|
||||
|
||||
return "other"
|
||||
},
|
||||
"1": function (n) {
|
||||
if ((isBetween((n % 100), 3, 10)))
|
||||
return "few";
|
||||
if (n == 0)
|
||||
return "zero";
|
||||
if ((isBetween((n % 100), 11, 99)))
|
||||
return "many";
|
||||
if (n == 2)
|
||||
return "two";
|
||||
if (n == 1)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"2": function (n) {
|
||||
if (n != 0 && (n % 10) == 0)
|
||||
return "many";
|
||||
if (n == 2)
|
||||
return "two";
|
||||
if (n == 1)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"3": function (n) {
|
||||
if (n == 1)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"4": function (n) {
|
||||
if ((isBetween(n, 0, 1)))
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"5": function (n) {
|
||||
if ((isBetween(n, 0, 2)) && n != 2)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"6": function (n) {
|
||||
if (n == 0)
|
||||
return "zero";
|
||||
if ((n % 10) == 1 && (n % 100) != 11)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"7": function (n) {
|
||||
if (n == 2)
|
||||
return "two";
|
||||
if (n == 1)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"8": function (n) {
|
||||
if ((isBetween(n, 3, 6)))
|
||||
return "few";
|
||||
if ((isBetween(n, 7, 10)))
|
||||
return "many";
|
||||
if (n == 2)
|
||||
return "two";
|
||||
if (n == 1)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"9": function (n) {
|
||||
if (n == 0 || n != 1 && (isBetween((n % 100), 1, 19)))
|
||||
return "few";
|
||||
if (n == 1)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"10": function (n) {
|
||||
if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19)))
|
||||
return "few";
|
||||
if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19)))
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"11": function (n) {
|
||||
if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
|
||||
return "few";
|
||||
if ((n % 10) == 0 || (isBetween((n % 10), 5, 9)) || (isBetween((n % 100), 11, 14)))
|
||||
return "many";
|
||||
if ((n % 10) == 1 && (n % 100) != 11)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"12": function (n) {
|
||||
if ((isBetween(n, 2, 4)))
|
||||
return "few";
|
||||
if (n == 1)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"13": function (n) {
|
||||
if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
|
||||
return "few";
|
||||
if (n != 1 && (isBetween((n % 10), 0, 1)) || (isBetween((n % 10), 5, 9)) || (isBetween((n % 100), 12, 14)))
|
||||
return "many";
|
||||
if (n == 1)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"14": function (n) {
|
||||
if ((isBetween((n % 100), 3, 4)))
|
||||
return "few";
|
||||
if ((n % 100) == 2)
|
||||
return "two";
|
||||
if ((n % 100) == 1)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"15": function (n) {
|
||||
if (n == 0 || (isBetween((n % 100), 2, 10)))
|
||||
return "few";
|
||||
if ((isBetween((n % 100), 11, 19)))
|
||||
return "many";
|
||||
if (n == 1)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"16": function (n) {
|
||||
if ((n % 10) == 1 && n != 11)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"17": function (n) {
|
||||
if (n == 3)
|
||||
return "few";
|
||||
if (n == 0)
|
||||
return "zero";
|
||||
if (n == 6)
|
||||
return "many";
|
||||
if (n == 2)
|
||||
return "two";
|
||||
if (n == 1)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"18": function (n) {
|
||||
if (n == 0)
|
||||
return "zero";
|
||||
if ((isBetween(n, 0, 2)) && n != 0 && n != 2)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"19": function (n) {
|
||||
if ((isBetween(n, 2, 10)))
|
||||
return "few";
|
||||
if ((isBetween(n, 0, 1)))
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"20": function (n) {
|
||||
if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !(isBetween((n % 100), 10, 19) || isBetween((n % 100), 70, 79) || isBetween((n % 100), 90, 99)))
|
||||
return "few";
|
||||
if ((n % 1000000) == 0 && n != 0)
|
||||
return "many";
|
||||
if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92]))
|
||||
return "two";
|
||||
if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91]))
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"21": function (n) {
|
||||
if (n == 0)
|
||||
return "zero";
|
||||
if (n == 1)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"22": function (n) {
|
||||
if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99)))
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"23": function (n) {
|
||||
if ((isBetween((n % 10), 1, 2)) || (n % 20) == 0)
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
"24": function (n) {
|
||||
if ((isBetween(n, 3, 10) || isBetween(n, 13, 19)))
|
||||
return "few";
|
||||
if (isIn(n, [2, 12]))
|
||||
return "two";
|
||||
if (isIn(n, [1, 11]))
|
||||
return "one";
|
||||
return "other"
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a function that gives the plural form name for a given integer
|
||||
* for the specified `locale`
|
||||
* let fun = getRulesForLocale('en');
|
||||
* fun(1) -> 'one'
|
||||
* fun(0) -> 'other'
|
||||
* fun(1000) -> 'other'
|
||||
*/
|
||||
exports.getRulesForLocale = function getRulesForLocale(locale) {
|
||||
let index = LOCALES_TO_RULES[locale];
|
||||
if (!(index in RULES)) {
|
||||
console.warn('Plural form unknown for locale "' + locale + '"');
|
||||
return function () { return "other"; };
|
||||
}
|
||||
return RULES[index];
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
lazyRequire(this, "../system/events", "on");
|
||||
lazyRequireModule(this, "./core", "core");
|
||||
const { id: jetpackId } = require('../self');
|
||||
|
||||
const OPTIONS_DISPLAYED = "addon-options-displayed";
|
||||
|
||||
function enable() {
|
||||
on(OPTIONS_DISPLAYED, onOptionsDisplayed);
|
||||
}
|
||||
exports.enable = enable;
|
||||
|
||||
function onOptionsDisplayed({ subject: document, data: addonId }) {
|
||||
if (addonId !== jetpackId)
|
||||
return;
|
||||
localizeInlineOptions(document);
|
||||
}
|
||||
|
||||
function localizeInlineOptions(document) {
|
||||
let query = 'setting[data-jetpack-id="' + jetpackId + '"][pref-name], ' +
|
||||
'button[data-jetpack-id="' + jetpackId + '"][pref-name]';
|
||||
let nodes = document.querySelectorAll(query);
|
||||
for (let node of nodes) {
|
||||
let name = node.getAttribute("pref-name");
|
||||
if (node.tagName == "setting") {
|
||||
let desc = core.get(name + "_description");
|
||||
if (desc)
|
||||
node.setAttribute("desc", desc);
|
||||
let title = core.get(name + "_title");
|
||||
if (title)
|
||||
node.setAttribute("title", title);
|
||||
|
||||
for (let item of node.querySelectorAll("menuitem, radio")) {
|
||||
let key = name + "_options." + item.getAttribute("label");
|
||||
let label = core.get(key);
|
||||
if (label)
|
||||
item.setAttribute("label", label);
|
||||
}
|
||||
}
|
||||
else if (node.tagName == "button") {
|
||||
let label = core.get(name + "_label");
|
||||
if (label)
|
||||
node.setAttribute("label", label);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.localizeInlineOptions = localizeInlineOptions;
|
|
@ -1,89 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Cu } = require("chrome");
|
||||
lazyRequire(this, '../../url/utils', 'newURI');
|
||||
lazyRequire(this, "../plural-rules", 'getRulesForLocale');
|
||||
lazyRequire(this, '../locale', 'getPreferedLocales');
|
||||
const { rootURI } = require("@loader/options");
|
||||
const { Services } = require("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const baseURI = rootURI + "locale/";
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "preferedLocales", () => getPreferedLocales(true));
|
||||
|
||||
// Make sure we don't get stale data after an update
|
||||
// (See Bug 1300735 for rationale).
|
||||
Services.strings.flushBundles();
|
||||
|
||||
function getLocaleURL(locale) {
|
||||
// if the locale is a valid chrome URI, return it
|
||||
try {
|
||||
let uri = newURI(locale);
|
||||
if (uri.scheme == 'chrome')
|
||||
return uri.spec;
|
||||
}
|
||||
catch(_) {}
|
||||
// otherwise try to construct the url
|
||||
return baseURI + locale + ".properties";
|
||||
}
|
||||
|
||||
function getKey(locale, key) {
|
||||
let bundle = Services.strings.createBundle(getLocaleURL(locale));
|
||||
try {
|
||||
return bundle.GetStringFromName(key) + "";
|
||||
}
|
||||
catch (_) {}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function get(key, n, locales) {
|
||||
// try this locale
|
||||
let locale = locales.shift();
|
||||
let localized;
|
||||
|
||||
if (typeof n == 'number') {
|
||||
if (n == 0) {
|
||||
localized = getKey(locale, key + '[zero]');
|
||||
}
|
||||
else if (n == 1) {
|
||||
localized = getKey(locale, key + '[one]');
|
||||
}
|
||||
else if (n == 2) {
|
||||
localized = getKey(locale, key + '[two]');
|
||||
}
|
||||
|
||||
if (!localized) {
|
||||
// Retrieve the plural mapping function
|
||||
let pluralForm = (getRulesForLocale(locale.split("-")[0].toLowerCase()) ||
|
||||
getRulesForLocale("en"))(n);
|
||||
localized = getKey(locale, key + '[' + pluralForm + ']');
|
||||
}
|
||||
|
||||
if (!localized) {
|
||||
localized = getKey(locale, key + '[other]');
|
||||
}
|
||||
}
|
||||
|
||||
if (!localized) {
|
||||
localized = getKey(locale, key);
|
||||
}
|
||||
|
||||
if (!localized) {
|
||||
localized = getKey(locale, key + '[other]');
|
||||
}
|
||||
|
||||
if (localized) {
|
||||
return localized;
|
||||
}
|
||||
|
||||
// try next locale
|
||||
if (locales.length)
|
||||
return get(key, n, locales);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
exports.get = (k, n) => get(k, n, Array.slice(preferedLocales));
|
|
@ -1,178 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use strict';
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { Cc, Ci, Cu } = require('chrome');
|
||||
lazyRequire(this, '../system/events', "on");
|
||||
lazyRequire(this, '../self', "preferencesBranch");
|
||||
lazyRequire(this, '../l10n/prefs', "localizeInlineOptions");
|
||||
|
||||
lazyRequire(this, "resource://gre/modules/Services.jsm", "Services");
|
||||
lazyRequire(this, "resource://gre/modules/AddonManager.jsm", "AddonManager");
|
||||
lazyRequire(this, "resource://gre/modules/Preferences.jsm", "Preferences");
|
||||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";;
|
||||
const DEFAULT_OPTIONS_URL = 'data:text/xml,<placeholder/>';
|
||||
|
||||
const VALID_PREF_TYPES = ['bool', 'boolint', 'integer', 'string', 'color',
|
||||
'file', 'directory', 'control', 'menulist', 'radio'];
|
||||
|
||||
const isFennec = require("sdk/system/xul-app").is("Fennec");
|
||||
|
||||
function enable({ preferences, id }) {
|
||||
return new Promise(resolve => {
|
||||
validate(preferences);
|
||||
|
||||
setDefaults(preferences, preferencesBranch);
|
||||
|
||||
// allow the use of custom options.xul
|
||||
AddonManager.getAddonByID(id, (addon) => {
|
||||
on('addon-options-displayed', onAddonOptionsDisplayed, true);
|
||||
resolve({ id });
|
||||
});
|
||||
|
||||
function onAddonOptionsDisplayed({ subject: doc, data }) {
|
||||
if (data === id) {
|
||||
let parent;
|
||||
|
||||
if (isFennec) {
|
||||
parent = doc.querySelector('.options-box');
|
||||
|
||||
// NOTE: This disable the CSS rule that makes the options invisible
|
||||
let item = doc.querySelector('#addons-details .addon-item');
|
||||
item.removeAttribute("optionsURL");
|
||||
} else {
|
||||
parent = doc.getElementById('detail-downloads').parentNode;
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
injectOptions({
|
||||
preferences: preferences,
|
||||
preferencesBranch: preferencesBranch,
|
||||
document: doc,
|
||||
parent: parent,
|
||||
id: id
|
||||
});
|
||||
localizeInlineOptions(doc);
|
||||
} else {
|
||||
throw Error("Preferences parent node not found in Addon Details. The configured custom preferences will not be visible.");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.enable = enable;
|
||||
|
||||
// centralized sanity checks
|
||||
function validate(preferences) {
|
||||
for (let { name, title, type, label, options } of preferences) {
|
||||
// make sure the title is set and non-empty
|
||||
if (!title)
|
||||
throw Error("The '" + name + "' pref requires a title");
|
||||
|
||||
// make sure that pref type is a valid inline option type
|
||||
if (!~VALID_PREF_TYPES.indexOf(type))
|
||||
throw Error("The '" + name + "' pref must be of valid type");
|
||||
|
||||
// if it's a control, make sure it has a label
|
||||
if (type === 'control' && !label)
|
||||
throw Error("The '" + name + "' control requires a label");
|
||||
|
||||
// if it's a menulist or radio, make sure it has options
|
||||
if (type === 'menulist' || type === 'radio') {
|
||||
if (!options)
|
||||
throw Error("The '" + name + "' pref requires options");
|
||||
|
||||
// make sure each option has a value and a label
|
||||
for (let item of options) {
|
||||
if (!('value' in item) || !('label' in item))
|
||||
throw Error("Each option requires both a value and a label");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check that pref type matches default value type
|
||||
}
|
||||
}
|
||||
exports.validate = validate;
|
||||
|
||||
// initializes default preferences, emulates defaults/prefs.js
|
||||
function setDefaults(preferences, preferencesBranch) {
|
||||
let prefs = new Preferences({
|
||||
branch: `extensions.${preferencesBranch}.`,
|
||||
defaultBranch: true,
|
||||
});
|
||||
|
||||
for (let { name, value } of preferences)
|
||||
if (value !== undefined)
|
||||
prefs.set(name, value);
|
||||
}
|
||||
exports.setDefaults = setDefaults;
|
||||
|
||||
// dynamically injects inline options into about:addons page at runtime
|
||||
// NOTE: on Firefox Desktop the about:addons page is a xul page document,
|
||||
// on Firefox for Android the about:addons page is an xhtml page, to support both
|
||||
// the XUL xml namespace have to be enforced.
|
||||
function injectOptions({ preferences, preferencesBranch, document, parent, id }) {
|
||||
preferences.forEach(({name, type, hidden, title, description, label, options, on, off}) => {
|
||||
if (hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
let setting = document.createElementNS(XUL_NS, 'setting');
|
||||
setting.setAttribute('pref-name', name);
|
||||
setting.setAttribute('data-jetpack-id', id);
|
||||
setting.setAttribute('pref', 'extensions.' + preferencesBranch + '.' + name);
|
||||
setting.setAttribute('type', type);
|
||||
setting.setAttribute('title', title);
|
||||
if (description)
|
||||
setting.setAttribute('desc', description);
|
||||
|
||||
if (type === 'file' || type === 'directory') {
|
||||
setting.setAttribute('fullpath', 'true');
|
||||
}
|
||||
else if (type === 'control') {
|
||||
let button = document.createElementNS(XUL_NS, 'button');
|
||||
button.setAttribute('pref-name', name);
|
||||
button.setAttribute('data-jetpack-id', id);
|
||||
button.setAttribute('label', label);
|
||||
button.addEventListener('command', function() {
|
||||
Services.obs.notifyObservers(null, `${id}-cmdPressed`, name);
|
||||
}, true);
|
||||
setting.appendChild(button);
|
||||
}
|
||||
else if (type === 'boolint') {
|
||||
setting.setAttribute('on', on);
|
||||
setting.setAttribute('off', off);
|
||||
}
|
||||
else if (type === 'menulist') {
|
||||
let menulist = document.createElementNS(XUL_NS, 'menulist');
|
||||
let menupopup = document.createElementNS(XUL_NS, 'menupopup');
|
||||
for (let { value, label } of options) {
|
||||
let menuitem = document.createElementNS(XUL_NS, 'menuitem');
|
||||
menuitem.setAttribute('value', value);
|
||||
menuitem.setAttribute('label', label);
|
||||
menupopup.appendChild(menuitem);
|
||||
}
|
||||
menulist.appendChild(menupopup);
|
||||
setting.appendChild(menulist);
|
||||
}
|
||||
else if (type === 'radio') {
|
||||
let radiogroup = document.createElementNS(XUL_NS, 'radiogroup');
|
||||
for (let { value, label } of options) {
|
||||
let radio = document.createElementNS(XUL_NS, 'radio');
|
||||
radio.setAttribute('value', value);
|
||||
radio.setAttribute('label', label);
|
||||
radiogroup.appendChild(radio);
|
||||
}
|
||||
setting.appendChild(radiogroup);
|
||||
}
|
||||
|
||||
parent.appendChild(setting);
|
||||
});
|
||||
}
|
||||
exports.injectOptions = injectOptions;
|
Загрузка…
Ссылка в новой задаче