summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/lib/RemoteL10n.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/lib/RemoteL10n.sys.mjs')
-rw-r--r--browser/components/newtab/lib/RemoteL10n.sys.mjs249
1 files changed, 249 insertions, 0 deletions
diff --git a/browser/components/newtab/lib/RemoteL10n.sys.mjs b/browser/components/newtab/lib/RemoteL10n.sys.mjs
new file mode 100644
index 0000000000..fe25c56a79
--- /dev/null
+++ b/browser/components/newtab/lib/RemoteL10n.sys.mjs
@@ -0,0 +1,249 @@
+/* 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/. */
+
+/**
+ * The downloaded Fluent file is located in this sub-directory of the local
+ * profile directory.
+ */
+const USE_REMOTE_L10N_PREF =
+ "browser.newtabpage.activity-stream.asrouter.useRemoteL10n";
+
+/**
+ * All supported locales for remote l10n
+ *
+ * This is used by ASRouter.jsm to check if the locale is supported before
+ * issuing the request for remote fluent files to RemoteSettings.
+ *
+ * Note:
+ * * this is generated based on "browser/locales/all-locales" as l10n doesn't
+ * provide an API to fetch that list
+ *
+ * * this list doesn't include "en-US", though "en-US" is well supported and
+ * `_RemoteL10n.isLocaleSupported()` will handle it properly
+ */
+const ALL_LOCALES = new Set([
+ "ach",
+ "af",
+ "an",
+ "ar",
+ "ast",
+ "az",
+ "be",
+ "bg",
+ "bn",
+ "bo",
+ "br",
+ "brx",
+ "bs",
+ "ca",
+ "ca-valencia",
+ "cak",
+ "ckb",
+ "cs",
+ "cy",
+ "da",
+ "de",
+ "dsb",
+ "el",
+ "en-CA",
+ "en-GB",
+ "eo",
+ "es-AR",
+ "es-CL",
+ "es-ES",
+ "es-MX",
+ "et",
+ "eu",
+ "fa",
+ "ff",
+ "fi",
+ "fr",
+ "fy-NL",
+ "ga-IE",
+ "gd",
+ "gl",
+ "gn",
+ "gu-IN",
+ "he",
+ "hi-IN",
+ "hr",
+ "hsb",
+ "hu",
+ "hy-AM",
+ "hye",
+ "ia",
+ "id",
+ "is",
+ "it",
+ "ja",
+ "ja-JP-mac",
+ "ka",
+ "kab",
+ "kk",
+ "km",
+ "kn",
+ "ko",
+ "lij",
+ "lo",
+ "lt",
+ "ltg",
+ "lv",
+ "meh",
+ "mk",
+ "mr",
+ "ms",
+ "my",
+ "nb-NO",
+ "ne-NP",
+ "nl",
+ "nn-NO",
+ "oc",
+ "pa-IN",
+ "pl",
+ "pt-BR",
+ "pt-PT",
+ "rm",
+ "ro",
+ "ru",
+ "scn",
+ "si",
+ "sk",
+ "sl",
+ "son",
+ "sq",
+ "sr",
+ "sv-SE",
+ "szl",
+ "ta",
+ "te",
+ "th",
+ "tl",
+ "tr",
+ "trs",
+ "uk",
+ "ur",
+ "uz",
+ "vi",
+ "wo",
+ "xh",
+ "zh-CN",
+ "zh-TW",
+]);
+
+export class _RemoteL10n {
+ constructor() {
+ this._l10n = null;
+ }
+
+ createElement(doc, elem, options = {}) {
+ let node;
+ if (options.content && options.content.string_id) {
+ node = doc.createElement("remote-text");
+ } else {
+ node = doc.createElementNS("http://www.w3.org/1999/xhtml", elem);
+ }
+ if (options.classList) {
+ node.classList.add(options.classList);
+ }
+ this.setString(node, options);
+
+ return node;
+ }
+
+ // If `string_id` is present it means we are relying on fluent for translations.
+ // Otherwise, we have a vanilla string.
+ setString(el, { content, attributes = {} }) {
+ if (content && content.string_id) {
+ for (let [fluentId, value] of Object.entries(attributes)) {
+ el.setAttribute(`fluent-variable-${fluentId}`, value);
+ }
+ el.setAttribute("fluent-remote-id", content.string_id);
+ } else {
+ el.textContent = content;
+ }
+ }
+
+ /**
+ * Creates a new DOMLocalization instance with the Fluent file from Remote Settings.
+ *
+ * Note: it will use the local Fluent file in any of following cases:
+ * * the remote Fluent file is not available
+ * * it was told to use the local Fluent file
+ */
+ _createDOML10n() {
+ /* istanbul ignore next */
+ let useRemoteL10n = Services.prefs.getBoolPref(USE_REMOTE_L10N_PREF, true);
+ if (useRemoteL10n && !L10nRegistry.getInstance().hasSource("cfr")) {
+ const appLocale = Services.locale.appLocaleAsBCP47;
+ const l10nFluentDir = PathUtils.join(
+ Services.dirsvc.get("ProfLD", Ci.nsIFile).path,
+ "settings",
+ "main",
+ "ms-language-packs"
+ );
+ let cfrIndexedFileSource = new L10nFileSource(
+ "cfr",
+ "app",
+ [appLocale],
+ `file://${l10nFluentDir}/`,
+ {
+ addResourceOptions: {
+ allowOverrides: true,
+ },
+ },
+ [`file://${l10nFluentDir}/browser/newtab/asrouter.ftl`]
+ );
+ L10nRegistry.getInstance().registerSources([cfrIndexedFileSource]);
+ } else if (!useRemoteL10n && L10nRegistry.getInstance().hasSource("cfr")) {
+ L10nRegistry.getInstance().removeSources(["cfr"]);
+ }
+
+ return new DOMLocalization(
+ [
+ "branding/brand.ftl",
+ "browser/defaultBrowserNotification.ftl",
+ "browser/newtab/asrouter.ftl",
+ "toolkit/branding/accounts.ftl",
+ "toolkit/branding/brandings.ftl",
+ ],
+ false
+ );
+ }
+
+ get l10n() {
+ if (!this._l10n) {
+ this._l10n = this._createDOML10n();
+ }
+ return this._l10n;
+ }
+
+ reloadL10n() {
+ this._l10n = null;
+ }
+
+ isLocaleSupported(locale) {
+ return locale === "en-US" || ALL_LOCALES.has(locale);
+ }
+
+ /**
+ * Format given `localizableText`.
+ *
+ * Format `localizableText` if it is an object using any `string_id` field,
+ * otherwise return `localizableText` unmodified.
+ *
+ * @param {object|string} `localizableText` to format.
+ * @return {string} formatted text.
+ */
+ async formatLocalizableText(localizableText) {
+ if (typeof localizableText !== "string") {
+ // It's more useful to get an error than passing through an object without
+ // a `string_id` field.
+ let value = await this.l10n.formatValue(localizableText.string_id);
+ return value;
+ }
+ return localizableText;
+ }
+}
+
+export const RemoteL10n = new _RemoteL10n();