From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- intl/locale/tests/unit/data/chrome.manifest | 1 + .../tests/unit/data/intl_on_workers_worker.js | 6 + intl/locale/tests/unit/test_bug1086527.js | 22 ++ intl/locale/tests/unit/test_bug22310.js | 65 ++++ intl/locale/tests/unit/test_intl_on_workers.js | 29 ++ intl/locale/tests/unit/test_langPackMatcher.js | 287 +++++++++++++++ intl/locale/tests/unit/test_localeService.js | 240 +++++++++++++ .../unit/test_localeService_negotiateLanguages.js | 205 +++++++++++ intl/locale/tests/unit/test_osPreferences.js | 44 +++ intl/locale/tests/unit/test_pluralForm.js | 390 +++++++++++++++++++++ intl/locale/tests/unit/test_pluralForm_english.js | 31 ++ .../tests/unit/test_pluralForm_makeGetter.js | 38 ++ intl/locale/tests/unit/xpcshell.ini | 21 ++ 13 files changed, 1379 insertions(+) create mode 100644 intl/locale/tests/unit/data/chrome.manifest create mode 100644 intl/locale/tests/unit/data/intl_on_workers_worker.js create mode 100644 intl/locale/tests/unit/test_bug1086527.js create mode 100644 intl/locale/tests/unit/test_bug22310.js create mode 100644 intl/locale/tests/unit/test_intl_on_workers.js create mode 100644 intl/locale/tests/unit/test_langPackMatcher.js create mode 100644 intl/locale/tests/unit/test_localeService.js create mode 100644 intl/locale/tests/unit/test_localeService_negotiateLanguages.js create mode 100644 intl/locale/tests/unit/test_osPreferences.js create mode 100644 intl/locale/tests/unit/test_pluralForm.js create mode 100644 intl/locale/tests/unit/test_pluralForm_english.js create mode 100644 intl/locale/tests/unit/test_pluralForm_makeGetter.js create mode 100644 intl/locale/tests/unit/xpcshell.ini (limited to 'intl/locale/tests/unit') 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] -- cgit v1.2.3