mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-02 10:00:54 +00:00
211 lines
4.9 KiB
C++
211 lines
4.9 KiB
C++
/* -*- 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 nsACString& aLocale)
|
|
{
|
|
int32_t position = 0;
|
|
|
|
if (!IsASCII(aLocale)) {
|
|
mIsValid = false;
|
|
return;
|
|
}
|
|
|
|
nsAutoCString normLocale(aLocale);
|
|
normLocale.ReplaceChar('_', '-');
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
const nsCString
|
|
Locale::AsString() const
|
|
{
|
|
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& aOther, bool aThisRange, bool aOtherRange) const
|
|
{
|
|
if (!IsValid() || !aOther.IsValid()) {
|
|
return false;
|
|
}
|
|
|
|
if ((!aThisRange || !mLanguage.IsEmpty()) &&
|
|
(!aOtherRange || !aOther.mLanguage.IsEmpty()) &&
|
|
!mLanguage.Equals(aOther.mLanguage)) {
|
|
return false;
|
|
}
|
|
|
|
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()
|
|
{
|
|
const int32_t kLocaleMax = 160;
|
|
char maxLocale[kLocaleMax];
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
uloc_addLikelySubtags(AsString().get(), maxLocale, kLocaleMax, &status);
|
|
|
|
if (U_FAILURE(status)) {
|
|
return false;
|
|
}
|
|
|
|
nsDependentCString maxLocStr(maxLocale);
|
|
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();
|
|
}
|