michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: "use strict"; michael@0: michael@0: module.metadata = { michael@0: "stability": "unstable" michael@0: }; michael@0: michael@0: const prefs = require("../preferences/service"); michael@0: const { Cu, Cc, Ci } = require("chrome"); michael@0: const { Services } = Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: /** michael@0: * Gets the currently selected locale for display. michael@0: * Gets all usable locale that we can use sorted by priority of relevance michael@0: * @return Array of locales, begins with highest priority michael@0: */ michael@0: const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; michael@0: const PREF_SELECTED_LOCALE = "general.useragent.locale"; michael@0: const PREF_ACCEPT_LANGUAGES = "intl.accept_languages"; michael@0: michael@0: function getPreferedLocales(caseSensitve) { michael@0: let locales = []; michael@0: function addLocale(locale) { michael@0: locale = locale.trim(); michael@0: if (!caseSensitve) michael@0: locale = locale.toLowerCase(); michael@0: if (locales.indexOf(locale) === -1) michael@0: locales.push(locale); michael@0: } michael@0: michael@0: // Most important locale is OS one. But we use it, only if michael@0: // "intl.locale.matchOS" pref is set to `true`. michael@0: // Currently only used for multi-locales mobile builds. michael@0: // http://mxr.mozilla.org/mozilla-central/source/mobile/android/installer/Makefile.in#46 michael@0: if (prefs.get(PREF_MATCH_OS_LOCALE, false)) { michael@0: let localeService = Cc["@mozilla.org/intl/nslocaleservice;1"]. michael@0: getService(Ci.nsILocaleService); michael@0: let osLocale = localeService.getLocaleComponentForUserAgent(); michael@0: addLocale(osLocale); michael@0: } michael@0: michael@0: // In some cases, mainly on Fennec and on Linux version, michael@0: // `general.useragent.locale` is a special 'localized' value, like: michael@0: // "chrome://global/locale/intl.properties" michael@0: let browserUiLocale = prefs.getLocalized(PREF_SELECTED_LOCALE, "") || michael@0: prefs.get(PREF_SELECTED_LOCALE, ""); michael@0: if (browserUiLocale) michael@0: addLocale(browserUiLocale); michael@0: michael@0: // Third priority is the list of locales used for web content michael@0: let contentLocales = prefs.get(PREF_ACCEPT_LANGUAGES, ""); michael@0: if (contentLocales) { michael@0: // This list is a string of locales seperated by commas. michael@0: // There is spaces after commas, so strip each item michael@0: for each(let locale in contentLocales.split(",")) michael@0: addLocale(locale.replace(/(^\s+)|(\s+$)/g, "")); michael@0: } michael@0: michael@0: // Finally, we ensure that en-US is the final fallback if it wasn't added michael@0: addLocale("en-US"); michael@0: michael@0: return locales; michael@0: } michael@0: exports.getPreferedLocales = getPreferedLocales; michael@0: michael@0: /** michael@0: * Selects the closest matching locale from a list of locales. michael@0: * michael@0: * @param aLocales michael@0: * An array of available locales michael@0: * @param aMatchLocales michael@0: * An array of prefered locales, ordered by priority. Most wanted first. michael@0: * Locales have to be in lowercase. michael@0: * If null, uses getPreferedLocales() results michael@0: * @return the best match for the currently selected locale michael@0: * michael@0: * Stolen from http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm michael@0: */ michael@0: exports.findClosestLocale = function findClosestLocale(aLocales, aMatchLocales) { michael@0: aMatchLocales = aMatchLocales || getPreferedLocales(); michael@0: michael@0: // Holds the best matching localized resource michael@0: let bestmatch = null; michael@0: // The number of locale parts it matched with michael@0: let bestmatchcount = 0; michael@0: // The number of locale parts in the match michael@0: let bestpartcount = 0; michael@0: michael@0: for each (let locale in aMatchLocales) { michael@0: let lparts = locale.split("-"); michael@0: for each (let localized in aLocales) { michael@0: let found = localized.toLowerCase(); michael@0: // Exact match is returned immediately michael@0: if (locale == found) michael@0: return localized; michael@0: michael@0: let fparts = found.split("-"); michael@0: /* If we have found a possible match and this one isn't any longer michael@0: then we dont need to check further. */ michael@0: if (bestmatch && fparts.length < bestmatchcount) michael@0: continue; michael@0: michael@0: // Count the number of parts that match michael@0: let maxmatchcount = Math.min(fparts.length, lparts.length); michael@0: let matchcount = 0; michael@0: while (matchcount < maxmatchcount && michael@0: fparts[matchcount] == lparts[matchcount]) michael@0: matchcount++; michael@0: michael@0: /* If we matched more than the last best match or matched the same and michael@0: this locale is less specific than the last best match. */ michael@0: if (matchcount > bestmatchcount || michael@0: (matchcount == bestmatchcount && fparts.length < bestpartcount)) { michael@0: bestmatch = localized; michael@0: bestmatchcount = matchcount; michael@0: bestpartcount = fparts.length; michael@0: } michael@0: } michael@0: // If we found a valid match for this locale return it michael@0: if (bestmatch) michael@0: return bestmatch; michael@0: } michael@0: return null; michael@0: }