summaryrefslogtreecommitdiffstats
path: root/intl/locale/tests/unit
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /intl/locale/tests/unit
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'intl/locale/tests/unit')
-rw-r--r--intl/locale/tests/unit/data/chrome.manifest1
-rw-r--r--intl/locale/tests/unit/data/intl_on_workers_worker.js6
-rw-r--r--intl/locale/tests/unit/test_bug1086527.js22
-rw-r--r--intl/locale/tests/unit/test_bug22310.js65
-rw-r--r--intl/locale/tests/unit/test_intl_on_workers.js29
-rw-r--r--intl/locale/tests/unit/test_langPackMatcher.js287
-rw-r--r--intl/locale/tests/unit/test_localeService.js240
-rw-r--r--intl/locale/tests/unit/test_localeService_negotiateLanguages.js205
-rw-r--r--intl/locale/tests/unit/test_osPreferences.js44
-rw-r--r--intl/locale/tests/unit/test_pluralForm.js390
-rw-r--r--intl/locale/tests/unit/test_pluralForm_english.js31
-rw-r--r--intl/locale/tests/unit/test_pluralForm_makeGetter.js38
-rw-r--r--intl/locale/tests/unit/xpcshell.ini21
13 files changed, 1379 insertions, 0 deletions
diff --git a/intl/locale/tests/unit/data/chrome.manifest b/intl/locale/tests/unit/data/chrome.manifest
new file mode 100644
index 0000000000..a8678deef3
--- /dev/null
+++ b/intl/locale/tests/unit/data/chrome.manifest
@@ -0,0 +1 @@
+content locale ./
diff --git a/intl/locale/tests/unit/data/intl_on_workers_worker.js b/intl/locale/tests/unit/data/intl_on_workers_worker.js
new file mode 100644
index 0000000000..e6f01e71a8
--- /dev/null
+++ b/intl/locale/tests/unit/data/intl_on_workers_worker.js
@@ -0,0 +1,6 @@
+/* eslint-env worker */
+
+self.onmessage = function (data) {
+ let myLocale = Intl.NumberFormat().resolvedOptions().locale;
+ self.postMessage(myLocale);
+};
diff --git a/intl/locale/tests/unit/test_bug1086527.js b/intl/locale/tests/unit/test_bug1086527.js
new file mode 100644
index 0000000000..5e33ce060a
--- /dev/null
+++ b/intl/locale/tests/unit/test_bug1086527.js
@@ -0,0 +1,22 @@
+/* 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/. */
+
+/**
+ * This unit test makes sure that PluralForm.get can be called from strict mode
+ */
+
+const { PluralForm } = ChromeUtils.importESModule(
+ "resource://gre/modules/PluralForm.sys.mjs"
+);
+
+delete PluralForm.numForms;
+delete PluralForm.get;
+[PluralForm.get, PluralForm.numForms] = PluralForm.makeGetter(9);
+
+function run_test() {
+ "use strict";
+
+ Assert.equal(3, PluralForm.numForms());
+ Assert.equal("one", PluralForm.get(5, "one;many"));
+}
diff --git a/intl/locale/tests/unit/test_bug22310.js b/intl/locale/tests/unit/test_bug22310.js
new file mode 100644
index 0000000000..c14296d931
--- /dev/null
+++ b/intl/locale/tests/unit/test_bug22310.js
@@ -0,0 +1,65 @@
+String.prototype.has = function (s) {
+ return this.includes(s);
+};
+
+function dt(locale) {
+ var date = new Date("2008-06-30T13:56:34");
+ const dtOptions = {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ hour: "numeric",
+ minute: "numeric",
+ second: "numeric",
+ };
+ return date.toLocaleString(locale, dtOptions);
+}
+
+var all_passed = true;
+const tests = [
+ [dt("en-US").has("June"), "month name in en-US"],
+ [dt("en-US").has("2008"), "year in en-US"],
+ [dt("da").has("jun"), "month name in da"],
+ [dt("da-DK") == dt("da"), "da same as da-DK"],
+ [
+ dt("en-GB").has("30") &&
+ dt("en-GB").has("June") &&
+ dt("en-GB").indexOf("30") < dt("en-GB").indexOf("June"),
+ "day before month in en-GB",
+ ],
+ [
+ dt("en-US").has("30") &&
+ dt("en-US").has("June") &&
+ dt("en-US").indexOf("30") > dt("en-US").indexOf("June"),
+ "month before day in en-US",
+ ],
+ [dt("ja-JP").has("\u5E746\u670830\u65E5"), "year month and day in ja-JP"],
+ // The Firefox locale code ja-JP-mac will be resolved to a BCP47-compliant
+ // tag ja-JP-x-lvariant-mac by uloc_toLanguageTag
+ [
+ dt("ja-JP") == dt("ja-JP-x-lvariant-mac"),
+ "ja-JP-x-lvariant-mac same as ja-JP",
+ ],
+ [dt("nn-NO").has("juni"), "month name in nn-NO"],
+ [dt("nb-NO").has("juni"), "month name in nb-NO"],
+ // Bug 1261775 - failures on win10
+ //[dt("no-NO").has("30. juni"), "month name in no-NO"],
+ [dt("sv-SE").has("30 jun"), "month name in sv-SE"],
+ [dt("kok").has("\u091C\u0942\u0928"), "month name in kok"],
+ [dt("ta-IN").has("\u0B9C\u0BC2\u0BA9\u0BCD"), "month name in ta-IN"],
+ [!!dt("ab-CD").length, "fallback for ab-CD"],
+];
+
+function one_test(testcase, msg) {
+ if (!testcase) {
+ all_passed = false;
+ dump("Unexpected date format: " + msg + "\n");
+ }
+}
+
+function run_test() {
+ for (var i = 0; i < tests.length; ++i) {
+ one_test(tests[i][0], tests[i][1]);
+ }
+ Assert.ok(all_passed);
+}
diff --git a/intl/locale/tests/unit/test_intl_on_workers.js b/intl/locale/tests/unit/test_intl_on_workers.js
new file mode 100644
index 0000000000..b5e05c4678
--- /dev/null
+++ b/intl/locale/tests/unit/test_intl_on_workers.js
@@ -0,0 +1,29 @@
+function run_test() {
+ do_load_manifest("data/chrome.manifest");
+
+ if (typeof Intl !== "object") {
+ dump("Intl not enabled, skipping test\n");
+ equal(true, true);
+ return;
+ }
+
+ let mainThreadLocale = Intl.NumberFormat().resolvedOptions().locale;
+ let testWorker = new Worker(
+ "chrome://locale/content/intl_on_workers_worker.js"
+ );
+ testWorker.onmessage = function (e) {
+ try {
+ let workerLocale = e.data;
+ equal(
+ mainThreadLocale,
+ workerLocale,
+ "Worker should inherit Intl locale from main thread."
+ );
+ } finally {
+ do_test_finished();
+ }
+ };
+
+ do_test_pending();
+ testWorker.postMessage("go!");
+}
diff --git a/intl/locale/tests/unit/test_langPackMatcher.js b/intl/locale/tests/unit/test_langPackMatcher.js
new file mode 100644
index 0000000000..03adec7bff
--- /dev/null
+++ b/intl/locale/tests/unit/test_langPackMatcher.js
@@ -0,0 +1,287 @@
+/* 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 { getAddonAndLocalAPIsMocker } = ChromeUtils.importESModule(
+ "resource://testing-common/LangPackMatcherTestUtils.sys.mjs"
+);
+const { LangPackMatcher } = ChromeUtils.importESModule(
+ "resource://gre/modules/LangPackMatcher.sys.mjs"
+);
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+
+const sandbox = sinon.createSandbox();
+const mockAddonAndLocaleAPIs = getAddonAndLocalAPIsMocker(this, sandbox);
+
+add_task(function initSandbox() {
+ registerCleanupFunction(() => {
+ sandbox.restore();
+ });
+});
+
+add_task(function test_appLocaleLanguageMismatch() {
+ sandbox.restore();
+ mockAddonAndLocaleAPIs({
+ systemLocale: "es-ES",
+ appLocale: "en-US",
+ });
+
+ deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
+ systemLocaleRaw: "es-ES",
+ systemLocale: { baseName: "es-ES", language: "es", region: "ES" },
+ appLocaleRaw: "en-US",
+ appLocale: { baseName: "en-US", language: "en", region: "US" },
+ matchType: "language-mismatch",
+ canLiveReload: true,
+ displayNames: {
+ systemLanguage: "Español (ES)",
+ appLanguage: "English (US)",
+ },
+ });
+});
+
+add_task(function test_appLocaleRegionMismatch() {
+ sandbox.restore();
+ mockAddonAndLocaleAPIs({
+ sandbox,
+ systemLocale: "en-CA",
+ appLocale: "en-US",
+ });
+
+ deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
+ systemLocaleRaw: "en-CA",
+ systemLocale: { baseName: "en-CA", language: "en", region: "CA" },
+ appLocaleRaw: "en-US",
+ appLocale: { baseName: "en-US", language: "en", region: "US" },
+ matchType: "region-mismatch",
+ canLiveReload: true,
+ displayNames: {
+ systemLanguage: "English (CA)",
+ appLanguage: "English (US)",
+ },
+ });
+});
+
+add_task(function test_appLocaleScriptMismatch() {
+ sandbox.restore();
+ // Script mismatch:
+ mockAddonAndLocaleAPIs({
+ sandbox,
+ systemLocale: "zh-Hans-CN",
+ appLocale: "zh-CN",
+ });
+
+ deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
+ systemLocaleRaw: "zh-Hans-CN",
+ systemLocale: { baseName: "zh-Hans-CN", language: "zh", region: "CN" },
+ appLocaleRaw: "zh-CN",
+ appLocale: { baseName: "zh-CN", language: "zh", region: "CN" },
+ matchType: "match",
+ canLiveReload: true,
+ displayNames: {
+ systemLanguage: "Chinese (Hans, China)",
+ appLanguage: "简体中文",
+ },
+ });
+});
+
+add_task(function test_appLocaleInvalidSystem() {
+ sandbox.restore();
+ // Script mismatch:
+ mockAddonAndLocaleAPIs({
+ sandbox,
+ systemLocale: "Not valid",
+ appLocale: "en-US",
+ });
+
+ deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
+ systemLocaleRaw: "Not valid",
+ systemLocale: null,
+ appLocaleRaw: "en-US",
+ appLocale: { baseName: "en-US", language: "en", region: "US" },
+ matchType: "unknown",
+ canLiveReload: null,
+ displayNames: { systemLanguage: null, appLanguage: "English (US)" },
+ });
+});
+
+add_task(function test_bidiSwitchDisabled() {
+ Services.prefs.setBoolPref(
+ "intl.multilingual.liveReloadBidirectional",
+ false
+ );
+ sandbox.restore();
+ // Script mismatch:
+ mockAddonAndLocaleAPIs({
+ sandbox,
+ systemLocale: "ar-EG",
+ appLocale: "en-US",
+ });
+
+ deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
+ systemLocaleRaw: "ar-EG",
+ systemLocale: { baseName: "ar-EG", language: "ar", region: "EG" },
+ appLocaleRaw: "en-US",
+ appLocale: { baseName: "en-US", language: "en", region: "US" },
+ matchType: "language-mismatch",
+ canLiveReload: false,
+ displayNames: {
+ systemLanguage: "Arabic (Egypt)",
+ appLanguage: "English (US)",
+ },
+ });
+ Services.prefs.clearUserPref("intl.multilingual.liveReloadBidirectional");
+});
+
+add_task(async function test_bidiSwitchEnabled() {
+ Services.prefs.setBoolPref("intl.multilingual.liveReloadBidirectional", true);
+ sandbox.restore();
+ // Script mismatch:
+ mockAddonAndLocaleAPIs({
+ sandbox,
+ systemLocale: "ar-EG",
+ appLocale: "en-US",
+ });
+
+ deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
+ systemLocaleRaw: "ar-EG",
+ systemLocale: { baseName: "ar-EG", language: "ar", region: "EG" },
+ appLocaleRaw: "en-US",
+ appLocale: { baseName: "en-US", language: "en", region: "US" },
+ matchType: "language-mismatch",
+ canLiveReload: true,
+ displayNames: {
+ systemLanguage: "Arabic (Egypt)",
+ appLanguage: "English (US)",
+ },
+ });
+
+ Services.prefs.clearUserPref("intl.multilingual.liveReloadBidirectional");
+});
+
+function shuffle(array) {
+ return array
+ .map(value => ({ value, sort: Math.random() }))
+ .sort((a, b) => a.sort - b.sort)
+ .map(({ value }) => value);
+}
+
+add_task(async function test_negotiateLangPacks() {
+ const negotiations = [
+ {
+ // Exact match found.
+ systemLocale: "en-US",
+ availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
+ expectedLangPack: "en-US",
+ expectedDisplayName: "English (US)",
+ },
+ {
+ // Region-less match.
+ systemLocale: "en-CA",
+ availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
+ expectedLangPack: "en",
+ expectedDisplayName: "English",
+ },
+ {
+ // Fallback to a different region.
+ systemLocale: "en-CA",
+ availableLangPacks: ["en-US", "zh", "zh-CN", "zh-Hans-CN"],
+ expectedLangPack: "en-US",
+ expectedDisplayName: "English (US)",
+ },
+ {
+ // Match with a script. zh-Hans-CN is the locale used with simplified
+ // Chinese scripts, while zh-CN uses the Latin script.
+ systemLocale: "zh-Hans-CN",
+ availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-TW", "zh-Hans-CN"],
+ expectedLangPack: "zh-Hans-CN",
+ expectedDisplayName: "Chinese (Hans, China)",
+ },
+ {
+ // Match excluding script but matching region. zh-Hant-TW should
+ // match zh-TW before zh-CN.
+ systemLocale: "zh-Hant-TW",
+ availableLangPacks: ["en", "zh", "zh-CN", "zh-TW"],
+ expectedLangPack: "zh-TW",
+ expectedDisplayName: "正體中文",
+ },
+ {
+ // No reasonable match could be found.
+ systemLocale: "tlh", // Klingon
+ availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
+ expectedLangPack: null,
+ expectedDisplayName: null,
+ },
+ {
+ // Weird, but valid locale identifiers.
+ systemLocale: "en-US-u-hc-h23-ca-islamic-civil-ss-true",
+ availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
+ expectedLangPack: "en-US",
+ expectedDisplayName: "English (US)",
+ },
+ {
+ // Invalid system locale
+ systemLocale: "Not valid",
+ availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
+ expectedLangPack: null,
+ expectedDisplayName: null,
+ },
+ ];
+
+ for (const {
+ systemLocale,
+ availableLangPacks,
+ expectedLangPack,
+ expectedDisplayName,
+ } of negotiations) {
+ sandbox.restore();
+ const { resolveLangPacks } = mockAddonAndLocaleAPIs({
+ sandbox,
+ systemLocale,
+ });
+
+ const promise = LangPackMatcher.negotiateLangPackForLanguageMismatch();
+ // Shuffle the order to ensure that this test doesn't require on ordering of the
+ // langpack responses.
+ resolveLangPacks(shuffle(availableLangPacks));
+ const { langPack, langPackDisplayName } = await promise;
+ equal(
+ langPack?.target_locale,
+ expectedLangPack,
+ `Resolve the systemLocale "${systemLocale}" with available langpacks: ${JSON.stringify(
+ availableLangPacks
+ )}`
+ );
+ equal(
+ langPackDisplayName,
+ expectedDisplayName,
+ "The display name matches."
+ );
+ }
+});
+
+add_task(async function test_ensureLangPackInstalled() {
+ sandbox.restore();
+ const { resolveLangPacks, resolveInstaller } = mockAddonAndLocaleAPIs({
+ sandbox,
+ systemLocale: "es-ES",
+ appLocale: "en-US",
+ });
+
+ const negotiatePromise =
+ LangPackMatcher.negotiateLangPackForLanguageMismatch();
+ resolveLangPacks(["es-ES"]);
+ const { langPack } = await negotiatePromise;
+
+ const installPromise1 = LangPackMatcher.ensureLangPackInstalled(langPack);
+ const installPromise2 = LangPackMatcher.ensureLangPackInstalled(langPack);
+
+ resolveInstaller(["fake langpack"]);
+
+ info("Ensure both installers resolve when called twice in a row.");
+ await installPromise1;
+ await installPromise2;
+ ok(true, "Both were called.");
+});
diff --git a/intl/locale/tests/unit/test_localeService.js b/intl/locale/tests/unit/test_localeService.js
new file mode 100644
index 0000000000..ae2d949e80
--- /dev/null
+++ b/intl/locale/tests/unit/test_localeService.js
@@ -0,0 +1,240 @@
+/* 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 osPrefs = Cc["@mozilla.org/intl/ospreferences;1"].getService(
+ Ci.mozIOSPreferences
+);
+
+const localeService = Services.locale;
+
+/**
+ * Make sure the locale service can be instantiated.
+ */
+
+add_test(function test_defaultLocale() {
+ const defaultLocale = localeService.defaultLocale;
+ Assert.ok(defaultLocale.length !== 0, "Default locale is not empty");
+ run_next_test();
+});
+
+add_test(function test_lastFallbackLocale() {
+ const lastFallbackLocale = localeService.lastFallbackLocale;
+ Assert.ok(lastFallbackLocale === "en-US", "Last fallback locale is en-US");
+ run_next_test();
+});
+
+add_test(function test_appLocalesAsLangTags() {
+ const appLocale = localeService.appLocaleAsLangTag;
+ Assert.ok(appLocale != "", "appLocale is non-empty");
+
+ const appLocales = localeService.appLocalesAsLangTags;
+ Assert.ok(Array.isArray(appLocales), "appLocales returns an array");
+
+ Assert.ok(
+ appLocale == appLocales[0],
+ "appLocale matches first entry in appLocales"
+ );
+
+ const enUSLocales = appLocales.filter(loc => loc === "en-US");
+ Assert.ok(enUSLocales.length == 1, "en-US is present exactly one time");
+
+ run_next_test();
+});
+
+const PREF_REQUESTED_LOCALES = "intl.locale.requested";
+const REQ_LOC_CHANGE_EVENT = "intl:requested-locales-changed";
+
+add_test(function test_requestedLocales() {
+ const requestedLocales = localeService.requestedLocales;
+ Assert.ok(
+ Array.isArray(requestedLocales),
+ "requestedLocales returns an array"
+ );
+
+ run_next_test();
+});
+
+/**
+ * In this test we verify that after we set an observer on the LocaleService
+ * event for requested locales change, it will be fired when the
+ * pref for matchOS is set to true.
+ *
+ * Then, we test that when the matchOS is set to true, we will retrieve
+ * OS locale from requestedLocales.
+ */
+add_test(function test_requestedLocales_matchOS() {
+ do_test_pending();
+
+ Services.prefs.setCharPref(PREF_REQUESTED_LOCALES, "ar-IR");
+
+ const observer = {
+ observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case REQ_LOC_CHANGE_EVENT:
+ const reqLocs = localeService.requestedLocales;
+ Assert.ok(reqLocs[0] === osPrefs.systemLocale);
+ Services.obs.removeObserver(observer, REQ_LOC_CHANGE_EVENT);
+ do_test_finished();
+ }
+ },
+ };
+
+ Services.obs.addObserver(observer, REQ_LOC_CHANGE_EVENT);
+ Services.prefs.setCharPref(PREF_REQUESTED_LOCALES, "");
+
+ run_next_test();
+});
+
+/**
+ * In this test we verify that after we set an observer on the LocaleService
+ * event for requested locales change, it will be fired when the
+ * pref for browser UI locale changes.
+ */
+add_test(function test_requestedLocales_onChange() {
+ do_test_pending();
+
+ Services.prefs.setCharPref(PREF_REQUESTED_LOCALES, "ar-IR");
+
+ const observer = {
+ observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case REQ_LOC_CHANGE_EVENT:
+ const reqLocs = localeService.requestedLocales;
+ Assert.ok(reqLocs[0] === "sr-RU");
+ Services.obs.removeObserver(observer, REQ_LOC_CHANGE_EVENT);
+ do_test_finished();
+ }
+ },
+ };
+
+ Services.obs.addObserver(observer, REQ_LOC_CHANGE_EVENT);
+ Services.prefs.setCharPref(PREF_REQUESTED_LOCALES, "sr-RU");
+
+ run_next_test();
+});
+
+add_test(function test_requestedLocale() {
+ Services.prefs.setCharPref(PREF_REQUESTED_LOCALES, "tlh");
+
+ let requestedLocale = localeService.requestedLocale;
+ Assert.ok(
+ requestedLocale === "tlh",
+ "requestedLocale returns the right value"
+ );
+
+ Services.prefs.clearUserPref(PREF_REQUESTED_LOCALES);
+
+ run_next_test();
+});
+
+add_test(function test_requestedLocales() {
+ localeService.requestedLocales = ["de-AT", "de-DE", "de-CH"];
+
+ let locales = localeService.requestedLocales;
+ Assert.ok(locales[0] === "de-AT");
+ Assert.ok(locales[1] === "de-DE");
+ Assert.ok(locales[2] === "de-CH");
+
+ run_next_test();
+});
+
+add_test(function test_isAppLocaleRTL() {
+ Assert.ok(typeof localeService.isAppLocaleRTL === "boolean");
+
+ run_next_test();
+});
+
+add_test(function test_isAppLocaleRTL_pseudo() {
+ let avLocales = localeService.availableLocales;
+ let reqLocales = localeService.requestedLocales;
+
+ localeService.availableLocales = ["en-US"];
+ localeService.requestedLocales = ["en-US"];
+ Services.prefs.setCharPref("intl.l10n.pseudo", "");
+
+ Assert.ok(localeService.isAppLocaleRTL === false);
+
+ Services.prefs.setCharPref("intl.l10n.pseudo", "bidi");
+ Assert.ok(localeService.isAppLocaleRTL === true);
+
+ Services.prefs.setCharPref("intl.l10n.pseudo", "accented");
+ Assert.ok(localeService.isAppLocaleRTL === false);
+
+ // Clean up
+ localeService.availableLocales = avLocales;
+ localeService.requestedLocales = reqLocales;
+ Services.prefs.clearUserPref("intl.l10n.pseudo");
+
+ run_next_test();
+});
+
+add_test(function test_packagedLocales() {
+ const locales = localeService.packagedLocales;
+ Assert.ok(locales.length !== 0, "Packaged locales are empty");
+ run_next_test();
+});
+
+add_test(function test_availableLocales() {
+ const avLocales = localeService.availableLocales;
+ localeService.availableLocales = ["und", "ar-IR"];
+
+ let locales = localeService.availableLocales;
+ Assert.ok(locales.length == 2);
+ Assert.ok(locales[0] === "und");
+ Assert.ok(locales[1] === "ar-IR");
+
+ localeService.availableLocales = avLocales;
+
+ run_next_test();
+});
+
+/**
+ * This test verifies that all values coming from the pref are sanitized.
+ */
+add_test(function test_requestedLocales_sanitize() {
+ Services.prefs.setStringPref(
+ PREF_REQUESTED_LOCALES,
+ "de,2,#$@#,pl,ąó,!a2,DE-at,,;"
+ );
+
+ let locales = localeService.requestedLocales;
+ Assert.equal(locales[0], "de");
+ Assert.equal(locales[1], "pl");
+ Assert.equal(locales[2], "de-AT");
+ Assert.equal(locales.length, 3);
+
+ Services.prefs.clearUserPref(PREF_REQUESTED_LOCALES);
+
+ run_next_test();
+});
+
+add_test(function test_handle_ja_JP_mac() {
+ const bkpAvLocales = localeService.availableLocales;
+
+ localeService.availableLocales = ["ja-JP-mac", "en-US"];
+
+ localeService.requestedLocales = ["ja-JP-mac"];
+
+ let reqLocales = localeService.requestedLocales;
+ Assert.equal(reqLocales[0], "ja-JP-macos");
+
+ let avLocales = localeService.availableLocales;
+ Assert.equal(avLocales[0], "ja-JP-macos");
+
+ let appLocales = localeService.appLocalesAsBCP47;
+ Assert.equal(appLocales[0], "ja-JP-macos");
+
+ let appLocalesAsLT = localeService.appLocalesAsLangTags;
+ Assert.equal(appLocalesAsLT[0], "ja-JP-mac");
+
+ Assert.equal(localeService.appLocaleAsLangTag, "ja-JP-mac");
+
+ localeService.availableLocales = bkpAvLocales;
+
+ run_next_test();
+});
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(PREF_REQUESTED_LOCALES);
+});
diff --git a/intl/locale/tests/unit/test_localeService_negotiateLanguages.js b/intl/locale/tests/unit/test_localeService_negotiateLanguages.js
new file mode 100644
index 0000000000..f929bc3a43
--- /dev/null
+++ b/intl/locale/tests/unit/test_localeService_negotiateLanguages.js
@@ -0,0 +1,205 @@
+/* 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 localeService = Services.locale;
+
+const data = {
+ filtering: {
+ "exact match": [
+ [["en"], ["en"], ["en"]],
+ [["en-US"], ["en-US"], ["en-US"]],
+ [["en-Latn-US"], ["en-Latn-US"], ["en-Latn-US"]],
+ [["en-Latn-US-windows"], ["en-Latn-US-windows"], ["en-Latn-US-windows"]],
+ [["fr-FR"], ["de", "it", "fr-FR"], ["fr-FR"]],
+ [
+ ["fr", "pl", "de-DE"],
+ ["pl", "en-US", "de-DE"],
+ ["pl", "de-DE"],
+ ],
+ ],
+ "available as range": [
+ [["en-US"], ["en"], ["en"]],
+ [["en-Latn-US"], ["en-US"], ["en-US"]],
+ [["en-US-windows"], ["en-US"], ["en-US"]],
+ [
+ ["fr-CA", "de-DE"],
+ ["fr", "it", "de"],
+ ["fr", "de"],
+ ],
+ [["ja-JP-windows"], ["ja"], ["ja"]],
+ [
+ ["en-Latn-GB", "en-Latn-IN"],
+ ["en-IN", "en-GB"],
+ ["en-GB", "en-IN"],
+ ],
+ ],
+ "should match on likely subtag": [
+ [["en"], ["en-GB", "de", "en-US"], ["en-US", "en-GB"]],
+ [
+ ["en"],
+ ["en-Latn-GB", "de", "en-Latn-US"],
+ ["en-Latn-US", "en-Latn-GB"],
+ ],
+ [["fr"], ["fr-CA", "fr-FR"], ["fr-FR", "fr-CA"]],
+ [["az-IR"], ["az-Latn", "az-Arab"], ["az-Arab"]],
+ [["sr-RU"], ["sr-Cyrl", "sr-Latn"], ["sr-Latn"]],
+ [["sr"], ["sr-Latn", "sr-Cyrl"], ["sr-Cyrl"]],
+ [["zh-GB"], ["zh-Hans", "zh-Hant"], ["zh-Hant"]],
+ [["sr", "ru"], ["sr-Latn", "ru"], ["ru"]],
+ [["sr-RU"], ["sr-Latn-RO", "sr-Cyrl"], ["sr-Latn-RO"]],
+ ],
+ "should match likelySubtag region over other regions": [
+ [["en-CA"], ["en-ZA", "en-GB", "en-US"], ["en-US", "en-ZA", "en-GB"]],
+ ],
+ "should match cross-region": [
+ [["en"], ["en-US"], ["en-US"]],
+ [["en-US"], ["en-GB"], ["en-GB"]],
+ [["en-Latn-US"], ["en-Latn-GB"], ["en-Latn-GB"]],
+ ],
+ "should match cross-variant": [
+ [["en-US-linux"], ["en-US-windows"], ["en-US-windows"]],
+ ],
+ "should prioritize properly": [
+ // exact match first
+ [
+ ["en-US"],
+ ["en-US-windows", "en", "en-US"],
+ ["en-US", "en", "en-US-windows"],
+ ],
+ // available as range second
+ [["en-Latn-US"], ["en-GB", "en-US"], ["en-US", "en-GB"]],
+ // likely subtags third
+ [["en"], ["en-Cyrl-US", "en-Latn-US"], ["en-Latn-US"]],
+ // variant range fourth
+ [
+ ["en-US-macos"],
+ ["en-US-windows", "en-GB-macos"],
+ ["en-US-windows", "en-GB-macos"],
+ ],
+ // regional range fifth
+ [["en-US-macos"], ["en-GB-windows"], ["en-GB-windows"]],
+ [["en-US"], ["en-GB", "en"], ["en", "en-GB"]],
+ [
+ ["fr-CA-macos", "de-DE"],
+ ["de-DE", "fr-FR-windows"],
+ ["fr-FR-windows", "de-DE"],
+ ],
+ ],
+ "should handle default locale properly": [
+ [["fr"], ["de", "it"], []],
+ [["fr"], ["de", "it"], "en-US", ["en-US"]],
+ [["fr"], ["de", "en-US"], "en-US", ["en-US"]],
+ [
+ ["fr", "de-DE"],
+ ["de-DE", "fr-CA"],
+ "en-US",
+ ["fr-CA", "de-DE", "en-US"],
+ ],
+ ],
+ "should handle all matches on the 1st higher than any on the 2nd": [
+ [
+ ["fr-CA-macos", "de-DE"],
+ ["de-DE", "fr-FR-windows"],
+ ["fr-FR-windows", "de-DE"],
+ ],
+ ],
+ "should handle cases and underscores, returning the form given in the 'available' list":
+ [
+ [["fr_FR"], ["fr-FR"], ["fr-FR"]],
+ [["fr_fr"], ["fr-FR"], ["fr-FR"]],
+ [["fr_Fr"], ["fr-fR"], ["fr-fR"]],
+ [["fr_lAtN_fr"], ["fr-Latn-FR"], ["fr-Latn-FR"]],
+ [["fr_FR"], ["fr_FR"], ["fr_FR"]],
+ [["fr-FR"], ["fr_FR"], ["fr_FR"]],
+ [["fr_Cyrl_FR_macos"], ["fr_Cyrl_fr-macos"], ["fr_Cyrl_fr-macos"]],
+ ],
+ "should handle mozilla specific 3-letter variants": [
+ [
+ ["ja-JP-mac", "de-DE"],
+ ["ja-JP-mac", "de-DE"],
+ ["ja-JP-mac", "de-DE"],
+ ],
+ ],
+ "should not crash on invalid input": [
+ [["fą-FŻ"], ["ór_Fń"], []],
+ [["2"], ["ąóżł"], []],
+ [[[""]], ["fr-FR"], []],
+ ],
+ "should not match on invalid input": [[["en"], ["ťŮ"], []]],
+ },
+ matching: {
+ "should match only one per requested": [
+ [
+ ["fr", "en"],
+ ["en-US", "fr-FR", "en", "fr"],
+ null,
+ localeService.langNegStrategyMatching,
+ ["fr", "en"],
+ ],
+ ],
+ },
+ lookup: {
+ "should match only one": [
+ [
+ ["fr-FR", "en"],
+ ["en-US", "fr-FR", "en", "fr"],
+ "en-US",
+ localeService.langNegStrategyLookup,
+ ["fr-FR"],
+ ],
+ ],
+ },
+};
+
+function run_test() {
+ const nl = localeService.negotiateLanguages;
+
+ const json = JSON.stringify;
+ for (const strategy in data) {
+ for (const groupName in data[strategy]) {
+ const group = data[strategy][groupName];
+ for (const test of group) {
+ const requested = test[0];
+ const available = test[1];
+ const defaultLocale = test.length > 3 ? test[2] : undefined;
+ const strategyInner = test.length > 4 ? test[3] : undefined;
+ const supported = test[test.length - 1];
+
+ const result = nl(test[0], test[1], defaultLocale, strategyInner);
+ deepEqual(
+ result,
+ supported,
+ `\nExpected ${json(requested)} * ${json(available)} = ${json(
+ supported
+ )}.\n`
+ );
+ }
+ }
+ }
+
+ // Verify that we error out when requested or available is not an array.
+ for (const [req, avail] in [
+ [null, ["fr-FR"]],
+ [undefined, ["fr-FR"]],
+ [2, ["fr-FR"]],
+ ["fr-FR", ["fr-FR"]],
+ [["fr-FR"], null],
+ [["fr-FR"], undefined],
+ [["fr-FR"], 2],
+ [["fr-FR"], "fr-FR"],
+ [[null], []],
+ [[undefined], []],
+ [[undefined], [null]],
+ [[undefined], [undefined]],
+ [[null], [null]],
+ [[[]], [[2]]],
+ ]) {
+ Assert.throws(
+ () => {
+ nl(req, avail);
+ },
+ err => err.result == Cr.NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY
+ );
+ }
+}
diff --git a/intl/locale/tests/unit/test_osPreferences.js b/intl/locale/tests/unit/test_osPreferences.js
new file mode 100644
index 0000000000..6d1bb637ff
--- /dev/null
+++ b/intl/locale/tests/unit/test_osPreferences.js
@@ -0,0 +1,44 @@
+/* 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/. */
+
+function run_test() {
+ const osprefs = Cc["@mozilla.org/intl/ospreferences;1"].getService(
+ Ci.mozIOSPreferences
+ );
+
+ const systemLocale = osprefs.systemLocale;
+ Assert.ok(systemLocale != "", "systemLocale is non-empty");
+
+ const systemLocales = osprefs.systemLocales;
+ Assert.ok(Array.isArray(systemLocales), "systemLocales returns an array");
+
+ Assert.ok(
+ systemLocale == systemLocales[0],
+ "systemLocale matches first entry in systemLocales"
+ );
+
+ const rgLocales = osprefs.regionalPrefsLocales;
+ Assert.ok(Array.isArray(rgLocales), "regionalPrefsLocales returns an array");
+
+ const getDateTimePatternTests = [
+ [osprefs.dateTimeFormatStyleNone, osprefs.dateTimeFormatStyleNone, ""],
+ [osprefs.dateTimeFormatStyleShort, osprefs.dateTimeFormatStyleNone, ""],
+ [osprefs.dateTimeFormatStyleNone, osprefs.dateTimeFormatStyleLong, "ar"],
+ [osprefs.dateTimeFormatStyleFull, osprefs.dateTimeFormatStyleMedium, "ru"],
+ ];
+
+ for (let i = 0; i < getDateTimePatternTests.length; i++) {
+ const test = getDateTimePatternTests[i];
+
+ const pattern = osprefs.getDateTimePattern(...test);
+ if (
+ test[0] !== osprefs.dateTimeFormatStyleNone &&
+ test[1] !== osprefs.dateTImeFormatStyleNone
+ ) {
+ Assert.greater(pattern.length, 0, "pattern is not empty.");
+ }
+ }
+
+ Assert.ok(1, "osprefs didn't crash");
+}
diff --git a/intl/locale/tests/unit/test_pluralForm.js b/intl/locale/tests/unit/test_pluralForm.js
new file mode 100644
index 0000000000..e7717d20c1
--- /dev/null
+++ b/intl/locale/tests/unit/test_pluralForm.js
@@ -0,0 +1,390 @@
+/* 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/. */
+
+/**
+ * Make sure each of the plural forms have the correct number of forms and
+ * match up in functionality.
+ */
+
+const { PluralForm } = ChromeUtils.importESModule(
+ "resource://gre/modules/PluralForm.sys.mjs"
+);
+
+function run_test() {
+ let allExpect = [
+ [
+ // 0: Chinese 0-9, 10-19, ..., 90-99
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ // 100-109, 110-119, ..., 190-199
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ // 200-209, 210-219, ..., 290-299
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ ],
+ [
+ // 1: English 0-9, 10-19, ..., 90-99
+ 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ // 100-109, 110-119, ..., 190-199
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ // 200-209, 210-219, ..., 290-299
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ ],
+ [
+ // 2: French 0-9, 10-19, ..., 90-99
+ 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ // 100-109, 110-119, ..., 190-199
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ // 200-209, 210-219, ..., 290-299
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ ],
+ [
+ // 3: Latvian 0-9, 10-19, ..., 90-99
+ 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3,
+ 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3,
+ 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3,
+ 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3,
+ // 100-109, 110-119, ..., 190-199
+ 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3,
+ 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3,
+ 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3,
+ 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3,
+ // 200-209, 210-219, ..., 290-299
+ 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3,
+ 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3,
+ 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3,
+ 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3,
+ ],
+ [
+ // 4: Scottish Gaelic 0-9, 10-19, ..., 90-99
+ 4, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ // 100-109, 110-119, ..., 190-199
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ // 200-209, 210-219, ..., 290-299
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ ],
+ [
+ // 5: Romanian 0-9, 10-19, ..., 90-99
+ 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ // 100-109, 110-119, ..., 190-199
+ 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ // 200-209, 210-219, ..., 290-299
+ 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ ],
+ [
+ // 6: Lithuanian 0-9, 10-19, ..., 90-99
+ 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 3, 3,
+ 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 3, 3, 3,
+ 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3,
+ // 100-109, 110-119, ..., 190-199
+ 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 3, 3,
+ 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 3, 3, 3,
+ 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3,
+ // 200-209, 210-219, ..., 290-299
+ 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 3, 3,
+ 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 3, 3, 3,
+ 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3,
+ ],
+ [
+ // 7: Russian 0-9, 10-19, ..., 90-99
+ 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3,
+ // 100-109, 110-119, ..., 190-199
+ 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3,
+ // 200-209, 210-219, ..., 290-299
+ 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3,
+ ],
+ [
+ // 8: Slovak 0-9, 10-19, ..., 90-99
+ 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ // 100-109, 110-119, ..., 190-199
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ // 200-209, 210-219, ..., 290-299
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ ],
+ [
+ // 9: Polish 0-9, 10-19, ..., 90-99
+ 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3,
+ // 100-109, 110-119, ..., 190-199
+ 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3,
+ // 200-209, 210-219, ..., 290-299
+ 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3,
+ ],
+ [
+ // 10: Slovenian 0-9, 10-19, ..., 90-99
+ 4, 1, 2, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ // 100-109, 110-119, ..., 190-199
+ 4, 1, 2, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ // 200-209, 210-219, ..., 290-299
+ 4, 1, 2, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ ],
+ [
+ // 11: Irish Gaeilge 0-9, 10-19, ..., 90-99
+ 5, 1, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ // 100-109, 110-119, ..., 190-199
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ // 200-209, 210-219, ..., 290-299
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ ],
+ [
+ // 12: Arabic 0-9, 10-19, ..., 90-99
+ 6, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ // 100-109, 110-119, ..., 190-199
+ 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ // 200-209, 210-219, ..., 290-299
+ 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ ],
+ [
+ // 13: Maltese 0-9, 10-19, ..., 90-99
+ 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ // 100-109, 110-119, ..., 190-199
+ 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ // 200-209, 210-219, ..., 290-299
+ 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ ],
+ [
+ // 14: Unused 0-9, 10-19, ..., 90-99
+ 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3,
+ 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3,
+ 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3,
+ 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3,
+ // 100-109, 110-119, ..., 190-199
+ 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3,
+ 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3,
+ 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3,
+ 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3,
+ // 200-209, 210-219, ..., 290-299
+ 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3,
+ 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3,
+ 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3,
+ 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3, 3, 3, 3,
+ ],
+ [
+ // 15: Icelandic, Macedonian 0-9, 10-19, ..., 90-99
+ 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2,
+ // 100-109, 110-119, ..., 190-199
+ 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2,
+ // 200-209, 210-219, ..., 290-299
+ 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2,
+ ],
+ [
+ // 16: Breton 0-9, 10-19, ..., 90-99
+ 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 2, 3, 3,
+ 5, 5, 5, 5, 3, 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 1, 2, 3, 3, 5, 5, 5, 5, 3,
+ 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ // 100-109, 110-119, ..., 190-199
+ 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 2, 3, 3,
+ 5, 5, 5, 5, 3, 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 1, 2, 3, 3, 5, 5, 5, 5, 3,
+ 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ // 200-209, 210-219, ..., 290-299
+ 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 2, 3, 3,
+ 5, 5, 5, 5, 3, 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 1, 2, 3, 3, 5, 5, 5, 5, 3,
+ 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 1, 2, 3, 3, 5, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ ],
+ [
+ // 17: Shuar 0-9, 10-19, ..., 90-99
+ 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ // 100-109, 110-119, ..., 190-199
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ // 200-209, 210-219, ..., 290-299
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ ],
+ [
+ // 18: Welsh 0-9, 10-19, ..., 90-99
+ 1, 2, 3, 4, 6, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ // 100-109, 110-119, ..., 190-199
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ // 200-209, 210-219, ..., 290-299
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ ],
+ [
+ // 19: Slavic languages (bs, hr, sr) 0-9, 10-19, ..., 90-99
+ 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3,
+ // 100-109, 110-119, ..., 190-199
+ 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3,
+ // 200-209, 210-219, ..., 290-299
+ 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 2, 2, 3, 3, 3, 3, 3,
+ ],
+ ];
+
+ for (let [rule, expect] of allExpect.entries()) {
+ print("\nTesting rule #" + rule);
+
+ let [get, numForms] = PluralForm.makeGetter(rule);
+
+ // Make sure the largest value expected matches the number of plural forms
+ let maxExpect = Math.max.apply(this, expect);
+ Assert.equal(maxExpect, numForms());
+
+ // Make a string of numbers, e.g., 1;2;3;4;5
+ let words = [];
+ for (let i = 1; i <= maxExpect; i++) {
+ words.push(i);
+ }
+ words = words.join(";");
+
+ // Make sure we get the expected number
+ for (let [index, number] of expect.entries()) {
+ print(
+ [
+ "Plural form of ",
+ index,
+ " should be ",
+ number,
+ " (",
+ words,
+ ")",
+ ].join("")
+ );
+ Assert.equal(get(index, words), number);
+ }
+ }
+}
diff --git a/intl/locale/tests/unit/test_pluralForm_english.js b/intl/locale/tests/unit/test_pluralForm_english.js
new file mode 100644
index 0000000000..7ad221e885
--- /dev/null
+++ b/intl/locale/tests/unit/test_pluralForm_english.js
@@ -0,0 +1,31 @@
+/* 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/. */
+
+/**
+ * This unit test makes sure the plural form for the default language (by
+ * development), English, is working for the PluralForm javascript module.
+ */
+
+const { PluralForm } = ChromeUtils.importESModule(
+ "resource://gre/modules/PluralForm.sys.mjs"
+);
+
+function run_test() {
+ // English has 2 plural forms
+ Assert.equal(2, PluralForm.numForms());
+
+ // Make sure for good inputs, things work as expected
+ for (var num = 0; num <= 200; num++) {
+ Assert.equal(
+ num == 1 ? "word" : "words",
+ PluralForm.get(num, "word;words")
+ );
+ }
+
+ // Not having enough plural forms defaults to the first form
+ Assert.equal("word", PluralForm.get(2, "word"));
+
+ // Empty forms defaults to the first form
+ Assert.equal("word", PluralForm.get(2, "word;"));
+}
diff --git a/intl/locale/tests/unit/test_pluralForm_makeGetter.js b/intl/locale/tests/unit/test_pluralForm_makeGetter.js
new file mode 100644
index 0000000000..0a5ec28f69
--- /dev/null
+++ b/intl/locale/tests/unit/test_pluralForm_makeGetter.js
@@ -0,0 +1,38 @@
+/* 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/. */
+
+/**
+ * This unit test makes sure the plural form for Irish Gaeilge is working by
+ * using the makeGetter method instead of using the default language (by
+ * development), English.
+ */
+
+const { PluralForm } = ChromeUtils.importESModule(
+ "resource://gre/modules/PluralForm.sys.mjs"
+);
+
+function run_test() {
+ // Irish is plural rule #11
+ let [get, numForms] = PluralForm.makeGetter(11);
+
+ // Irish has 5 plural forms
+ Assert.equal(5, numForms());
+
+ // I don't really know Irish, so I'll stick in some dummy text
+ let words = "is 1;is 2;is 3-6;is 7-10;everything else";
+
+ let test = function (text, low, high) {
+ for (let num = low; num <= high; num++) {
+ Assert.equal(text, get(num, words));
+ }
+ };
+
+ // Make sure for good inputs, things work as expected
+ test("everything else", 0, 0);
+ test("is 1", 1, 1);
+ test("is 2", 2, 2);
+ test("is 3-6", 3, 6);
+ test("is 7-10", 7, 10);
+ test("everything else", 11, 200);
+}
diff --git a/intl/locale/tests/unit/xpcshell.ini b/intl/locale/tests/unit/xpcshell.ini
new file mode 100644
index 0000000000..1b213949a1
--- /dev/null
+++ b/intl/locale/tests/unit/xpcshell.ini
@@ -0,0 +1,21 @@
+[DEFAULT]
+head =
+support-files =
+ data/intl_on_workers_worker.js
+ data/chrome.manifest
+
+[test_bug22310.js]
+skip-if = toolkit != "windows" && toolkit != "cocoa"
+
+[test_bug1086527.js]
+[test_intl_on_workers.js]
+skip-if = toolkit == "android" # bug 1309447
+[test_langPackMatcher.js]
+[test_pluralForm.js]
+[test_pluralForm_english.js]
+[test_pluralForm_makeGetter.js]
+
+[test_osPreferences.js]
+skip-if = toolkit == "android" # bug 1344596
+[test_localeService.js]
+[test_localeService_negotiateLanguages.js]