author | Zibi Braniecki <zbraniecki@mozilla.com> |
Thu, 25 Jan 2018 14:50:32 -0800 | |
changeset 747691 | 141a9cae473ed5b29f7e7774496864eff8bfdbb7 |
parent 747278 | 20b0802c3f00bd0f440194b869201ac94144732f |
push id | 96986 |
push user | bmo:gandalf@aviary.pl |
push date | Fri, 26 Jan 2018 17:57:56 +0000 |
reviewers | jfkthame |
bugs | 1428698 |
milestone | 60.0a1 |
--- a/intl/locale/LocaleService.cpp +++ b/intl/locale/LocaleService.cpp @@ -441,95 +441,98 @@ LocaleService::FilterMatches(const nsTAr LangNegStrategy aStrategy, nsTArray<nsCString>& aRetVal) { // Local copy of the list of available locales, in Locale form for flexible // matching. We will remove entries from this list as they get appended to // aRetVal, so that no available locale will be found more than once. AutoTArray<Locale, 100> availLocales; for (auto& avail : aAvailable) { - availLocales.AppendElement(Locale(avail, true)); + availLocales.AppendElement(Locale(avail)); } // Helper to erase an entry from availLocales once we have copied it to // the result list. Returns an iterator pointing to the entry that was // immediately after the one that was erased (or availLocales.end() if // the target was the last in the array). auto eraseFromAvail = [&](nsTArray<Locale>::iterator aIter) { nsTArray<Locale>::size_type index = aIter - availLocales.begin(); availLocales.RemoveElementAt(index); return availLocales.begin() + index; }; for (auto& requested : aRequested) { + if (requested.IsEmpty()) { + continue; + } // 1) Try to find a simple (case-insensitive) string match for the request. - auto matchesExactly = [&](const Locale& aLoc) { + auto matchesExactly = [&](Locale& aLoc) { return requested.Equals(aLoc.AsString(), nsCaseInsensitiveCStringComparator()); }; auto match = std::find_if(availLocales.begin(), availLocales.end(), matchesExactly); if (match != availLocales.end()) { aRetVal.AppendElement(match->AsString()); eraseFromAvail(match); } if (!aRetVal.IsEmpty()) { HANDLE_STRATEGY; } // 2) Try to match against the available locales treated as ranges. - auto findRangeMatches = [&](const Locale& aReq) { - auto matchesRange = [&](const Locale& aLoc) { - return aReq.Matches(aLoc); + auto findRangeMatches = [&](Locale& aReq, bool aAvailRange, bool aReqRange) { + auto matchesRange = [&](Locale& aLoc) { + return aLoc.Matches(aReq, aAvailRange, aReqRange); }; bool foundMatch = false; auto match = availLocales.begin(); while ((match = std::find_if(match, availLocales.end(), matchesRange)) != availLocales.end()) { aRetVal.AppendElement(match->AsString()); match = eraseFromAvail(match); foundMatch = true; if (aStrategy != LangNegStrategy::Filtering) { return true; // we only want the first match } } return foundMatch; }; - Locale requestedLocale = Locale(requested, false); - if (findRangeMatches(requestedLocale)) { + Locale requestedLocale = Locale(requested); + if (findRangeMatches(requestedLocale, true, false)) { HANDLE_STRATEGY; } // 3) Try to match against a maximized version of the requested locale if (requestedLocale.AddLikelySubtags()) { - if (findRangeMatches(requestedLocale)) { + if (findRangeMatches(requestedLocale, true, false)) { HANDLE_STRATEGY; } } // 4) Try to match against a variant as a range - requestedLocale.SetVariantRange(); - if (findRangeMatches(requestedLocale)) { + requestedLocale.ClearVariants(); + if (findRangeMatches(requestedLocale, true, true)) { HANDLE_STRATEGY; } // 5) Try to match against the likely subtag without region - if (requestedLocale.AddLikelySubtagsWithoutRegion()) { - if (findRangeMatches(requestedLocale)) { + requestedLocale.ClearRegion(); + if (requestedLocale.AddLikelySubtags()) { + if (findRangeMatches(requestedLocale, true, false)) { HANDLE_STRATEGY; } } - // 6) Try to match against a region as a range - requestedLocale.SetRegionRange(); - if (findRangeMatches(requestedLocale)) { + requestedLocale.ClearRegion(); + if (findRangeMatches(requestedLocale, true, true)) { HANDLE_STRATEGY; } } } bool LocaleService::NegotiateLanguages(const nsTArray<nsCString>& aRequested, const nsTArray<nsCString>& aAvailable, @@ -587,20 +590,22 @@ LocaleService::Observe(nsISupports *aSub RequestedLocalesChanged(); } } return NS_OK; } bool -LocaleService::LanguagesMatch(const nsCString& aRequested, - const nsCString& aAvailable) +LocaleService::LanguagesMatch(const nsACString& aRequested, + const nsACString& aAvailable) { - return Locale(aRequested, true).LanguageMatches(Locale(aAvailable, true)); + Locale requested = Locale(aRequested); + Locale available = Locale(aAvailable); + return requested.GetLanguage().Equals(available.GetLanguage()); } bool LocaleService::IsServer() { return mIsServer; }
--- a/intl/locale/LocaleService.h +++ b/intl/locale/LocaleService.h @@ -227,33 +227,34 @@ public: * Available - locales for which the data is available * Supported - locales negotiated by the algorithm * * Additionally, if defaultLocale is provided, it adds it to the end of the * result list as a "last resort" locale. * * Strategy is one of the three strategies described at the top of this file. * - * The result list is ordered according to the order of the requested locales. + * The result list is canonicalized and ordered according to the order + * of the requested locales. * * (See mozILocaleService.idl for a JS-callable version of this.) */ bool NegotiateLanguages(const nsTArray<nsCString>& aRequested, const nsTArray<nsCString>& aAvailable, const nsACString& aDefaultLocale, LangNegStrategy aLangNegStrategy, nsTArray<nsCString>& aRetVal); /** * Returns whether the current app locale is RTL. */ bool IsAppLocaleRTL(); - static bool LanguagesMatch(const nsCString& aRequested, - const nsCString& aAvailable); + static bool LanguagesMatch(const nsACString& aRequested, + const nsACString& aAvailable); bool IsServer(); private: void FilterMatches(const nsTArray<nsCString>& aRequested, const nsTArray<nsCString>& aAvailable, LangNegStrategy aStrategy, nsTArray<nsCString>& aRetVal);
--- a/intl/locale/MozLocale.cpp +++ b/intl/locale/MozLocale.cpp @@ -1,157 +1,212 @@ /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* 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 "mozilla/intl/MozLocale.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" + #include "unicode/uloc.h" using namespace mozilla::intl; /** * Note: The file name is `MozLocale` to avoid compilation problems on case-insensitive * Windows. The class name is `Locale`. */ -Locale::Locale(const nsCString& aLocale, bool aRange) - : mLocaleStr(aLocale) +Locale::Locale(const nsACString& aLocale) { - int32_t partNum = 0; + int32_t position = 0; + + if (!IsASCII(aLocale)) { + mIsValid = false; + return; + } nsAutoCString normLocale(aLocale); normLocale.ReplaceChar('_', '-'); - for (const nsACString& part : normLocale.Split('-')) { - switch (partNum) { - case 0: - if (part.EqualsLiteral("*") || - part.Length() == 2 || part.Length() == 3) { - mLanguage.Assign(part); - } - break; - case 1: - if (part.EqualsLiteral("*") || part.Length() == 4) { - mScript.Assign(part); - break; - } - - // fallover to region case - partNum++; - MOZ_FALLTHROUGH; - case 2: - if (part.EqualsLiteral("*") || part.Length() == 2) { - mRegion.Assign(part); - } - break; - case 3: - if (part.EqualsLiteral("*") || (part.Length() >= 3 && part.Length() <= 8)) { - mVariant.Assign(part); - } - break; - } - partNum++; - } - - if (aRange) { - if (mLanguage.IsEmpty()) { - mLanguage.AssignLiteral("*"); - } - if (mScript.IsEmpty()) { - mScript.AssignLiteral("*"); - } - if (mRegion.IsEmpty()) { - mRegion.AssignLiteral("*"); - } - if (mVariant.IsEmpty()) { - mVariant.AssignLiteral("*"); + /** + * BCP47 language tag: + * + * langtag = language 2*3ALPHA + * ["-" extlang] 3ALPHA *2("-" 3ALPHA) + * ["-" script] 4ALPHA + * ["-" region] 2ALPHA / 3DIGIT + * *("-" variant) 5*8alphanum / (DIGIT 3alphanum) + * *("-" extension) [0-9a-wy-z] 1*("-" (1*8alphanum)) + * ["-" privateuse] x 1*("-" (1*8alphanum)) + * + * This class currently supports a subset of the full BCP47 language tag + * with a single extension of allowing variants to be 3ALPHA to support + * `ja-JP-mac` code: + * + * langtag = language 2*3ALPHA + * ["-" script] 4ALPHA + * ["-" region] 2ALPHA + * *("-" variant) 3*8alphanum + * + * The `position` variable represents the currently expected section of the tag + * and intentionally skips positions (like `extlang`) which may be added later. + */ + for (const nsACString& subTag : normLocale.Split('-')) { + auto slen = subTag.Length(); + if (position == 0) { + if (slen < 2 || slen > 3) { + mIsValid = false; + return; + } + mLanguage = subTag; + ToLowerCase(mLanguage); + position = 2; + } else if (position <= 2 && slen == 4) { + mScript = subTag; + ToLowerCase(mScript); + mScript.Replace(0, 1, ToUpperCase(mScript[0])); + position = 3; + } else if (position <= 3 && slen == 2) { + mRegion = subTag; + ToUpperCase(mRegion); + position = 4; + } else if (position <= 4 && slen >= 3 && slen <= 8) { + // we're quirky here because we allow for variant to be 3 char long. + // BCP47 requires variants to be 5-8 char long at lest. + // + // We do this to support the `ja-JP-mac` quirk that we have. + nsAutoCString lcSubTag(subTag); + ToLowerCase(lcSubTag); + mVariants.InsertElementSorted(lcSubTag); + position = 4; } } } -static bool -SubtagMatches(const nsCString& aSubtag1, const nsCString& aSubtag2) +bool +Locale::IsValid() +{ + return mIsValid; +} + +const nsCString +Locale::AsString() { - return aSubtag1.EqualsLiteral("*") || - aSubtag2.EqualsLiteral("*") || - aSubtag1.Equals(aSubtag2, nsCaseInsensitiveCStringComparator()); + nsCString tag; + + if (!mIsValid) { + tag.AppendLiteral("und"); + return tag; + } + + tag.Append(mLanguage); + + if (!mScript.IsEmpty()) { + tag.AppendLiteral("-"); + tag.Append(mScript); + } + + if (!mRegion.IsEmpty()) { + tag.AppendLiteral("-"); + tag.Append(mRegion); + } + + for (const auto& variant : mVariants) { + tag.AppendLiteral("-"); + tag.Append(variant); + } + return tag; +} + +const nsACString& +Locale::GetLanguage() const +{ + return mLanguage; +} + +const nsACString& +Locale::GetScript() const +{ + return mScript; +} + +const nsACString& +Locale::GetRegion() const +{ + return mRegion; +} + +const nsTArray<nsCString>& +Locale::GetVariants() const +{ + return mVariants; } bool -Locale::Matches(const Locale& aLocale) const -{ - return SubtagMatches(mLanguage, aLocale.mLanguage) && - SubtagMatches(mScript, aLocale.mScript) && - SubtagMatches(mRegion, aLocale.mRegion) && - SubtagMatches(mVariant, aLocale.mVariant); -} - -bool -Locale::LanguageMatches(const Locale& aLocale) const +Locale::Matches(const Locale& aOther, bool aThisRange, bool aOtherRange) const { - return SubtagMatches(mLanguage, aLocale.mLanguage) && - SubtagMatches(mScript, aLocale.mScript); -} + if ((!aThisRange || !mLanguage.IsEmpty()) && + (!aOtherRange || !aOther.mLanguage.IsEmpty()) && + !mLanguage.Equals(aOther.mLanguage)) { + return false; + } -void -Locale::SetVariantRange() -{ - mVariant.AssignLiteral("*"); -} - -void -Locale::SetRegionRange() -{ - mRegion.AssignLiteral("*"); + if ((!aThisRange || !mScript.IsEmpty()) && + (!aOtherRange || !aOther.mScript.IsEmpty()) && + !mScript.Equals(aOther.mScript)) { + return false; + } + if ((!aThisRange || !mRegion.IsEmpty()) && + (!aOtherRange || !aOther.mRegion.IsEmpty()) && + !mRegion.Equals(aOther.mRegion)) { + return false; + } + if ((!aThisRange || !mVariants.IsEmpty()) && + (!aOtherRange || !aOther.mVariants.IsEmpty()) && + mVariants != aOther.mVariants) { + return false; + } + return true; } bool Locale::AddLikelySubtags() { - return AddLikelySubtagsForLocale(mLocaleStr); -} - -bool -Locale::AddLikelySubtagsWithoutRegion() -{ - nsAutoCString locale(mLanguage); - - if (!mScript.IsEmpty()) { - locale.Append("-"); - locale.Append(mScript); - } - - // We don't add variant here because likelySubtag doesn't care about it. - - return AddLikelySubtagsForLocale(locale); -} - -bool -Locale::AddLikelySubtagsForLocale(const nsACString& aLocale) -{ const int32_t kLocaleMax = 160; char maxLocale[kLocaleMax]; - nsAutoCString locale(aLocale); UErrorCode status = U_ZERO_ERROR; - uloc_addLikelySubtags(locale.get(), maxLocale, kLocaleMax, &status); + uloc_addLikelySubtags(AsString().get(), maxLocale, kLocaleMax, &status); if (U_FAILURE(status)) { return false; } nsDependentCString maxLocStr(maxLocale); - Locale loc = Locale(maxLocStr, false); + Locale loc = Locale(maxLocStr); if (loc == *this) { return false; } mLanguage = loc.mLanguage; mScript = loc.mScript; mRegion = loc.mRegion; // We don't update variant from likelySubtag since it's not going to // provide it and we want to preserve the range return true; } + +void +Locale::ClearVariants() +{ + mVariants.Clear(); +} + +void +Locale::ClearRegion() +{ + mRegion.Truncate(); +}
--- a/intl/locale/MozLocale.h +++ b/intl/locale/MozLocale.h @@ -1,64 +1,93 @@ /* -*- 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/. */ -#ifndef mozilla_intl_Locale_h__ -#define mozilla_intl_Locale_h__ +#ifndef mozilla_intl_MozLocale_h__ +#define mozilla_intl_MozLocale_h__ #include "nsString.h" +#include "nsTArray.h" namespace mozilla { namespace intl { /** - * Locale object, a BCP47-style tag decomposed into subtags for - * matching purposes. + * Locale class is a core representation of a single locale. + * + * A locale is a data object representing a combination of language, script, + * region, variant and a set of regional extension preferences that may further specify + * particular user choices like calendar, numbering system, etc. + * + * A locale can be expressed as a language tag string, like a simple "fr" for French, + * or a more specific "sr-Cyrl-RS-u-hc-h12" for Serbian in Russia with a Cyrylic script, + * and hour cycle selected to be `h12`. + * + * The format of the language tag follows BCP47 standard and implements a subset of it. + * In the future we expect to extend this class to cover more subtags and extensions. + * + * BCP47: https://tools.ietf.org/html/bcp47 * - * If constructed with aRange = true, any missing subtags will be - * set to "*". + * The aim of this class it aid in validation, parsing and canonicalization of the + * string. + * + * It allows the user to input any well-formed BCP47 language tag and operate + * on its subtags in a canonicalized form. + * + * It should be used for all operations on language tags, and together with + * LocaleService::NegotiateLanguages for language negotiation. + * + * Example: + * + * Locale loc = Locale("de-at"); + * + * ASSERT_TRUE(loc.GetLanguage().Equals("de")); + * ASSERT_TRUE(loc.GetScript().IsEmpty()); + * ASSERT_TRUE(loc.GetRegion().Equals("AT")); // canonicalized to upper case + * * * Note: The file name is `MozLocale` to avoid compilation problems on case-insensitive * Windows. The class name is `Locale`. */ class Locale { public: - Locale(const nsCString& aLocale, bool aRange); - - bool Matches(const Locale& aLocale) const; - bool LanguageMatches(const Locale& aLocale) const; - + explicit Locale(const nsACString& aLocale); + explicit Locale(const char* aLocale) + : Locale(nsDependentCString(aLocale)) + { }; - void SetVariantRange(); - void SetRegionRange(); + const nsACString& GetLanguage() const; + const nsACString& GetScript() const; + const nsACString& GetRegion() const; + const nsTArray<nsCString>& GetVariants() const; - // returns false if nothing changed + bool IsValid(); + const nsCString AsString(); + + bool Matches(const Locale& aOther, bool aThisRange, bool aOtherRange) const; bool AddLikelySubtags(); - bool AddLikelySubtagsWithoutRegion(); - - const nsCString& AsString() const { - return mLocaleStr; - } + void ClearVariants(); + void ClearRegion(); bool operator== (const Locale& aOther) { - const auto& cmp = nsCaseInsensitiveCStringComparator(); - return mLanguage.Equals(aOther.mLanguage, cmp) && - mScript.Equals(aOther.mScript, cmp) && - mRegion.Equals(aOther.mRegion, cmp) && - mVariant.Equals(aOther.mVariant, cmp); + return mLanguage.Equals(aOther.mLanguage) && + mScript.Equals(aOther.mScript) && + mRegion.Equals(aOther.mRegion) && + mVariants == aOther.mVariants; + } private: - const nsCString& mLocaleStr; - nsCString mLanguage; - nsCString mScript; - nsCString mRegion; - nsCString mVariant; - - bool AddLikelySubtagsForLocale(const nsACString& aLocale); + nsAutoCStringN<3> mLanguage; + nsAutoCStringN<4> mScript; + nsAutoCStringN<2> mRegion; + nsTArray<nsCString> mVariants; + bool mIsValid = true; }; } // intl } // namespace mozilla -#endif /* mozilla_intl_Locale_h__ */ +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::intl::Locale) + +#endif /* mozilla_intl_MozLocale_h__ */
--- a/intl/locale/mozILocaleService.idl +++ b/intl/locale/mozILocaleService.idl @@ -114,17 +114,18 @@ interface mozILocaleService : nsISupport * Available - locales for which the data is available * Supported - locales negotiated by the algorithm * * Additionally, if defaultLocale is provided, it adds it to the end of the * result list as a "last resort" locale. * * Strategy is one of the three strategies described at the top of this file. * - * The result list is ordered according to the order of the requested locales. + * The result list is canonicalized and ordered according to the order + * of the requested locales. * * (See LocaleService.h for a more C++-friendly version of this.) */ void negotiateLanguages([array, size_is(aRequestedCount)] in string aRequested, [array, size_is(aAvailableCount)] in string aAvailable, [optional] in string aDefaultLocale, [optional] in long langNegStrategy, [optional] in unsigned long aRequestedCount,
new file mode 100644 --- /dev/null +++ b/intl/locale/tests/gtest/TestMozLocale.cpp @@ -0,0 +1,64 @@ +/* -*- 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/MozLocale.h" + +using namespace mozilla::intl; + + +TEST(Intl_Locale_Locale, Locale) { + Locale loc = Locale("en-US"); + + ASSERT_TRUE(loc.GetLanguage().Equals("en")); + ASSERT_TRUE(loc.GetRegion().Equals("US")); +} + +TEST(Intl_Locale_Locale, AsString) { + Locale loc = Locale("ja-jp-windows"); + + ASSERT_TRUE(loc.AsString().Equals("ja-JP-windows")); +} + +TEST(Intl_Locale_Locale, GetSubTags) { + Locale loc = Locale("en-latn-us-macos"); + + ASSERT_TRUE(loc.GetLanguage().Equals("en")); + ASSERT_TRUE(loc.GetScript().Equals("Latn")); + ASSERT_TRUE(loc.GetRegion().Equals("US")); + ASSERT_TRUE(loc.GetVariants().Length() == 1); + ASSERT_TRUE(loc.GetVariants()[0].Equals("macos")); +} + +TEST(Intl_Locale_Locale, Matches) { + Locale loc = Locale("en-US"); + + Locale loc2 = Locale("en-GB"); + ASSERT_FALSE(loc == loc2); + + Locale loc3 = Locale("en-US"); + ASSERT_TRUE(loc == loc3); + + Locale loc4 = Locale("En_us"); + ASSERT_TRUE(loc == loc4); +} + +TEST(Intl_Locale_Locale, MatchesRange) { + Locale loc = Locale("en-US"); + + Locale loc2 = Locale("en-Latn-US"); + ASSERT_FALSE(loc == loc2); + ASSERT_TRUE(loc.Matches(loc2, true, false)); + ASSERT_FALSE(loc.Matches(loc2, false, true)); + ASSERT_FALSE(loc.Matches(loc2, false, false)); + ASSERT_TRUE(loc.Matches(loc2, true, true)); + + Locale loc3 = Locale("en"); + ASSERT_FALSE(loc == loc3); + ASSERT_TRUE(loc.Matches(loc3, false, true)); + ASSERT_FALSE(loc.Matches(loc3, true, false)); + ASSERT_FALSE(loc.Matches(loc3, false, false)); + ASSERT_TRUE(loc.Matches(loc3, true, true)); +}
--- a/intl/locale/tests/gtest/moz.build +++ b/intl/locale/tests/gtest/moz.build @@ -4,12 +4,13 @@ # 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 += [ 'TestCollation.cpp', 'TestDateTimeFormat.cpp', 'TestLocaleService.cpp', 'TestLocaleServiceNegotiate.cpp', + 'TestMozLocale.cpp', 'TestOSPreferences.cpp', ] FINAL_LIBRARY = 'xul-gtest'
--- a/intl/locale/tests/unit/test_localeService_negotiateLanguages.js +++ b/intl/locale/tests/unit/test_localeService_negotiateLanguages.js @@ -7,90 +7,84 @@ const localeService = .getService(Components.interfaces.mozILocaleService); 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-mac"], ["en-Latn-US-mac"], ["en-Latn-US-mac"]], + [["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-mac"], ["en-US"], ["en-US"]], + [["en-US-windows"], ["en-US"], ["en-US"]], [["fr-CA", "de-DE"], ["fr", "it", "de"], ["fr", "de"]], - [["ja-JP-mac"], ["ja"], ["ja"]], + [["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 on a requested locale as a range": [ - [["en-*-US"], ["en-US"], ["en-US"]], - [["en-Latn-US-*"], ["en-Latn-US"], ["en-Latn-US"]], - [["en-*-US-*"], ["en-US"], ["en-US"]], - ], "should match cross-region": [ [["en"], ["en-US"], ["en-US"]], [["en-US"], ["en-GB"], ["en-GB"]], [["en-Latn-US"], ["en-Latn-GB"], ["en-Latn-GB"]], - // This is a cross-region check, because the requested Locale - // is really lang: en, script: *, region: undefined - [["en-*"], ["en-US"], ["en-US"]], ], "should match cross-variant": [ - [["en-US-mac"], ["en-US-win"], ["en-US-win"]], + [["en-US-linux"], ["en-US-windows"], ["en-US-windows"]], ], "should prioritize properly": [ // exact match first - [["en-US"], ["en-US-mac", "en", "en-US"], ["en-US", "en", "en-US-mac"]], + [["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-mac"], ["en-US-win", "en-GB-mac"], ["en-US-win", "en-GB-mac"]], + [["en-US-macos"], ["en-US-windows", "en-GB-macos"], ["en-US-windows", "en-GB-macos"]], // regional range fifth - [["en-US-mac"], ["en-GB-win"], ["en-GB-win"]], - ], - "should prioritize properly (extra tests)": [ + [["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-mac", "de-DE"], ["de-DE", "fr-FR-win"], ["fr-FR-win", "de-DE"]], + [["fr-CA-macos", "de-DE"], ["de-DE", "fr-FR-windows"], ["fr-FR-windows", "de-DE"]], ], "should handle cases and underscores": [ [["fr_FR"], ["fr-FR"], ["fr-FR"]], - [["fr_fr"], ["fr-fr"], ["fr-fr"]], - [["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_mac"], ["fr_Cyrl_fr-mac"], ["fr_Cyrl_fr-mac"]], + [["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": [ [null, ["fr-FR"], []], [[null], [], []], [[undefined], [], []], [[undefined], [null], []], [[undefined], [undefined], []], [[null], [null], null, null, []],
--- a/toolkit/mozapps/extensions/test/addons/test_bug397778/install.rdf +++ b/toolkit/mozapps/extensions/test/addons/test_bug397778/install.rdf @@ -24,17 +24,17 @@ <em:localized> <Description em:locale="de-DE"> <em:name>de-DE Name</em:name> </Description> </em:localized> <em:localized> - <Description em:locale="ES-es"> + <Description em:locale="es-ES"> <em:name>es-ES Name</em:name> <em:description>es-ES Description</em:description> </Description> </em:localized> <em:localized> <Description em:locale="zh-TW"> <em:name>zh-TW Name</em:name>