RemoteL10n.sys.mjs (5419B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * The downloaded Fluent file is located in this sub-directory of the local 7 * profile directory. 8 */ 9 const USE_REMOTE_L10N_PREF = 10 "browser.newtabpage.activity-stream.asrouter.useRemoteL10n"; 11 12 /** 13 * All supported locales for remote l10n 14 * 15 * This is used by ASRouter.sys.mjs to check if the locale is supported before 16 * issuing the request for remote fluent files to RemoteSettings. 17 * 18 * Note: 19 * * this is generated based on "browser/locales/all-locales" as l10n doesn't 20 * provide an API to fetch that list 21 * 22 * * this list doesn't include "en-US", though "en-US" is well supported and 23 * `_RemoteL10n.isLocaleSupported()` will handle it properly 24 */ 25 const ALL_LOCALES = new Set([ 26 "ach", 27 "af", 28 "an", 29 "ar", 30 "ast", 31 "az", 32 "be", 33 "bg", 34 "bn", 35 "bo", 36 "br", 37 "brx", 38 "bs", 39 "ca", 40 "ca-valencia", 41 "cak", 42 "ckb", 43 "cs", 44 "cy", 45 "da", 46 "de", 47 "dsb", 48 "el", 49 "en-CA", 50 "en-GB", 51 "eo", 52 "es-AR", 53 "es-CL", 54 "es-ES", 55 "es-MX", 56 "et", 57 "eu", 58 "fa", 59 "ff", 60 "fi", 61 "fr", 62 "fy-NL", 63 "ga-IE", 64 "gd", 65 "gl", 66 "gn", 67 "gu-IN", 68 "he", 69 "hi-IN", 70 "hr", 71 "hsb", 72 "hu", 73 "hy-AM", 74 "hye", 75 "ia", 76 "id", 77 "is", 78 "it", 79 "ja", 80 "ja-JP-mac", 81 "ka", 82 "kab", 83 "kk", 84 "km", 85 "kn", 86 "ko", 87 "lij", 88 "lo", 89 "lt", 90 "ltg", 91 "lv", 92 "meh", 93 "mk", 94 "mr", 95 "ms", 96 "my", 97 "nb-NO", 98 "ne-NP", 99 "nl", 100 "nn-NO", 101 "oc", 102 "pa-IN", 103 "pl", 104 "pt-BR", 105 "pt-PT", 106 "rm", 107 "ro", 108 "ru", 109 "scn", 110 "si", 111 "sk", 112 "sl", 113 "son", 114 "sq", 115 "sr", 116 "sv-SE", 117 "szl", 118 "ta", 119 "te", 120 "th", 121 "tl", 122 "tr", 123 "trs", 124 "uk", 125 "ur", 126 "uz", 127 "vi", 128 "wo", 129 "xh", 130 "zh-CN", 131 "zh-TW", 132 ]); 133 134 export class _RemoteL10n { 135 constructor() { 136 this._l10n = null; 137 } 138 139 createElement(doc, elem, options = {}) { 140 let node; 141 if (options.content && options.content.string_id) { 142 node = doc.createElement("remote-text"); 143 } else { 144 node = doc.createElementNS("http://www.w3.org/1999/xhtml", elem); 145 } 146 if (options.classList) { 147 node.classList.add(options.classList); 148 } 149 this.setString(node, options); 150 151 return node; 152 } 153 154 // If `string_id` is present it means we are relying on fluent for translations. 155 // Otherwise, we have a vanilla string. 156 setString(el, { content, attributes = {} }) { 157 if (content && content.string_id) { 158 for (let [fluentId, value] of Object.entries(attributes)) { 159 el.setAttribute(`fluent-variable-${fluentId}`, value); 160 } 161 el.setAttribute("fluent-remote-id", content.string_id); 162 } else { 163 el.textContent = content; 164 } 165 } 166 167 get cfrFluentFileDir() { 168 return PathUtils.join( 169 Services.dirsvc.get("ProfLD", Ci.nsIFile).path, 170 "settings", 171 "main", 172 "ms-language-packs" 173 ); 174 } 175 176 get cfrFluentFilePath() { 177 return PathUtils.join( 178 this.cfrFluentFileDir, 179 "browser", 180 "newtab", 181 "asrouter.ftl" 182 ); 183 } 184 185 /** 186 * Creates a new DOMLocalization instance with the Fluent file from Remote Settings. 187 * 188 * Note: it will use the local Fluent file in any of following cases: 189 * * the remote Fluent file is not available 190 * * it was told to use the local Fluent file 191 */ 192 _createDOML10n() { 193 /* istanbul ignore next */ 194 let useRemoteL10n = Services.prefs.getBoolPref(USE_REMOTE_L10N_PREF, true); 195 if (useRemoteL10n && !L10nRegistry.getInstance().hasSource("cfr")) { 196 const appLocale = Services.locale.appLocaleAsBCP47; 197 let cfrIndexedFileSource = new L10nFileSource( 198 "cfr", 199 "app", 200 [appLocale], 201 `${PathUtils.toFileURI(this.cfrFluentFileDir)}/`, 202 { 203 addResourceOptions: { 204 allowOverrides: true, 205 }, 206 }, 207 [PathUtils.toFileURI(this.cfrFluentFilePath)] 208 ); 209 L10nRegistry.getInstance().registerSources([cfrIndexedFileSource]); 210 } else if (!useRemoteL10n && L10nRegistry.getInstance().hasSource("cfr")) { 211 L10nRegistry.getInstance().removeSources(["cfr"]); 212 } 213 214 return new DOMLocalization( 215 [ 216 "branding/brand.ftl", 217 "browser/defaultBrowserNotification.ftl", 218 "browser/newtab/asrouter.ftl", 219 "browser/profiles.ftl", 220 "browser/termsofuse.ftl", 221 "toolkit/branding/brandings.ftl", 222 ], 223 false 224 ); 225 } 226 227 get l10n() { 228 if (!this._l10n) { 229 this._l10n = this._createDOML10n(); 230 } 231 return this._l10n; 232 } 233 234 reloadL10n() { 235 this._l10n = null; 236 } 237 238 isLocaleSupported(locale) { 239 return locale === "en-US" || ALL_LOCALES.has(locale); 240 } 241 242 /** 243 * Format given `localizableText`. 244 * 245 * Format `localizableText` if it is an object using any `string_id` field, 246 * otherwise return `localizableText` unmodified. 247 * 248 * @param {object|string} `localizableText` to format. 249 * @return {string} formatted text. 250 */ 251 async formatLocalizableText(localizableText) { 252 if (typeof localizableText !== "string") { 253 // It's more useful to get an error than passing through an object without 254 // a `string_id` field. 255 let value = await this.l10n.formatValue(localizableText.string_id); 256 return value; 257 } 258 return localizableText; 259 } 260 } 261 262 export const RemoteL10n = new _RemoteL10n();