Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Bug 739249: Implement HTML Localization. #387

Merged
merged 1 commit into from

2 participants

ochameau Irakli Gozalishvili
ochameau
Owner

HTML localization feature described here:
https://github.com/mozilla/addon-sdk/wiki/HTML-Page-Localization
And discussed there:
https://groups.google.com/d/msg/mozilla-labs-jetpack/-/Jc-YQjf8tiQJ
Related bug:
https://bugzilla.mozilla.org/show_bug.cgi?id=739249

This pull request is not completely ready, I'd like to get some feedback on the new method based on DOM instead of templates.
This patch needs some more unit tests, but is considered closed to what can land.

Irakli Gozalishvili Gozala was assigned
packages/addon-kit/data/test-localization.html
@@ -0,0 +1,23 @@
+<!-- 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/. -->
+
+<html>
+ <head>
+ <title>HTML Localization</title>
+ </head>
+ <body>
+ <div data-l10n-id="Not translated">Kept as-is</div>
+ <ul data-l10n-id="Translated">
+ <li>Inner html content is replaced,</li>
+ <li data-l10n-id="text-content">
+ Elements with data-l10n-id, that are a (in)direct child of an element
+ with data-l10n-id, are lost.
Irakli Gozalishvili Owner
Gozala added a note

Do you mean are not localized ? Lost sounds quite brutal.

Irakli Gozalishvili Owner
Gozala added a note

Ok it looks like the owner element will be localized replacing all the content, but I think it's still better to rephrase this. Maybe @wbamberg can help.

ochameau Owner

What do you think about: Elements with data-l10n-id attribute whose parent element is translated will be replaced by the content of the translation.

Irakli Gozalishvili Owner
Gozala added a note

