'use strict'; /** * Helper object to find all supported languages; * * (Needs l10n.js or l20n.js, and the settings permission) */ (function(exports) { var LOCALES_FILE = '/shared/resources/languages.json'; function readFile(file, callback) { return new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function loadFile() { if (xhr.readyState === 4) { if (xhr.status === 0 || xhr.status === 200) { resolve(xhr.response); } else { reject(xhr.statusText); } } }; xhr.open('GET', file, true); // async xhr.responseType = 'json'; xhr.send(); }); } function onError(name) { console.error('Error checking setting ' + name); } function readSetting(name, callback) { var settings = window.navigator.mozSettings; if (!settings || !settings.createLock) { return callback(null); } return settings.createLock().get(name).then(function(res) { return res[name]; }, onError.bind(null, name)); } exports.LanguageList = { _languages: null, // for stubbing in tests _readFile: readFile, _readSetting: readSetting, _extendPseudo: function(languages, currentLang, pseudoEnabled) { // 1. remove buildtime pseudolocales is pseudo is disabled if (!pseudoEnabled) { for (var code in languages) { var isCurrent = (code === currentLang); if (code.indexOf('-x-ps') > -1 && !isCurrent) { delete languages[code]; } } } if (!document.l10n) { return Promise.resolve(languages); } // 2. add the remaining runtime pseudolocales if pseudo enabled var runtimePseudoCodes = Object.keys(document.l10n.pseudo).filter( code => !(code in languages) && (code === currentLang || pseudoEnabled)); function obj(arr1, arr2) { const zipped = []; arr1.forEach(x => { arr2.forEach(y => { zipped.push([x, y]); }); }); return zipped.reduce( (obj, [x, y]) => Object.assign(obj, { [x]: y }), {}); /* jshint ignore: end */ } return Promise.all( runtimePseudoCodes.map( code => document.l10n.pseudo[code].getName())).then( names => Object.assign(languages, obj(runtimePseudoCodes, names))); }, _extendAdditional: function(languages, ver, additional) { /* jshint boss:true */ for (var lang in additional) { for (var i = 0, locale; locale = additional[lang][i]; i++) { if (locale.target === ver) { languages[lang] = locale.name; break; } } } }, _removeWithNoSpeech: function(languages, srEnabled) { if (srEnabled) { var speechLangs = new Set( window.speechSynthesis.getVoices().map(v => v.lang.split('-')[0])); for (var langName in languages) { if (!speechLangs.has(langName.split('-')[0])) { delete languages[langName]; } } } }, _copyObj: function(obj) { var copy = Object.create(null); for (var prop in obj) { copy[prop] = obj[prop]; } return copy; }, _parseVersion: function(ver) { return ver.split('.').slice(0, 2).join('.'); }, _build: function() { return Promise.all([ this._languages || (this._languages = this._readFile(LOCALES_FILE)), this._readSetting('language.current'), this._readSetting('devtools.pseudolocalization.enabled') ]).then(([langsFromFile, current, pseudoEnabled]) => { var langs = this._copyObj(langsFromFile); return Promise.all([ this._extendPseudo(langs, current, pseudoEnabled), this._readSetting('langpack.channel'), current, this._readSetting('accessibility.screenreader'), navigator.mozApps.getAdditionalLanguages() ]); }).then(([langs, ver, current, srEnabled, addl]) => { this._extendAdditional(langs, this._parseVersion(ver), addl); this._removeWithNoSpeech(langs, srEnabled); return [langs, current]; }); }, get: function(callback) { if (!callback) { return; } this._build().then(Function.prototype.apply.bind(callback, null)); }, wrapBidi: function(langCode, langName) { // Right-to-Left (RTL) languages: // (http://www.w3.org/International/questions/qa-scripts) // Arabic, Hebrew, Farsi, Pashto, Bidi English (pseudo), Urdu var rtlList = ['ar', 'he', 'fa', 'ps', 'ar-x-psbidi', 'ur']; // Use script direction control-characters to wrap the text labels // since markup (i.e. ) does not work inside