diff options
Diffstat (limited to '')
25 files changed, 2781 insertions, 0 deletions
diff --git a/intl/locale/tests/LangPackMatcherTestUtils.sys.mjs b/intl/locale/tests/LangPackMatcherTestUtils.sys.mjs new file mode 100644 index 0000000000..4b18f1be13 --- /dev/null +++ b/intl/locale/tests/LangPackMatcherTestUtils.sys.mjs @@ -0,0 +1,124 @@ +/* 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/. */ + +import { LangPackMatcher } from "resource://gre/modules/LangPackMatcher.sys.mjs"; + +/** + * LangPackMatcher.jsm calls out to to the addons store, which involves network requests. + * Other tests create a fake addons server, and install mock XPIs. At the time of this + * writing that infrastructure is not available for mochitests. + * + * Instead, this test mocks out APIs that have a side-effect, so the addons of the browser + * are never modified. + * + * The calls to get the app's locale and system's locale are also mocked so that the + * different language mismatch scenarios can be run through. + * + * The locales are BCP 47 identifiers: + * + * @param {{ + * sandbox: SinonSandbox, + * systemLocale: string, + * appLocale, string, + * }} + */ +export function getAddonAndLocalAPIsMocker(testScope, sandbox) { + const { info } = testScope; + return function mockAddonAndLocaleAPIs({ systemLocale, appLocale }) { + info("Mocking LangPackMatcher.jsm APIs"); + + let resolveLangPacks; + const langPackPromise = new Promise(resolve => { + resolveLangPacks = availableLangpacks => { + info( + `Resolving which langpacks are available for download: ${JSON.stringify( + availableLangpacks + )}` + ); + resolve( + availableLangpacks.map(locale => ({ + guid: `langpack-${locale}@firefox.mozilla.org`, + type: "language", + hash: locale, + target_locale: locale, + current_compatible_version: { + files: [ + { + platform: "all", + url: `http://example.com/${locale}.langpack.xpi`, + }, + ], + }, + })) + ); + }; + }); + + let resolveInstaller; + const installerPromise = new Promise(resolve => { + resolveInstaller = () => { + info("LangPack install finished."); + resolve(); + }; + }); + + const { mockable } = LangPackMatcher; + if (appLocale) { + const availableLocales = [appLocale]; + if (appLocale !== "en-US") { + // Ensure the fallback behavior is accurately simulated for Firefox. + availableLocales.push("en-US"); + } + sandbox + .stub(mockable, "getAvailableLocalesIncludingFallback") + .returns(availableLocales); + sandbox.stub(mockable, "getDefaultLocale").returns(appLocale); + sandbox.stub(mockable, "getAppLocaleAsBCP47").returns(appLocale); + sandbox.stub(mockable, "getLastFallbackLocale").returns("en-US"); + } + if (systemLocale) { + sandbox.stub(mockable, "getSystemLocale").returns(systemLocale); + } + + sandbox.stub(mockable, "getAvailableLangpacks").callsFake(() => { + info("Requesting which langpacks are available for download"); + return langPackPromise; + }); + + sandbox.stub(mockable, "installLangPack").callsFake(langPack => { + info(`LangPack install started, but pending: ${langPack.target_locale}`); + return installerPromise; + }); + + sandbox.stub(mockable, "setRequestedAppLocales").callsFake(locales => { + info( + `Changing the browser's requested locales to: ${JSON.stringify( + locales + )}` + ); + }); + + return { + /** + * Resolves the addons API call with available langpacks. Call with a list + * of BCP 47 identifiers. + * + * @type {(availableLangpacks: string[]) => {}} + */ + resolveLangPacks, + + /** + * Resolves the pending call to install a langpack. + * + * @type {() => {}} + */ + resolveInstaller, + + /** + * The mocked APIs. + */ + mockable, + }; + }; +} diff --git a/intl/locale/tests/gtest/TestAppDateTimeFormat.cpp b/intl/locale/tests/gtest/TestAppDateTimeFormat.cpp new file mode 100644 index 0000000000..a83b373428 --- /dev/null +++ b/intl/locale/tests/gtest/TestAppDateTimeFormat.cpp @@ -0,0 +1,310 @@ +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/intl/AppDateTimeFormat.h" +#include "mozilla/intl/DateTimeFormat.h" + +namespace mozilla::intl { +using Style = DateTimeFormat::Style; +using StyleBag = DateTimeFormat::StyleBag; +using ComponentsBag = DateTimeFormat::ComponentsBag; + +static DateTimeFormat::StyleBag ToStyleBag(Maybe<DateTimeFormat::Style> date, + Maybe<DateTimeFormat::Style> time) { + DateTimeFormat::StyleBag style; + style.date = date; + style.time = time; + return style; +} + +TEST(AppDateTimeFormat, FormatPRExplodedTime) +{ + PRTime prTime = 0; + PRExplodedTime prExplodedTime; + PR_ExplodeTime(prTime, PR_GMTParameters, &prExplodedTime); + + AppDateTimeFormat::sLocale = new nsCString("en-US"); + AppDateTimeFormat::DeleteCache(); + StyleBag style = ToStyleBag(Some(Style::Long), Some(Style::Long)); + + nsAutoString formattedTime; + nsresult rv = + AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(formattedTime.Find(u"January") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"12:00:00 AM") != kNotFound || + formattedTime.Find(u"12:00:00\u202FAM") != kNotFound || + formattedTime.Find(u"00:00:00") != kNotFound); + + prExplodedTime = {0, 0, 19, 0, 1, 0, 1970, 4, 0, {(19 * 60), 0}}; + + rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime); + + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(formattedTime.Find(u"January") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"12:19:00 AM") != kNotFound || + formattedTime.Find(u"12:19:00\u202FAM") != kNotFound || + formattedTime.Find(u"00:19:00") != kNotFound); + + prExplodedTime = {0, 0, 0, 7, 1, + 0, 1970, 4, 0, {(6 * 60 * 60), (1 * 60 * 60)}}; + rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(formattedTime.Find(u"January") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"7:00:00 AM") != kNotFound || + formattedTime.Find(u"7:00:00\u202FAM") != kNotFound || + formattedTime.Find(u"07:00:00") != kNotFound); + + prExplodedTime = { + 0, 0, 29, 11, 1, + 0, 1970, 4, 0, {(10 * 60 * 60) + (29 * 60), (1 * 60 * 60)}}; + rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(formattedTime.Find(u"January") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"11:29:00 AM") != kNotFound || + formattedTime.Find(u"11:29:00\u202FAM") != kNotFound || + formattedTime.Find(u"11:29:00") != kNotFound); + + prExplodedTime = {0, 0, 37, 23, 31, 11, 1969, 3, 364, {-(23 * 60), 0}}; + rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(formattedTime.Find(u"December") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"31") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"1969") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"11:37:00 PM") != kNotFound || + formattedTime.Find(u"11:37:00\u202FPM") != kNotFound || + formattedTime.Find(u"23:37:00") != kNotFound); + + prExplodedTime = {0, 0, 0, 17, 31, 11, 1969, 3, 364, {-(7 * 60 * 60), 0}}; + rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(formattedTime.Find(u"December") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"31") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"1969") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"5:00:00 PM") != kNotFound || + formattedTime.Find(u"5:00:00\u202FPM") != kNotFound || + formattedTime.Find(u"17:00:00") != kNotFound); + + prExplodedTime = { + 0, 0, 47, 14, 31, + 11, 1969, 3, 364, {-((10 * 60 * 60) + (13 * 60)), (1 * 60 * 60)}}; + rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(formattedTime.Find(u"December") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"31") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"1969") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"2:47:00 PM") != kNotFound || + formattedTime.Find(u"2:47:00\u202FPM") != kNotFound || + formattedTime.Find(u"14:47:00") != kNotFound); +} + +TEST(AppDateTimeFormat, DateFormatSelectors) +{ + PRTime prTime = 0; + PRExplodedTime prExplodedTime; + PR_ExplodeTime(prTime, PR_GMTParameters, &prExplodedTime); + + AppDateTimeFormat::sLocale = new nsCString("en-US"); + AppDateTimeFormat::DeleteCache(); + + nsAutoString formattedTime; + + { + ComponentsBag components{}; + components.year = Some(DateTimeFormat::Numeric::Numeric); + components.month = Some(DateTimeFormat::Month::TwoDigit); + + nsresult rv = + AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_STREQ("01/1970", NS_ConvertUTF16toUTF8(formattedTime).get()); + } + { + ComponentsBag components{}; + components.year = Some(DateTimeFormat::Numeric::Numeric); + components.month = Some(DateTimeFormat::Month::Long); + + nsresult rv = + AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_STREQ("January 1970", NS_ConvertUTF16toUTF8(formattedTime).get()); + } + { + ComponentsBag components{}; + components.month = Some(DateTimeFormat::Month::Long); + + nsresult rv = + AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_STREQ("January", NS_ConvertUTF16toUTF8(formattedTime).get()); + } + { + ComponentsBag components{}; + components.weekday = Some(DateTimeFormat::Text::Short); + + nsresult rv = + AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_STREQ("Thu", NS_ConvertUTF16toUTF8(formattedTime).get()); + } +} + +TEST(AppDateTimeFormat, FormatPRExplodedTimeForeign) +{ + PRTime prTime = 0; + PRExplodedTime prExplodedTime; + PR_ExplodeTime(prTime, PR_GMTParameters, &prExplodedTime); + + AppDateTimeFormat::sLocale = new nsCString("de-DE"); + AppDateTimeFormat::DeleteCache(); + StyleBag style = ToStyleBag(Some(Style::Long), Some(Style::Long)); + + nsAutoString formattedTime; + nsresult rv = + AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(formattedTime.Find(u"1.") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"Januar") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"12:00:00 AM") != kNotFound || + formattedTime.Find(u"12:00:00\u202FAM") != kNotFound || + formattedTime.Find(u"00:00:00") != kNotFound); + + prExplodedTime = {0, 0, 19, 0, 1, 0, 1970, 4, 0, {(19 * 60), 0}}; + rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(formattedTime.Find(u"1.") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"Januar") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"12:19:00 AM") != kNotFound || + formattedTime.Find(u"12:19:00\u202FAM") != kNotFound || + formattedTime.Find(u"00:19:00") != kNotFound); + + prExplodedTime = {0, 0, 0, 7, 1, + 0, 1970, 4, 0, {(6 * 60 * 60), (1 * 60 * 60)}}; + rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(formattedTime.Find(u"1.") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"Januar") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"7:00:00 AM") != kNotFound || + formattedTime.Find(u"7:00:00\u202FAM") != kNotFound || + formattedTime.Find(u"07:00:00") != kNotFound); + + prExplodedTime = { + 0, 0, 29, 11, 1, + 0, 1970, 4, 0, {(10 * 60 * 60) + (29 * 60), (1 * 60 * 60)}}; + rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(formattedTime.Find(u"1.") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"Januar") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"11:29:00 AM") != kNotFound || + formattedTime.Find(u"11:29:00\u202FAM") != kNotFound || + formattedTime.Find(u"11:29:00") != kNotFound); + + prExplodedTime = {0, 0, 37, 23, 31, 11, 1969, 3, 364, {-(23 * 60), 0}}; + rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(formattedTime.Find(u"31.") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"Dezember") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"1969") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"11:37:00 PM") != kNotFound || + formattedTime.Find(u"11:37:00\u202FPM") != kNotFound || + formattedTime.Find(u"23:37:00") != kNotFound); + + prExplodedTime = {0, 0, 0, 17, 31, 11, 1969, 3, 364, {-(7 * 60 * 60), 0}}; + rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(formattedTime.Find(u"31.") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"Dezember") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"1969") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"5:00:00 PM") != kNotFound || + formattedTime.Find(u"5:00:00\u202FPM") != kNotFound || + formattedTime.Find(u"17:00:00") != kNotFound); + + prExplodedTime = { + 0, 0, 47, 14, 31, + 11, 1969, 3, 364, {-((10 * 60 * 60) + (13 * 60)), (1 * 60 * 60)}}; + rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(formattedTime.Find(u"31.") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"Dezember") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"1969") != kNotFound); + ASSERT_TRUE(formattedTime.Find(u"2:47:00 PM") != kNotFound || + formattedTime.Find(u"2:47:00\u202FPM") != kNotFound || + formattedTime.Find(u"14:47:00") != kNotFound); +} + +TEST(AppDateTimeFormat, DateFormatSelectorsForeign) +{ + PRTime prTime = 0; + PRExplodedTime prExplodedTime; + PR_ExplodeTime(prTime, PR_GMTParameters, &prExplodedTime); + + AppDateTimeFormat::sLocale = new nsCString("de-DE"); + AppDateTimeFormat::DeleteCache(); + + nsAutoString formattedTime; + { + ComponentsBag components{}; + components.year = Some(DateTimeFormat::Numeric::Numeric); + components.month = Some(DateTimeFormat::Month::TwoDigit); + + nsresult rv = + AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_STREQ("01.1970", NS_ConvertUTF16toUTF8(formattedTime).get()); + } + { + ComponentsBag components{}; + components.year = Some(DateTimeFormat::Numeric::Numeric); + components.month = Some(DateTimeFormat::Month::Long); + + nsresult rv = + AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_STREQ("Januar 1970", NS_ConvertUTF16toUTF8(formattedTime).get()); + } + { + ComponentsBag components{}; + components.weekday = Some(DateTimeFormat::Text::Short); + + nsresult rv = + AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_STREQ("Do", NS_ConvertUTF16toUTF8(formattedTime).get()); + } + { + ComponentsBag components{}; + components.weekday = Some(DateTimeFormat::Text::Long); + + nsresult rv = + AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_STREQ("Donnerstag", NS_ConvertUTF16toUTF8(formattedTime).get()); + } + { + ComponentsBag components{}; + components.month = Some(DateTimeFormat::Month::Long); + + nsresult rv = + AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_STREQ("Januar", NS_ConvertUTF16toUTF8(formattedTime).get()); + } + { + ComponentsBag components{}; + components.weekday = Some(DateTimeFormat::Text::Short); + + nsresult rv = + AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_STREQ("Do", NS_ConvertUTF16toUTF8(formattedTime).get()); + } +} + +} // namespace mozilla::intl diff --git a/intl/locale/tests/gtest/TestLocaleService.cpp b/intl/locale/tests/gtest/TestLocaleService.cpp new file mode 100644 index 0000000000..2cf19727d6 --- /dev/null +++ b/intl/locale/tests/gtest/TestLocaleService.cpp @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "gtest/gtest.h" +#include "mozilla/Preferences.h" +#include "mozilla/intl/Locale.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/intl/Collator.h" + +using namespace mozilla::intl; + +TEST(Intl_Locale_LocaleService, CanonicalizeLanguageId) +{ + nsCString locale("en-US.POSIX"); + ASSERT_TRUE(LocaleService::CanonicalizeLanguageId(locale)); + ASSERT_TRUE(locale.EqualsLiteral("en-US")); + + locale.AssignLiteral("en-US_POSIX"); + ASSERT_TRUE(LocaleService::CanonicalizeLanguageId(locale)); + ASSERT_TRUE(locale.EqualsLiteral("en-US-posix")); + + locale.AssignLiteral("en-US-POSIX"); + ASSERT_TRUE(LocaleService::CanonicalizeLanguageId(locale)); + ASSERT_TRUE(locale.EqualsLiteral("en-US-posix")); + + locale.AssignLiteral("C"); + ASSERT_FALSE(LocaleService::CanonicalizeLanguageId(locale)); + ASSERT_TRUE(locale.EqualsLiteral("und")); + + locale.AssignLiteral(""); + ASSERT_FALSE(LocaleService::CanonicalizeLanguageId(locale)); + ASSERT_TRUE(locale.EqualsLiteral("und")); +} + +TEST(Intl_Locale_LocaleService, GetAppLocalesAsBCP47) +{ + nsTArray<nsCString> appLocales; + LocaleService::GetInstance()->GetAppLocalesAsBCP47(appLocales); + + ASSERT_FALSE(appLocales.IsEmpty()); +} + +TEST(Intl_Locale_LocaleService, GetAppLocalesAsLangTags) +{ + nsTArray<nsCString> appLocales; + LocaleService::GetInstance()->GetAppLocalesAsLangTags(appLocales); + + ASSERT_FALSE(appLocales.IsEmpty()); +} + +TEST(Intl_Locale_LocaleService, GetAppLocalesAsLangTags_lastIsPresent) +{ + nsAutoCString lastFallbackLocale; + LocaleService::GetInstance()->GetLastFallbackLocale(lastFallbackLocale); + + nsTArray<nsCString> appLocales; + LocaleService::GetInstance()->GetAppLocalesAsLangTags(appLocales); + + ASSERT_TRUE(appLocales.Contains(lastFallbackLocale)); +} + +TEST(Intl_Locale_LocaleService, GetAppLocaleAsLangTag) +{ + nsTArray<nsCString> appLocales; + LocaleService::GetInstance()->GetAppLocalesAsLangTags(appLocales); + + nsAutoCString locale; + LocaleService::GetInstance()->GetAppLocaleAsLangTag(locale); + + ASSERT_TRUE(appLocales[0] == locale); +} + +TEST(Intl_Locale_LocaleService, GetRegionalPrefsLocales) +{ + nsTArray<nsCString> rpLocales; + LocaleService::GetInstance()->GetRegionalPrefsLocales(rpLocales); + + int32_t len = rpLocales.Length(); + ASSERT_TRUE(len > 0); +} + +TEST(Intl_Locale_LocaleService, GetWebExposedLocales) +{ + const nsTArray<nsCString> spoofLocale{"de"_ns}; + LocaleService::GetInstance()->SetAvailableLocales(spoofLocale); + LocaleService::GetInstance()->SetRequestedLocales(spoofLocale); + + nsTArray<nsCString> pvLocales; + + mozilla::Preferences::SetInt("privacy.spoof_english", 0); + LocaleService::GetInstance()->GetWebExposedLocales(pvLocales); + ASSERT_TRUE(pvLocales.Length() > 0); + ASSERT_TRUE(pvLocales[0].Equals("de"_ns)); + + mozilla::Preferences::SetCString("intl.locale.privacy.web_exposed", "zh-TW"); + LocaleService::GetInstance()->GetWebExposedLocales(pvLocales); + ASSERT_TRUE(pvLocales.Length() > 0); + ASSERT_TRUE(pvLocales[0].Equals("zh-TW"_ns)); + + mozilla::Preferences::SetInt("privacy.spoof_english", 2); + LocaleService::GetInstance()->GetWebExposedLocales(pvLocales); + ASSERT_EQ(1u, pvLocales.Length()); + ASSERT_TRUE(pvLocales[0].Equals("en-US"_ns)); +} + +TEST(Intl_Locale_LocaleService, GetRequestedLocales) +{ + nsTArray<nsCString> reqLocales; + LocaleService::GetInstance()->GetRequestedLocales(reqLocales); + + int32_t len = reqLocales.Length(); + ASSERT_TRUE(len > 0); +} + +TEST(Intl_Locale_LocaleService, GetAvailableLocales) +{ + nsTArray<nsCString> availableLocales; + LocaleService::GetInstance()->GetAvailableLocales(availableLocales); + + int32_t len = availableLocales.Length(); + ASSERT_TRUE(len > 0); +} + +TEST(Intl_Locale_LocaleService, GetPackagedLocales) +{ + nsTArray<nsCString> packagedLocales; + LocaleService::GetInstance()->GetPackagedLocales(packagedLocales); + + int32_t len = packagedLocales.Length(); + ASSERT_TRUE(len > 0); +} + +TEST(Intl_Locale_LocaleService, GetDefaultLocale) +{ + nsAutoCString locStr; + LocaleService::GetInstance()->GetDefaultLocale(locStr); + + ASSERT_FALSE(locStr.IsEmpty()); + Locale loc; + ASSERT_TRUE(LocaleParser::TryParse(locStr, loc).isOk()); +} + +TEST(Intl_Locale_LocaleService, IsAppLocaleRTL) +{ + mozilla::Preferences::SetCString("intl.l10n.pseudo", "bidi"); + ASSERT_TRUE(LocaleService::GetInstance()->IsAppLocaleRTL()); + mozilla::Preferences::ClearUser("intl.l10n.pseudo"); +} + +TEST(Intl_Locale_LocaleService, TryCreateComponent) +{ + { + // Create a Collator with the app locale. + auto result = LocaleService::GetInstance()->TryCreateComponent<Collator>(); + ASSERT_TRUE(result.isOk()); + } + { + // Create a Collator with the "en" locale. + auto result = + LocaleService::GetInstance()->TryCreateComponentWithLocale<Collator>( + "en"); + ASSERT_TRUE(result.isOk()); + } + { + // Fallback to the app locale when an invalid one is used. + auto result = + LocaleService::GetInstance()->TryCreateComponentWithLocale<Collator>( + "$invalidName"); + ASSERT_TRUE(result.isOk()); + } +} diff --git a/intl/locale/tests/gtest/TestLocaleServiceNegotiate.cpp b/intl/locale/tests/gtest/TestLocaleServiceNegotiate.cpp new file mode 100644 index 0000000000..c428e81c8d --- /dev/null +++ b/intl/locale/tests/gtest/TestLocaleServiceNegotiate.cpp @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "gtest/gtest.h" +#include "mozilla/intl/LocaleService.h" + +using namespace mozilla::intl; + +TEST(Intl_Locale_LocaleService, Negotiate) +{ + nsTArray<nsCString> requestedLocales; + nsTArray<nsCString> availableLocales; + nsTArray<nsCString> supportedLocales; + nsAutoCString defaultLocale("en-US"); + int32_t strategy = LocaleService::kLangNegStrategyFiltering; + + requestedLocales.AppendElement("sr"_ns); + + availableLocales.AppendElement("sr-Cyrl"_ns); + availableLocales.AppendElement("sr-Latn"_ns); + + LocaleService::GetInstance()->NegotiateLanguages( + requestedLocales, availableLocales, defaultLocale, strategy, + supportedLocales); + + ASSERT_TRUE(supportedLocales.Length() == 2); + ASSERT_TRUE(supportedLocales[0].EqualsLiteral("sr-Cyrl")); + ASSERT_TRUE(supportedLocales[1].EqualsLiteral("en-US")); +} + +TEST(Intl_Locale_LocaleService, UseLSDefaultLocale) +{ + nsTArray<nsCString> requestedLocales; + nsTArray<nsCString> availableLocales; + nsTArray<nsCString> supportedLocales; + nsAutoCString defaultLocale("en-US"); + int32_t strategy = LocaleService::kLangNegStrategyLookup; + + requestedLocales.AppendElement("sr"_ns); + + availableLocales.AppendElement("de"_ns); + + LocaleService::GetInstance()->NegotiateLanguages( + requestedLocales, availableLocales, defaultLocale, strategy, + supportedLocales); + + nsAutoCString lsDefaultLocale; + LocaleService::GetInstance()->GetDefaultLocale(lsDefaultLocale); + ASSERT_TRUE(supportedLocales.Length() == 1); + ASSERT_TRUE(supportedLocales[0].Equals(lsDefaultLocale)); +} diff --git a/intl/locale/tests/gtest/TestOSPreferences.cpp b/intl/locale/tests/gtest/TestOSPreferences.cpp new file mode 100644 index 0000000000..7e3b71582b --- /dev/null +++ b/intl/locale/tests/gtest/TestOSPreferences.cpp @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "gtest/gtest.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Preferences.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/intl/OSPreferences.h" + +using namespace mozilla::intl; + +/** + * We test that on all platforms we test against (irrelevant of the tier), + * we will be able to retrieve at least a single locale out of the system. + * + * In theory, that may not be true, but if we encounter such platform we should + * decide how to handle this and special case and this test should make + * it not happen without us noticing. + */ +TEST(Intl_Locale_OSPreferences, GetSystemLocales) +{ + nsTArray<nsCString> systemLocales; + ASSERT_TRUE(NS_SUCCEEDED( + OSPreferences::GetInstance()->GetSystemLocales(systemLocales))); + + ASSERT_FALSE(systemLocales.IsEmpty()); +} + +/** + * We test that on all platforms we test against (irrelevant of the tier), + * we will be able to retrieve at least a single locale out of the system. + * + * In theory, that may not be true, but if we encounter such platform we should + * decide how to handle this and special case and this test should make + * it not happen without us noticing. + */ +TEST(Intl_Locale_OSPreferences, GetRegionalPrefsLocales) +{ + nsTArray<nsCString> rgLocales; + ASSERT_TRUE(NS_SUCCEEDED( + OSPreferences::GetInstance()->GetRegionalPrefsLocales(rgLocales))); + + ASSERT_FALSE(rgLocales.IsEmpty()); +} + +/** + * We test that on all platforms we test against, + * we will be able to retrieve a date and time pattern. + * + * This may come back empty on platforms where we don't have platforms + * bindings for, so effectively, we're testing for crashes. We should + * never crash. + */ +TEST(Intl_Locale_OSPreferences, GetDateTimePattern) +{ + nsAutoCString pattern; + OSPreferences* osprefs = OSPreferences::GetInstance(); + + struct Test { + int dateStyle; + int timeStyle; + const char* locale; + }; + Test tests[] = {{0, 0, ""}, {1, 0, "pl"}, {2, 0, "de-DE"}, {3, 0, "fr"}, + {4, 0, "ar"}, + + {0, 1, ""}, {0, 2, "it"}, {0, 3, ""}, {0, 4, "ru"}, + + {4, 1, ""}, {3, 2, "cs"}, {2, 3, ""}, {1, 4, "ja"}}; + + for (unsigned i = 0; i < mozilla::ArrayLength(tests); i++) { + const Test& t = tests[i]; + if (NS_SUCCEEDED(osprefs->GetDateTimePattern( + t.dateStyle, t.timeStyle, nsDependentCString(t.locale), pattern))) { + ASSERT_TRUE((t.dateStyle == 0 && t.timeStyle == 0) || !pattern.IsEmpty()); + } + } + + // If the locale is not specified, we should get the pattern corresponding to + // the first regional prefs locale. + AutoTArray<nsCString, 10> rpLocales; + LocaleService::GetInstance()->GetRegionalPrefsLocales(rpLocales); + ASSERT_TRUE(rpLocales.Length() > 0); + + nsAutoCString rpLocalePattern; + ASSERT_TRUE(NS_SUCCEEDED( + osprefs->GetDateTimePattern(mozIOSPreferences::dateTimeFormatStyleLong, + mozIOSPreferences::dateTimeFormatStyleLong, + rpLocales[0], rpLocalePattern))); + ASSERT_TRUE(NS_SUCCEEDED( + osprefs->GetDateTimePattern(mozIOSPreferences::dateTimeFormatStyleLong, + mozIOSPreferences::dateTimeFormatStyleLong, + nsDependentCString(""), pattern))); + ASSERT_EQ(rpLocalePattern, pattern); +} + +/** + * Test that is possible to override the OS defaults through a pref. + */ +TEST(Intl_Locale_OSPreferences, GetDateTimePatternPrefOverrides) +{ + nsresult nr; + nsAutoCString default_pattern, pattern; + OSPreferences* osprefs = OSPreferences::GetInstance(); + + struct { + const char* DatePref; + const char* TimePref; + int32_t DateTimeFormatStyle; + } configs[] = {{"intl.date_time.pattern_override.date_short", + "intl.date_time.pattern_override.time_short", + mozIOSPreferences::dateTimeFormatStyleShort}, + {"intl.date_time.pattern_override.date_medium", + "intl.date_time.pattern_override.time_medium", + mozIOSPreferences::dateTimeFormatStyleMedium}, + {"intl.date_time.pattern_override.date_long", + "intl.date_time.pattern_override.time_long", + mozIOSPreferences::dateTimeFormatStyleLong}, + {"intl.date_time.pattern_override.date_full", + "intl.date_time.pattern_override.time_full", + mozIOSPreferences::dateTimeFormatStyleFull}}; + + for (const auto& config : configs) { + // Get default value for the OS + nr = osprefs->GetDateTimePattern(config.DateTimeFormatStyle, + mozIOSPreferences::dateTimeFormatStyleNone, + nsDependentCString(""), default_pattern); + ASSERT_NS_SUCCEEDED(nr); + + // Override date format + mozilla::Preferences::SetCString(config.DatePref, "yy-MM"); + nr = osprefs->GetDateTimePattern(config.DateTimeFormatStyle, + mozIOSPreferences::dateTimeFormatStyleNone, + nsDependentCString(""), pattern); + ASSERT_NS_SUCCEEDED(nr); + ASSERT_TRUE(pattern.EqualsASCII("yy-MM")); + + // Override time format + mozilla::Preferences::SetCString(config.TimePref, "HH:mm"); + nr = osprefs->GetDateTimePattern(mozIOSPreferences::dateTimeFormatStyleNone, + config.DateTimeFormatStyle, + nsDependentCString(""), pattern); + ASSERT_NS_SUCCEEDED(nr); + ASSERT_TRUE(pattern.EqualsASCII("HH:mm")); + + // Override both + nr = osprefs->GetDateTimePattern(config.DateTimeFormatStyle, + config.DateTimeFormatStyle, + nsDependentCString(""), pattern); + ASSERT_NS_SUCCEEDED(nr); + ASSERT_TRUE(pattern.Find("yy-MM") != kNotFound); + ASSERT_TRUE(pattern.Find("HH:mm") != kNotFound); + + // Clear overrides, we should get the default value back. + mozilla::Preferences::ClearUser(config.DatePref); + mozilla::Preferences::ClearUser(config.TimePref); + nr = osprefs->GetDateTimePattern(config.DateTimeFormatStyle, + mozIOSPreferences::dateTimeFormatStyleNone, + nsDependentCString(""), pattern); + ASSERT_NS_SUCCEEDED(nr); + ASSERT_EQ(default_pattern, pattern); + } + + // Test overriding connector + nr = osprefs->GetDateTimePattern(mozIOSPreferences::dateTimeFormatStyleShort, + mozIOSPreferences::dateTimeFormatStyleShort, + nsDependentCString(""), default_pattern); + + mozilla::Preferences::SetCString("intl.date_time.pattern_override.date_short", + "yyyy-MM-dd"); + mozilla::Preferences::SetCString("intl.date_time.pattern_override.time_short", + "HH:mm:ss"); + mozilla::Preferences::SetCString( + "intl.date_time.pattern_override.connector_short", "{1} {0}"); + nr = osprefs->GetDateTimePattern(mozIOSPreferences::dateTimeFormatStyleShort, + mozIOSPreferences::dateTimeFormatStyleShort, + nsDependentCString(""), pattern); + ASSERT_NS_SUCCEEDED(nr); + ASSERT_TRUE(pattern.EqualsASCII("yyyy-MM-dd HH:mm:ss")); + + // Reset to date and time to defaults + mozilla::Preferences::ClearUser("intl.date_time.pattern_override.date_short"); + mozilla::Preferences::ClearUser("intl.date_time.pattern_override.time_short"); + + // Invalid patterns are ignored + mozilla::Preferences::SetCString( + "intl.date_time.pattern_override.connector_short", "hello, world!"); + nr = osprefs->GetDateTimePattern(mozIOSPreferences::dateTimeFormatStyleShort, + mozIOSPreferences::dateTimeFormatStyleShort, + nsDependentCString(""), pattern); + ASSERT_NS_SUCCEEDED(nr); + ASSERT_EQ(default_pattern, pattern); + + // Clearing the override results in getting the default pattern back. + mozilla::Preferences::ClearUser( + "intl.date_time.pattern_override.connector_short"); + nr = osprefs->GetDateTimePattern(mozIOSPreferences::dateTimeFormatStyleShort, + mozIOSPreferences::dateTimeFormatStyleShort, + nsDependentCString(""), pattern); + ASSERT_NS_SUCCEEDED(nr); + ASSERT_EQ(default_pattern, pattern); +} diff --git a/intl/locale/tests/gtest/moz.build b/intl/locale/tests/gtest/moz.build new file mode 100644 index 0000000000..bd2a2b8f86 --- /dev/null +++ b/intl/locale/tests/gtest/moz.build @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +UNIFIED_SOURCES += [ + "TestAppDateTimeFormat.cpp", + "TestLocaleService.cpp", + "TestLocaleServiceNegotiate.cpp", + "TestOSPreferences.cpp", +] + +FINAL_LIBRARY = "xul-gtest" diff --git a/intl/locale/tests/sort/us-ascii_base.txt b/intl/locale/tests/sort/us-ascii_base.txt new file mode 100644 index 0000000000..3bc7fbc7be --- /dev/null +++ b/intl/locale/tests/sort/us-ascii_base.txt @@ -0,0 +1,95 @@ + +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +\ +] +^ +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +{ +| +} +~ diff --git a/intl/locale/tests/sort/us-ascii_base_case_res.txt b/intl/locale/tests/sort/us-ascii_base_case_res.txt new file mode 100644 index 0000000000..3e526d226b --- /dev/null +++ b/intl/locale/tests/sort/us-ascii_base_case_res.txt @@ -0,0 +1,96 @@ +' +- + +! +" +# +$ +% +& +( +) +* +, +. +/ +: +; +? +@ +[ +\ +] +^ +_ +` +{ +| +} +~ ++ +< += +> +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +A +b +B +c +C +d +D +e +E +f +F +g +G +h +H +i +I +j +J +k +K +l +L +m +M +n +N +o +O +p +P +q +Q +r +R +s +S +t +T +u +U +v +V +w +W +x +X +y +Y +z +Z + diff --git a/intl/locale/tests/sort/us-ascii_base_nocase_res.txt b/intl/locale/tests/sort/us-ascii_base_nocase_res.txt new file mode 100644 index 0000000000..2d18096db5 --- /dev/null +++ b/intl/locale/tests/sort/us-ascii_base_nocase_res.txt @@ -0,0 +1,96 @@ +' +- + +! +" +# +$ +% +& +( +) +* +, +. +/ +: +; +? +@ +[ +\ +] +^ +_ +` +{ +| +} +~ ++ +< += +> +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +a +B +b +C +c +d +D +e +E +f +F +g +G +h +H +i +I +j +J +k +K +L +l +m +M +n +N +O +o +p +P +q +Q +r +R +s +S +t +T +u +U +v +V +w +W +X +x +Y +y +Z +z + diff --git a/intl/locale/tests/sort/us-ascii_sort.txt b/intl/locale/tests/sort/us-ascii_sort.txt new file mode 100644 index 0000000000..b6962f8de8 --- /dev/null +++ b/intl/locale/tests/sort/us-ascii_sort.txt @@ -0,0 +1,78 @@ +ludwig van beethoven +Ludwig van Beethoven +Ludwig van beethoven +Jane +jane +JANE +jAne +jaNe +janE +JAne +JaNe +JanE +JANe +JaNE +JAnE +jANE +Umberto Eco +Umberto eco +umberto eco +umberto Eco +UMBERTO ECO +ace +bash +*ace +!ace +%ace +~ace +#ace +cork +denizen +[denizen] +(denizen) +{denizen} +/denizen/ +#denizen# +$denizen$ +@denizen +elf +full +gnome +gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Japanese +gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Javanese +hint +Internationalization +Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization +Zinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizatio +n +Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalization +ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTioninternationalizationinternationalizationinternationalizationinternationalization +ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTion +jostle +kin +Laymen +lumens +Lumens +motleycrew +motley crew +niven's creative talents +nivens creative talents +opie +posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rockies +posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rokkies +posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the rockies +quilt's +quilts +quilt +Rondo +street +tamale oxidization and iodization in rapid progress +tamale oxidization and iodization in rapid Progress +until +vera +Wobble +Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoneme +Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoname +yearn +zodiac + diff --git a/intl/locale/tests/sort/us-ascii_sort_case_res.txt b/intl/locale/tests/sort/us-ascii_sort_case_res.txt new file mode 100644 index 0000000000..2720a83426 --- /dev/null +++ b/intl/locale/tests/sort/us-ascii_sort_case_res.txt @@ -0,0 +1,79 @@ +!ace +#ace +#denizen# +$denizen$ +%ace +(denizen) +*ace +/denizen/ +@denizen +[denizen] +{denizen} +~ace +ace +bash +cork +denizen +elf +full +gnome +gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Japanese +gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Javanese +hint +Internationalization +jane +janE +jaNe +jAne +jANE +Jane +JanE +JaNe +JaNE +JAne +JAnE +JANe +JANE +jostle +kin +Laymen +ludwig van beethoven +Ludwig van beethoven +Ludwig van Beethoven +lumens +Lumens +motley crew +motleycrew +nivens creative talents +niven's creative talents +opie +posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the rockies +posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rockies +posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rokkies +quilt +quilts +quilt's +Rondo +street +tamale oxidization and iodization in rapid progress +tamale oxidization and iodization in rapid Progress +umberto eco +umberto Eco +Umberto eco +Umberto Eco +UMBERTO ECO +until +veda +Veda +vera +Vera +Wobble +Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoname +Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoneme +yearn +Zinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization +Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalization +Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization +ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTion +ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTioninternationalizationinternationalizationinternationalizationinternationalization +zodiac diff --git a/intl/locale/tests/sort/us-ascii_sort_nocase_res.txt b/intl/locale/tests/sort/us-ascii_sort_nocase_res.txt new file mode 100644 index 0000000000..fa3426d4e9 --- /dev/null +++ b/intl/locale/tests/sort/us-ascii_sort_nocase_res.txt @@ -0,0 +1,79 @@ +!ace +#ace +#denizen# +$denizen$ +%ace +(denizen) +*ace +/denizen/ +@denizen +[denizen] +{denizen} +~ace +ace +bash +cork +denizen +elf +full +gnome +gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Japanese +gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Javanese +hint +Internationalization +JANe +jANE +JAnE +JaNE +Jane +JanE +JaNe +JAne +janE +jaNe +jAne +JANE +jane +jostle +kin +Laymen +Ludwig van beethoven +Ludwig van Beethoven +ludwig van beethoven +Lumens +lumens +motley crew +motleycrew +nivens creative talents +niven's creative talents +opie +posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the rockies +posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rockies +posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rokkies +quilt +quilts +quilt's +Rondo +street +tamale oxidization and iodization in rapid Progress +tamale oxidization and iodization in rapid progress +Umberto eco +Umberto Eco +umberto eco +UMBERTO ECO +umberto Eco +until +Veda +veda +Vera +vera +Wobble +Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoname +Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoneme +yearn +Zinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization +Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalization +ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTioninternationalizationinternationalizationinternationalizationinternationalization +ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTion +Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization +zodiac 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] |