I think it's better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
packages/addon-kit/lib/l10n.js
((7 lines not shown))
const { getRulesForLocale } = require("api-utils/l10n/plural-rules");
-// Get URI for the addon root folder:
-const { rootURI } = require("@packaging");
-
-let globalHash = {};
-let pluralMappingFunction = getRulesForLocale("en");
+// Retrieve the plural mapping function
+let gPluralMappingFunction = null;
+if (core.locale) {
+ let shortLocaleCode = core.locale.split("-")[0].toLowerCase();
+ gPluralMappingFunction = getRulesForLocale(shortLocaleCode);
Irakli Gozalishvili Owner
Gozala added a note

It feels to me that api-utils/l10n/core should directly export short local.

ochameau Owner

I've added language attribute to core.
It looks as the best matching name for such attribute:
http://tools.ietf.org/html/rfc4646#page-5
But we can use shortLocale instead?

-or-

Rename locale to language and add shortLanguage in order to better match navigator.language (which is equal to current locale attribute)

Irakli Gozalishvili Owner
Gozala added a note

language sounds good to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
packages/addon-kit/lib/l10n.js
((7 lines not shown))
const { getRulesForLocale } = require("api-utils/l10n/plural-rules");
-// Get URI for the addon root folder:
-const { rootURI } = require("@packaging");
-
-let globalHash = {};
-let pluralMappingFunction = getRulesForLocale("en");
+// Retrieve the plural mapping function
+let gPluralMappingFunction = null;
Irakli Gozalishvili Owner
Gozala added a note

Why prefixing it with g ? In jetpack we don't use g prefixing pattern, as we don't have globals. Also in this case it does not looks like a global.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
packages/api-utils/lib/cuddlefish.js
@@ -231,6 +231,17 @@ const Loader = {
// in the addon's context. This function loads and executes the addon's
// entry point module.
main: function main(id, path) {
+ // Try initializing localization module before running main module
+ try {
+ // Do not enable HTML localization while running test as it is hard to
+ // disable. Because unit tests are evaluated in a another Loader who
+ // doesn't have access to this current loader.
+ if (path !== "test-harness/lib/run-tests.js")
+ this.require("api-utils/l10n/html").enable();
Irakli Gozalishvili Owner
Gozala added a note

I don't like this direction as it makes landing loader to firefox more complicated. As matter of fact in bug 743359 we're working on making loader independent of other modules in order to be able to land it to firefox. This on the other hand introduces new dependency. Unfortunately I do not have any good suggestions yet.

ochameau Owner

I totally agree with you and I don't think there is a better place for this. We are missing something for bug 743359. We just need to factors out this main method out of cuddlefish. We should not try to get rid of it. It just need to find another place.
Please see my comment in this bug: https://bugzilla.mozilla.org/show_bug.cgi?id=743359#c5
Can you just tell me if I should hold this during this discussion or can I go ahead and land this ?

Irakli Gozalishvili Owner
Gozala added a note

I will try to submit patch that would refactor parts of loader out in a similar way you've suggested. I'd rather see this change land on top of that one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
packages/api-utils/lib/l10n/html.js
((34 lines not shown))
+ // Accept only HTML documents
+ if (!(document instanceof Ci.nsIDOMHTMLDocument))
+ return;
+
+ // Accept only document from this addon
+ if (document.location.href.indexOf(uriPrefix) !== 0)
+ return;
+
+ // First hide content of the document in order to have content blinking
+ // between untranslated and translated states
+ // TODO: use result of bug 737003 discussion in order to avoid any conflict
+ // with document CSS
+ document.documentElement.style.visibility = "hidden";
+
+ // Wait for DOM tree to be built before applying localization
+ document.addEventListener("DOMContentLoaded", function listener() {
Irakli Gozalishvili Owner
Gozala added a note

Nit: would be nice if we could reuse same listener, which I think should be possible if you use event.target in the listener.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Irakli Gozalishvili
Owner

Overall it looks good! Not quite sure why this g prefix conventions all over the place.
Only real concern is a loader change, which would require landing all of this to firefox along with loader which I don't think is a good idea. We need to find a way to decouple add-on bootstrap stuff form loader itself.

ochameau
Owner

I rebased against last master. So that html localization is now initialized in addon/runner.js.

packages/addon-kit/locale/en-GB.properties
@@ -4,6 +4,8 @@
Translated= Yes
+text-content=no <span>HTML</span> injection
Irakli Gozalishvili Owner
Gozala added a note

Maybe <strong> or <italic> is better so it's visually distinct ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
packages/api-utils/lib/addon/runner.js
@@ -62,6 +62,16 @@ function startup(reason, options) {
if (reason === 'startup')
return wait(reason, options);
+ // Try initializing localization module before running main module
+ try {
+ // Do not enable HTML localization while running test as it is hard to
+ // disable. Because unit tests are evaluated in a another Loader who
+ // doesn't have access to this current loader.
+ if (options.loader.main.path !== "test-harness/lib/run-tests.js")
Irakli Gozalishvili Owner
Gozala added a note

Nit: I think it would be better off assertinng against main.id instead!! (Assuming value there is `'test-harness/run-tests')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
packages/api-utils/lib/addon/runner.js
@@ -62,6 +62,16 @@ function startup(reason, options) {
if (reason === 'startup')
return wait(reason, options);
+ // Try initializing localization module before running main module
+ try {
Irakli Gozalishvili Owner
Gozala added a note

This looks like if we fail to enable localization we will just log error. Could explain why in the comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
packages/api-utils/lib/l10n/core.js
((17 lines not shown))
+}
+
+Object.defineProperties(exports, {
+ // Returns the full length locale code: ja-JP-mac, en-US or fr
+ locale: {
+ get: function () {
+ return bestMatchingLocale;
+ }
+ },
+ // Returns the short locale code: ja, en, fr
+ language: {
+ get: function () {
+ return bestMatchingLocale.split("-")[0].toLowerCase();
+ }
+ }
+});
Irakli Gozalishvili Owner
Gozala added a note

I don't want to be picky, but is there reason why not us just simple functions instead, like locale() or getLocale() ? You can ignore it if you like as we do this in other places as well, but it looks like we won't be able to do it with ES.next modules so I'd suggest use of plain functions in new code so we don't increase surface for incompatibilities with harmony.

ochameau Owner

That's unfortunate! It is defined as getter just because I won't be able to get locale value synchronously in the long run.
Converting them to functions just sounds like a workaround ES.next limitation :(
I have in mind to convert l10n files access to asynchronous access,
We will have to delay addon main module evaluation before l10n files are loaded.
(We have to do that, as require("l10n").get is synchronous.)
Something like this in addon/runner.js:

require("api-utils/l10n/core").load().then(function () {
  // load main
  ...
  let program = load(loader, loader.main).exports;
  ...
});

But then, the unecessary init method in l10n will become this load method and be asynchronous.
So that we won't be able to set exports.locale during module loading
Do you have any idea how to define an attribute in ES.next that can't be defined during module load?
Is this a usecase where we have to end up with functions?

Irakli Gozalishvili Owner
Gozala added a note

ES.next won't allow dynamic properties / accessors as there is no exports object and imported modules are not an objects. You import objects / functions that were marked for export. Solution there is to export an object with such getters, which may be an option here as well.

As for async API that you've outlined above, I think it would be better to have it from day one, since usually users don't like switching from sync APIs to async. Also implications of such change may be bigger than it usually is.

Finally your then callback can return object with getters / properties l10n which may be a good alternative.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Irakli Gozalishvili Gozala commented on the diff
packages/api-utils/lib/l10n/core.js
((26 lines not shown))
+ // Returns the short locale code: ja, en, fr
+ language: {
+ get: function () {
+ return bestMatchingLocale.split("-")[0].toLowerCase();
+ }
+ }
+});
+
+function readURI(uri) {
+ let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Ci.nsIXMLHttpRequest);
+ request.open('GET', uri, false);
+ request.overrideMimeType('text/plain');
+ request.send();
+ return request.responseText;
+}
Irakli Gozalishvili Owner
Gozala added a note

Now that's yet another place we implement readURI I remember talking with @ZER0 about factoring it out to a separate module, don't know if he end up doing it, but maybe you could if he did not ?

If you prefer not to then please create a follow up bug to do that and put a comment referring to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
packages/api-utils/lib/l10n/core.js
((45 lines not shown))
+ return JSON.parse(readURI(uri));
+ }
+ catch(e) {
+ console.error("Error while reading locale file:\n" + uri + "\n" + e);
+ }
+ return {};
+}
+
+// Returns the array stored in `locales.json` manifest that list available
+// locales files
+function getAvailableLocales() {
+ let uri = rootURI + "locales.json";
+ let manifest = readJsonUri(uri);
+
+ return "locales" in manifest && Array.isArray(manifest.locales) ?
+ manifest.locales : [];
Irakli Gozalishvili Owner
Gozala added a note

Nit: It would be easier to read if you head line break just before Array.isArray in order to have complete condition in one line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Irakli Gozalishvili Gozala commented on the diff
packages/api-utils/lib/l10n/core.js
((79 lines not shown))
+ return rootURI + "locale/" + bestMatchingLocale + ".json";
+}
+
+function init() {
+ // First, search for a locale file:
+ let localeURI = getBestLocaleFile();
+ if (!localeURI)
+ return;
+
+ // 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?
+ globalHash = readJsonUri(localeURI);
+}
+init();
Irakli Gozalishvili Owner
Gozala added a note

It looks like you don't really need this function. In fact I would prefer following instead:

const globalHash = new function GlobalHash() {
   // your init function body here
}
ochameau Owner

It does not only set globalHash but inderectly set bestMatchingLocale.
I can just remove this method and put all this code in top level?
(but this method is meant to be exported, be asynchronous and be called by addon runner)
(See #387 (comment) for more info)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
packages/api-utils/lib/l10n/html.js
@@ -0,0 +1,84 @@
+/* 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/. */
+
+const { Ci } = require("chrome");
+const observers = require("api-utils/observer-service");
+const core = require("api-utils/l10n/core");
+const { uriPrefix } = require("@packaging");
Irakli Gozalishvili Owner
Gozala added a note

could you please use prefixURI instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
packages/api-utils/lib/l10n/html.js
((9 lines not shown))
+
+// 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)
Irakli Gozalishvili Owner
Gozala added a note

why not ?

if (data)
  child.textContent = data;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
packages/api-utils/lib/l10n/html.js
@@ -0,0 +1,84 @@
+/* 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/. */
+
+const { Ci } = require("chrome");
+const observers = require("api-utils/observer-service");
Irakli Gozalishvili Owner
Gozala added a note

Please use api-utils/system/events instead. I plan to deprecate this soonish!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Irakli Gozalishvili
Owner

Looks good r+ but please make sure to address following comments before landing:

All others are optional and I'd leave it up to you.

Thanks!

Irakli Gozalishvili
Owner

BTW do we really need to read all this file sync here ? Can't we do it async instead to avoid blocking firefox (specially since this happens at startup per add-on) ?

ochameau
Owner

I fixed prefixURI and used new observer event API.
I'd like to keep readURI refactoring in a followup patch as it won't be specific to html localization.
And yes, synchronous file access is bad and can be avoided. Especially now that we have addon/runner!
I can open another bug to start discussion how we should do that.
(see #387 (comment))

I didn't changed locale/language defined as getter. I'd like to avoid ending with function if possible.
There might be a way to avoid using getters while implementing asynchronous version!

May I land in these conditions?

Irakli Gozalishvili
Owner

@ochameau I'm not able to catch on IRC, so I'm writing here. I can leave with locale and language being getters (as we already do it in other places), but would really encourage to use simple functions instead, that way we can always improve API by switching to getters, which is much better than breaking API in a future and making it less pleasing.

Also I added comment on getters and ES.next modules above. Feel free to land as is if you feel strong about it. Also if you end up with a change no need for another review.

ochameau
Owner

I've changed these attributes to functions. It isn't that big deal for me, especially if we can find ways to improve it latter if we decide to give more visibility to these attributes.

ochameau ochameau merged commit 9bd8d3b into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 23, 2012
  1. ochameau
This page is out of date. Refresh to see the latest.
23 packages/addon-kit/data/test-localization.html
View
@@ -0,0 +1,23 @@
+<!-- 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/. -->
+
+<html>
+ <head>
+ <title>HTML Localization</title>
+ </head>
+ <body>
+ <div data-l10n-id="Not translated">Kept as-is</div>
+ <ul data-l10n-id="Translated">
+ <li>Inner html content is replaced,</li>
+ <li data-l10n-id="text-content">
+ Elements with data-l10n-id attribute whose parent element is translated
+ will be replaced by the content of the translation.
+ </li>
+ </ul>
+ <div data-l10n-id="text-content">No</div>
+ <div data-l10n-id="Translated">
+ A data-l10n-id value can be used in multiple elements
+ </div>
+ </body>
+</html
80 packages/addon-kit/lib/l10n.js
View
@@ -3,25 +3,21 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Cc, Ci } = require("chrome");
-const { getPreferedLocales, findClosestLocale } = require("api-utils/l10n/locale");
+const core = require("api-utils/l10n/core");
const { getRulesForLocale } = require("api-utils/l10n/plural-rules");
-// Get URI for the addon root folder:
-const { rootURI } = require("@packaging");
-
-let globalHash = {};
-let pluralMappingFunction = getRulesForLocale("en");
+// Retrieve the plural mapping function
+let pluralMappingFunction = getRulesForLocale(core.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");
// Get translation from big hashmap or default to hard coded string:
- let localized = globalHash[k] || k;
+ let localized = core.get(k) || k;
// # Simplest usecase:
// // String hard coded in source code:
@@ -81,69 +77,3 @@ exports.get = function get(k) {
return localized;
}
-
-function readURI(uri) {
- let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
- createInstance(Ci.nsIXMLHttpRequest);
- request.open('GET', uri, false);
- request.overrideMimeType('text/plain');
- request.send();
- return request.responseText;
-}
-
-function readJsonUri(uri) {
- try {
- return JSON.parse(readURI(uri));
- }
- catch(e) {
- console.error("Error while reading locale file:\n" + uri + "\n" + e);
- }
- return {};
-}
-
-// Returns the array stored in `locales.json` manifest that list available
-// locales files
-function getAvailableLocales() {
- let uri = rootURI + "locales.json";
- let manifest = readJsonUri(uri);
-
- return "locales" in manifest && Array.isArray(manifest.locales) ?
- manifest.locales : [];
-}
-
-// Returns URI of the best locales file to use from the XPI
-function getBestLocaleFile() {
-
- // Read localization manifest file that contains list of available languages
- let availableLocales = getAvailableLocales();
-
- // Retrieve list of prefered locales to use
- let preferedLocales = getPreferedLocales();
-
- // Compute the most preferable locale to use by using these two lists
- let bestMatchingLocale = findClosestLocale(availableLocales, preferedLocales);
-
- // It may be null if the addon doesn't have any locale file
- if (!bestMatchingLocale)
- return null;
-
- // Retrieve the related plural mapping function
- let shortLocaleCode = bestMatchingLocale.split("-")[0].toLowerCase();
- pluralMappingFunction = getRulesForLocale(shortLocaleCode);
-
- return rootURI + "locale/" + bestMatchingLocale + ".json";
-}
-
-function init() {
- // First, search for a locale file:
- let localeURI = getBestLocaleFile();
- if (!localeURI)
- return;
-
- // 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?
- globalHash = readJsonUri(localeURI);
-}
-init();
2  packages/addon-kit/locale/en-GB.properties
View
@@ -4,6 +4,8 @@
Translated= Yes
+text-content=no <b>HTML</b> injection
+
downloadsCount=%d downloads
downloadsCount[one]=one download
47 packages/addon-kit/tests/test-l10n.js
View
@@ -52,6 +52,53 @@ exports.testExactMatching = function(test) {
resetLocale();
}
+exports.testHtmlLocalization = function(test) {
+ test.waitUntilDone();
+
+ // Change the locale before loading new l10n modules in order to load
+ // the right .properties file
+ setLocale("en-GB");
+ let loader = Loader(module);
+
+ // Ensure initing html component that watch document creations
+ // Note that this module is automatically initialized in
+ // cuddlefish.js:Loader.main in regular addons. But it isn't for unit tests.
+ let loaderHtmlL10n = loader.require("api-utils/l10n/html");
+ loaderHtmlL10n.enable();
+
+ let uri = require("self").data.url("test-localization.html");
+ let worker = loader.require("page-worker").Page({
+ contentURL: uri,
+ contentScript: "new " + function ContentScriptScope() {
+ let nodes = document.body.querySelectorAll("*[data-l10n-id]");
+ self.postMessage([nodes[0].innerHTML,
+ nodes[1].innerHTML,
+ nodes[2].innerHTML,
+ nodes[3].innerHTML]);
+ },
+ onMessage: function (data) {
+ test.assertEqual(
+ data[0],
+ "Kept as-is",
+ "Nodes with unknown id in .properties are kept 'as-is'"
+ );
+ test.assertEqual(data[1], "Yes", "HTML is translated");
+ test.assertEqual(
+ data[2],
+ "no &lt;b&gt;HTML&lt;/b&gt; injection",
+ "Content from .properties is text content; HTML can't be injected."
+ );
+ test.assertEqual(data[3], "Yes", "Multiple elements with same data-l10n-id are accepted.");
+
+ loader.unload();
+ resetLocale();
+
+ test.done();
+ }
+ });
+
+}
+
exports.testEnUsLocaleName = function(test) {
let loader = Loader(module);
setLocale("en-US");
11 packages/api-utils/lib/addon/runner.js
View
@@ -62,6 +62,17 @@ function startup(reason, options) {
if (reason === 'startup')
return wait(reason, options);
+ // Try initializing localization module before running main module. Just print
+ // an exception in case of error, instead of preventing addon to be run.
+ try {
+ // Do not enable HTML localization while running test as it is hard to
+ // disable. Because unit tests are evaluated in a another Loader who
+ // doesn't have access to this current loader.
+ if (options.loader.main.id !== "test-harness/run-tests")
+ require("api-utils/l10n/html").enable();
+ } catch(error) {
+ console.exception(error);
+ }
try {
// TODO: When bug 564675 is implemented this will no longer be needed
// Always set the default prefs, because they disappear on restart
89 packages/api-utils/lib/l10n/core.js
View
@@ -0,0 +1,89 @@
+/* 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 { Cc, Ci } = require("chrome");
+const { getPreferedLocales, findClosestLocale } = require("api-utils/l10n/locale");
+
+// Get URI for the addon root folder:
+const { rootURI } = require("@packaging");
+
+let globalHash = {};
+let bestMatchingLocale = null;
+
+exports.get = function get(k) {
+ return k in globalHash ? globalHash[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.split("-")[0].toLowerCase();
+}
+
+function readURI(uri) {
+ let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Ci.nsIXMLHttpRequest);
+ request.open('GET', uri, false);
+ request.overrideMimeType('text/plain');
+ request.send();
+ return request.responseText;
+}
Irakli Gozalishvili Owner
Gozala added a note

Now that's yet another place we implement readURI I remember talking with @ZER0 about factoring it out to a separate module, don't know if he end up doing it, but maybe you could if he did not ?

If you prefer not to then please create a follow up bug to do that and put a comment referring to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+function readJsonUri(uri) {
+ try {
+ return JSON.parse(readURI(uri));
+ }
+ catch(e) {
+ console.error("Error while reading locale file:\n" + uri + "\n" + e);
+ }
+ return {};
+}
+
+// Returns the array stored in `locales.json` manifest that list available
+// locales files
+function getAvailableLocales() {
+ let uri = rootURI + "locales.json";
+ let manifest = readJsonUri(uri);
+
+ return "locales" in manifest &&
+ Array.isArray(manifest.locales) ?
+ manifest.locales : [];
+}
+
+// Returns URI of the best locales file to use from the XPI
+function getBestLocaleFile() {
+
+ // Read localization manifest file that contains list of available languages
+ let availableLocales = getAvailableLocales();
+
+ // Retrieve list of prefered locales to use
+ let preferedLocales = getPreferedLocales();
+
+ // Compute the most preferable locale to use by using these two lists
+ bestMatchingLocale = findClosestLocale(availableLocales, preferedLocales);
+
+ // It may be null if the addon doesn't have any locale file
+ if (!bestMatchingLocale)
+ return null;
+
+ return rootURI + "locale/" + bestMatchingLocale + ".json";
+}
+
+function init() {
+ // First, search for a locale file:
+ let localeURI = getBestLocaleFile();
+ if (!localeURI)
+ return;
+
+ // 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?
+ globalHash = readJsonUri(localeURI);
+}
+init();
Irakli Gozalishvili Owner
Gozala added a note

It looks like you don't really need this function. In fact I would prefer following instead:

const globalHash = new function GlobalHash() {
   // your init function body here
}
ochameau Owner

It does not only set globalHash but inderectly set bestMatchingLocale.
I can just remove this method and put all this code in top level?
(but this method is meant to be exported, be asynchronous and be called by addon runner)
(See #387 (comment) for more info)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
83 packages/api-utils/lib/l10n/html.js
View
@@ -0,0 +1,83 @@
+/* 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/. */
+
+const { Ci } = require("chrome");
+const events = require("api-utils/system/events");
+const core = require("api-utils/l10n/core");
+const { prefixURI } = require("@packaging");
+
+// 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;
+ }
+}
+exports.translateElement = translateElement;
+
+function onDocumentReady2Translate(event) {
+ let document = event.target;
+ document.removeEventListener("DOMContentLoaded", onDocumentReady2Translate,
+ false);
+
+ translateElement(document);
+
+ // Finally display document when we finished replacing all text content
+ document.documentElement.style.visibility = "visible";
+}
+
+function onContentWindow(event) {
+ let document = event.subject;
+
+ // Accept only HTML documents
+ if (!(document instanceof Ci.nsIDOMHTMLDocument))
+ return;
+
+ // Accept only document from this addon
+ if (document.location.href.indexOf(prefixURI) !== 0)
+ return;
+
+ // First hide content of the document in order to have content blinking
+ // between untranslated and translated states
+ // TODO: use result of bug 737003 discussion in order to avoid any conflict
+ // with document CSS
+ document.documentElement.style.visibility = "hidden";
+
+ // Wait for DOM tree to be built before applying localization
+ document.addEventListener("DOMContentLoaded", onDocumentReady2Translate,
+ false);
+}
+
+// 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) {
+ events.on(ON_CONTENT, onContentWindow);
+ enabled = true;
+ }
+}
+exports.enable = enable;
+
+function disable() {
+ if (enabled) {
+ events.off(ON_CONTENT, onContentWindow);
+ enabled = false;
+ }
+}
+exports.disable = disable;
+
+require("api-utils/unload").when(disable);
Something went wrong with that request. Please try again.