gecko-dev/intl/locale/MozLocale.cpp

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();
}