-
-
Save gregtatum/d21c427268f10619b60d71c282b4fb9b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
changeset: 591812:1d345bc79fcb | |
parent: 591800:9c9e3a7a8337 | |
user: Greg Tatum <gtatum@mozilla.com> | |
date: Tue Aug 10 11:59:03 2021 -0500 | |
files: intl/components/gtest/TestBuffer.h intl/components/gtest/TestDisplayNames.cpp intl/components/gtest/moz.build intl/components/moz.build intl/components/src/DisplayNames.cpp intl/components/src/DisplayNames.h intl/components/src/ICU4CGlue.cpp intl/components/src/ICU4CGlue.h | |
description: | |
WIP Bug 1719735 - Unify DisplayNames smozilla::intl | |
diff --git a/intl/components/gtest/TestBuffer.h b/intl/components/gtest/TestBuffer.h | |
--- a/intl/components/gtest/TestBuffer.h | |
+++ b/intl/components/gtest/TestBuffer.h | |
@@ -42,16 +42,21 @@ class TestBuffer { | |
size_t length() const { return mBuffer.length(); } | |
/** | |
* Returns the buffer's overall capacity. | |
*/ | |
size_t capacity() const { return mBuffer.capacity(); } | |
/** | |
+ * Clear the buffer. | |
+ */ | |
+ void clear() { mBuffer.clear(); } | |
+ | |
+ /** | |
* Resizes the buffer to the given amount of written elements. | |
* This is necessary because the buffer gets written to across | |
* FFI boundaries, so this needs to happen in a separate step. | |
*/ | |
void written(size_t aAmount) { | |
MOZ_ASSERT(aAmount <= mBuffer.capacity()); | |
mozilla::DebugOnly<bool> result = mBuffer.resizeUninitialized(aAmount); | |
MOZ_ASSERT(result); | |
diff --git a/intl/components/gtest/TestDisplayNames.cpp b/intl/components/gtest/TestDisplayNames.cpp | |
new file mode 100644 | |
--- /dev/null | |
+++ b/intl/components/gtest/TestDisplayNames.cpp | |
@@ -0,0 +1,153 @@ | |
+/* 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 "TestBuffer.h" | |
+#include "mozilla/intl/DisplayNames.h" | |
+ | |
+namespace mozilla::intl { | |
+ | |
+TEST(IntlDisplayNames, Script) | |
+{ | |
+ TestBuffer<char16_t> buffer; | |
+ | |
+ DisplayNames::Options options(DisplayNames::Type::Script); | |
+ options.style = DisplayNames::Style::Long; | |
+ | |
+ { | |
+ auto result = DisplayNames::TryCreate("en-US", options); | |
+ ASSERT_TRUE(result.isOk()); | |
+ auto displayNames = result.unwrap(); | |
+ ASSERT_TRUE(displayNames->Of(buffer, "Hans").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u"Simplified Han"); | |
+ | |
+ buffer.clear(); | |
+ ASSERT_TRUE(displayNames->Of(buffer, "ThisIsTooLong").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u""); | |
+ | |
+ buffer.clear(); | |
+ ASSERT_TRUE(displayNames->Of(buffer, "✋🏽 non-ascii input").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u""); | |
+ } | |
+ | |
+ { | |
+ auto result = DisplayNames::TryCreate("es-ES", options); | |
+ ASSERT_TRUE(result.isOk()); | |
+ auto displayNames = result.unwrap(); | |
+ | |
+ buffer.clear(); | |
+ ASSERT_TRUE(displayNames->Of(buffer, "Hans").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u"han simplificado"); | |
+ } | |
+ | |
+ options.style = DisplayNames::Style::Short; | |
+ { | |
+ auto result = DisplayNames::TryCreate("en-US", options); | |
+ ASSERT_TRUE(result.isOk()); | |
+ auto displayNames = result.unwrap(); | |
+ | |
+ buffer.clear(); | |
+ ASSERT_TRUE(displayNames->Of(buffer, "Hans").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u"Simplified"); | |
+ } | |
+ | |
+ { | |
+ auto result = DisplayNames::TryCreate("es-ES", options); | |
+ ASSERT_TRUE(result.isOk()); | |
+ auto displayNames = result.unwrap(); | |
+ | |
+ buffer.clear(); | |
+ ASSERT_TRUE(displayNames->Of(buffer, "Hans").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u"simplificado"); | |
+ } | |
+} | |
+ | |
+TEST(IntlDisplayNames, Language) | |
+{ | |
+ TestBuffer<char16_t> buffer; | |
+ DisplayNames::Options options(DisplayNames::Type::Language); | |
+ | |
+ { | |
+ auto result = DisplayNames::TryCreate("en-US", options); | |
+ ASSERT_TRUE(result.isOk()); | |
+ auto displayNames = result.unwrap(); | |
+ | |
+ ASSERT_TRUE(displayNames->Of(buffer, "es-ES").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u"Spanish (Spain)"); | |
+ | |
+ buffer.clear(); | |
+ ASSERT_TRUE(displayNames->Of(buffer, "zh-Hant").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u"Chinese (Traditional)"); | |
+ | |
+ // The undefined locale returns an empty string. | |
+ buffer.clear(); | |
+ ASSERT_TRUE(displayNames->Of(buffer, "und").isOk()); | |
+ ASSERT_TRUE(buffer.get_string_view().empty()); | |
+ | |
+ // Invalid locales return an empty string. | |
+ buffer.clear(); | |
+ ASSERT_TRUE(displayNames->Of(buffer, "asdf").isOk()); | |
+ ASSERT_TRUE(buffer.get_string_view().empty()); | |
+ } | |
+ { | |
+ auto result = DisplayNames::TryCreate("es-ES", options); | |
+ ASSERT_TRUE(result.isOk()); | |
+ auto displayNames = result.unwrap(); | |
+ | |
+ buffer.clear(); | |
+ ASSERT_TRUE(displayNames->Of(buffer, "es-ES").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u"español (España)"); | |
+ | |
+ buffer.clear(); | |
+ ASSERT_TRUE(displayNames->Of(buffer, "zh-Hant").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u"chino (tradicional)"); | |
+ } | |
+} | |
+ | |
+TEST(IntlDisplayNames, Region) | |
+{ | |
+ TestBuffer<char16_t> buffer; | |
+ | |
+ DisplayNames::Options options(DisplayNames::Type::Region); | |
+ options.style = DisplayNames::Style::Long; | |
+ | |
+ { | |
+ auto result = DisplayNames::TryCreate("en-US", options); | |
+ ASSERT_TRUE(result.isOk()); | |
+ auto displayNames = result.unwrap(); | |
+ | |
+ ASSERT_TRUE(displayNames->Of(buffer, "US").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u"United States"); | |
+ | |
+ ASSERT_TRUE(displayNames->Of(buffer, "ES").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u"Spain"); | |
+ } | |
+ { | |
+ auto result = DisplayNames::TryCreate("es-ES", options); | |
+ ASSERT_TRUE(result.isOk()); | |
+ auto displayNames = result.unwrap(); | |
+ | |
+ ASSERT_TRUE(displayNames->Of(buffer, "US").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u"Estados Unidos"); | |
+ | |
+ ASSERT_TRUE(displayNames->Of(buffer, "ES").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u"España"); | |
+ } | |
+} | |
+ | |
+TEST(IntlDisplayNames, Currency) | |
+{ | |
+ TestBuffer<char16_t> buffer; | |
+ | |
+ DisplayNames::Options options(DisplayNames::Type::Currency); | |
+ options.style = DisplayNames::Style::Long; | |
+ | |
+ auto result = DisplayNames::TryCreate("en-US", options); | |
+ ASSERT_TRUE(result.isOk()); | |
+ auto displayNames = result.unwrap(); | |
+ ASSERT_TRUE(displayNames->Of(buffer, u"EUR").isOk()); | |
+ ASSERT_EQ(buffer.get_string_view(), u"Euro"); | |
+} | |
+ | |
+} // namespace mozilla::intl | |
diff --git a/intl/components/gtest/moz.build b/intl/components/gtest/moz.build | |
--- a/intl/components/gtest/moz.build | |
+++ b/intl/components/gtest/moz.build | |
@@ -3,13 +3,14 @@ | |
# 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 += [ | |
"TestCalendar.cpp", | |
"TestCollator.cpp", | |
"TestDateTimeFormat.cpp", | |
+ "TestDisplayNames.cpp", | |
"TestNumberFormat.cpp", | |
"TestPluralRules.cpp", | |
] | |
FINAL_LIBRARY = "xul-gtest" | |
diff --git a/intl/components/moz.build b/intl/components/moz.build | |
--- a/intl/components/moz.build | |
+++ b/intl/components/moz.build | |
@@ -3,26 +3,28 @@ | |
# 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/. | |
EXPORTS.mozilla.intl = [ | |
"src/Calendar.h", | |
"src/Collator.h", | |
"src/DateTimeFormat.h", | |
"src/DateTimePatternGenerator.h", | |
+ "src/DisplayNames.h", | |
"src/ICU4CGlue.h", | |
"src/NumberFormat.h", | |
"src/PluralRules.h", | |
] | |
UNIFIED_SOURCES += [ | |
"src/Calendar.cpp", | |
"src/Collator.cpp", | |
"src/DateTimeFormat.cpp", | |
"src/DateTimePatternGenerator.cpp", | |
+ "src/DisplayNames.cpp", | |
"src/ICU4CGlue.cpp", | |
"src/NumberFormat.cpp", | |
"src/NumberFormatFields.cpp", | |
"src/NumberFormatterSkeleton.cpp", | |
"src/PluralRules.cpp", | |
] | |
if not CONFIG["JS_STANDALONE"]: | |
diff --git a/intl/components/src/DisplayNames.cpp b/intl/components/src/DisplayNames.cpp | |
new file mode 100644 | |
--- /dev/null | |
+++ b/intl/components/src/DisplayNames.cpp | |
@@ -0,0 +1,78 @@ | |
+/* 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/DisplayNames.h" | |
+ | |
+namespace mozilla::intl { | |
+ | |
+DisplayNames::~DisplayNames() { | |
+ // The mDisplayNames will not exist when the DisplayNames is being | |
+ // moved. | |
+ if (mULocaleDisplayNames.GetMut()) { | |
+ uldn_close(mULocaleDisplayNames.GetMut()); | |
+ } | |
+} | |
+ | |
+/* static */ | |
+Result<UniquePtr<DisplayNames>, ICUError> DisplayNames::TryCreate( | |
+ const char* aLocale, Options aOptions) { | |
+ UErrorCode status = U_ZERO_ERROR; | |
+ UDisplayContext contexts[] = { | |
+ // Use either standard or dialect names. | |
+ // For example either "English (GB)" or "British English". | |
+ aOptions.languageDisplay == DisplayNames::LanguageDisplay::Standard | |
+ ? UDISPCTX_STANDARD_NAMES | |
+ : UDISPCTX_DIALECT_NAMES, | |
+ | |
+ // Assume the display names are used in a stand-alone context. | |
+ UDISPCTX_CAPITALIZATION_FOR_STANDALONE, | |
+ | |
+ // Select either the long or short form. There's no separate narrow form | |
+ // available in ICU, therefore we equate "narrow"/"short" styles here. | |
+ aOptions.style == DisplayNames::Style::Long ? UDISPCTX_LENGTH_FULL | |
+ : UDISPCTX_LENGTH_SHORT, | |
+ | |
+ // Don't apply substitutes, because we need to apply our own fallbacks. | |
+ UDISPCTX_NO_SUBSTITUTE, | |
+ }; | |
+ | |
+ ULocaleDisplayNames* uLocaleDisplayNames = | |
+ uldn_openForContext(aLocale, contexts, std::size(contexts), &status); | |
+ | |
+ if (U_FAILURE(status)) { | |
+ return Err(ToICUError(status)); | |
+ } | |
+ return MakeUnique<DisplayNames>(uLocaleDisplayNames, MakeStringSpan(aLocale), | |
+ aOptions); | |
+}; | |
+ | |
+/* static */ | |
+bool DisplayNames::ConvertScriptToLocale(ScriptLocaleVector& aLocale, | |
+ const char* aScript) { | |
+ const char* localeText = "und-"; | |
+ for (size_t i = 0; i < 4; i++) { | |
+ mozilla::DebugOnly<bool> result = aLocale.append(localeText[i]); | |
+ // The allocation would only fail due to a logic error in the | |
+ // implementation, as the buffer is already stack-allocated. | |
+ MOZ_ASSERT(result); | |
+ } | |
+ | |
+ // Script tags are 4 ascii characters followed by a null terminator. | |
+ // https://unicode-org.github.io/cldr-staging/charts/37/supplemental/languages_and_scripts.html | |
+ size_t scriptLength = strlen(aScript); | |
+ if (scriptLength != 4) { | |
+ // The script was not the correct size, return early without writing to | |
+ // the buffer. | |
+ return false; | |
+ } | |
+ | |
+ for (size_t i = 0; i <= scriptLength; i++) { | |
+ mozilla::DebugOnly<bool> result = aLocale.append(aScript[i]); | |
+ // The allocation would only fail due to a logic error in the | |
+ // implementation, as the buffer is already stack-allocated. | |
+ MOZ_ASSERT(result); | |
+ } | |
+ return true; | |
+} | |
+ | |
+} // namespace mozilla::intl | |
diff --git a/intl/components/src/DisplayNames.h b/intl/components/src/DisplayNames.h | |
new file mode 100644 | |
--- /dev/null | |
+++ b/intl/components/src/DisplayNames.h | |
@@ -0,0 +1,330 @@ | |
+/* 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 intl_components_DisplayNames_h_ | |
+#define intl_components_DisplayNames_h_ | |
+ | |
+#include "unicode/uldnames.h" | |
+#include "unicode/uloc.h" | |
+#include "unicode/ucurr.h" | |
+#include "mozilla/Casting.h" | |
+#include "mozilla/Result.h" | |
+#include "mozilla/ResultVariant.h" | |
+#include "mozilla/Span.h" | |
+#include "mozilla/UniquePtr.h" | |
+#include "mozilla/intl/ICU4CGlue.h" | |
+ | |
+namespace mozilla::intl { | |
+ | |
+class DisplayNames final { | |
+ public: | |
+ enum class LocaleMatcher { | |
+ Lookup, | |
+ BestFit, | |
+ }; | |
+ | |
+ enum class Style { | |
+ Narrow, | |
+ Short, | |
+ Long, | |
+ }; | |
+ | |
+ enum class Type { | |
+ // Return the localized name of a language. | |
+ // | |
+ // Accepts: | |
+ // languageCode ["-" scriptCode] ["-" regionCode ] *("-" variant ) | |
+ // Where the language code is: | |
+ // 1. A two letters ISO 639-1 language code | |
+ // https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes | |
+ // 2. A three letters ISO 639-2 language code | |
+ // https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes | |
+ // | |
+ // Examples: | |
+ // "es-ES" => "European Spanish" (en-US), "español de España" (es-ES) | |
+ // "zh-Hant" => "Traditional Chinese" (en-US), "chino tradicional" (es-ES) | |
+ // | |
+ Language, | |
+ // Return the localized name of a region, | |
+ // | |
+ // Accepts: | |
+ // 1. an ISO-3166 two letters: | |
+ // https://www.iso.org/iso-3166-country-codes.html | |
+ // 2. region code, or a three digits UN M49 Geographic Regions. | |
+ // https://unstats.un.org/unsd/methodology/m49/ | |
+ // | |
+ // Examples | |
+ // "US" => "United States" (en-US), "Estados Unidos", (es-ES) | |
+ // "158" => "Taiwan" (en-US), "Taiwán", (es-ES) | |
+ Region, | |
+ // Returns the localized name of a script. | |
+ // | |
+ // Accepts: | |
+ // ECMA-402 expects the ISO-15924 four letters script code. | |
+ // https://unicode.org/iso15924/iso15924-codes.html | |
+ // e.g. "Latn" | |
+ // | |
+ // Examples: | |
+ // "Cher" => "Cherokee" (en-US), "cherokee" (es-ES) | |
+ // "Latn" => "Latin" (en-US), "latino" (es-ES) | |
+ Script, | |
+ // Return the localized name of a currency. | |
+ // | |
+ // Accepts: | |
+ // A 3-letter ISO 4217 currency code. | |
+ // https://en.wikipedia.org/wiki/ISO_4217 | |
+ // | |
+ // Examples: | |
+ // "EUR" => "Euro" (en-US), "euro" (es_ES), "欧元", (zh) | |
+ // "JPY" => "Japanese Yen" (en-US), "yen" (es_ES), "日元", (zh) | |
+ Currency, | |
+ }; | |
+ | |
+ enum class LanguageDisplay { | |
+ Standard, | |
+ Dialect, | |
+ }; | |
+ | |
+ /** | |
+ * These options map to ECMA 402 DisplayNames options. Make sure the defaults | |
+ * map to the default initialized values of ECMA 402. | |
+ * | |
+ * Note that fallbacking is not currently implemented here in the unified API, | |
+ * but is still handled in SpiderMonkey. This is due to support for the | |
+ * LanguageTag. See Bug 1719746. | |
+ * | |
+ * https://tc39.es/ecma402/#intl-displaynames-objects | |
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames | |
+ */ | |
+ struct Options { | |
+ // The constructor ensures the required Type option is created. | |
+ explicit Options(Type aType) : type(aType){}; | |
+ Options() = delete; | |
+ | |
+ // Required: | |
+ Type type; | |
+ | |
+ // Optional: | |
+ LocaleMatcher localeMatcher = LocaleMatcher::BestFit; | |
+ Style style = Style::Long; | |
+ LanguageDisplay languageDisplay = LanguageDisplay::Standard; | |
+ }; | |
+ | |
+ DisplayNames(ULocaleDisplayNames* aDisplayNames, Span<const char> aLocale, | |
+ Options aOptions) | |
+ : mOptions(aOptions), | |
+ mLocale(aLocale.data(), aLocale.size()), | |
+ mULocaleDisplayNames(aDisplayNames) { | |
+ MOZ_ASSERT(aDisplayNames); | |
+ }; | |
+ | |
+ static Result<UniquePtr<DisplayNames>, ICUError> TryCreate( | |
+ const char* aLocale, Options aOptions); | |
+ | |
+ // Not copyable or movable | |
+ DisplayNames(const DisplayNames&) = delete; | |
+ DisplayNames& operator=(const DisplayNames&) = delete; | |
+ | |
+ ~DisplayNames(); | |
+ | |
+ /** | |
+ * Get the results for a display name. | |
+ * | |
+ * https://tc39.es/ecma402/#sec-Intl.DisplayNames.prototype.of | |
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames/of | |
+ * | |
+ * Note that this function can take either a `char` or `char16_t` CharType. | |
+ * This was done to ensure there is a single public DisplayName::Of method | |
+ * exposed for computing everything. However, the underlying ICU4C calls may | |
+ * require one type or another. Rather than introduce extraneous string | |
+ * copying, this method enforces the correct CharType through a template. | |
+ * | |
+ * In the future, if we switch from ICU4C to ICU4X, this will most likely | |
+ * always require a `char` type. | |
+ */ | |
+ template <typename B, typename CharType> | |
+ ICUResult Of(B& aBuffer, const CharType* aCode) const { | |
+ if constexpr (std::is_same<CharType, char>::value) { | |
+ switch (mOptions.type) { | |
+ case Type::Language: | |
+ return this->GetLanguage(aBuffer, aCode); | |
+ case Type::Region: | |
+ return this->GetRegion(aBuffer, aCode); | |
+ case Type::Script: | |
+ return this->GetScript(aBuffer, aCode); | |
+ case Type::Currency: | |
+ MOZ_ASSERT_UNREACHABLE("Type requires a char CharType."); | |
+ } | |
+ } | |
+ if constexpr (std::is_same<CharType, char16_t>::value) { | |
+ switch (mOptions.type) { | |
+ case Type::Currency: | |
+ return this->GetCurrency(aBuffer, aCode); | |
+ case Type::Language: | |
+ case Type::Region: | |
+ case Type::Script: | |
+ MOZ_ASSERT_UNREACHABLE("Type requires a char16_t CharType."); | |
+ } | |
+ } | |
+ MOZ_ASSERT_UNREACHABLE(); | |
+ } | |
+ | |
+ private: | |
+ /** | |
+ * This is a specialized form of the FillBufferWithICUCall for DisplayNames. | |
+ * | |
+ * The display name APIs such as `uldn_scriptDisplayName`, | |
+ * `uloc_getDisplayScript`, and `uldn_regionDisplayName` report | |
+ * U_ILLEGAL_ARGUMENT_ERROR when no display name was found. In order to | |
+ * accomodate fallbacking, return an empty string in this case. | |
+ */ | |
+ template <typename B, typename F> | |
+ static ICUResult FillBufferWithICUDisplayNames(B& aBuffer, F aCallback) { | |
+ return FillBufferWithICUCall( | |
+ aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) { | |
+ int32_t res = aCallback(target, length, status); | |
+ | |
+ if (*status == U_ILLEGAL_ARGUMENT_ERROR) { | |
+ *status = U_ZERO_ERROR; | |
+ } | |
+ return res; | |
+ }); | |
+ } | |
+ | |
+ /** | |
+ * Get the display names for Apply the DisplayNames::Type::Language. | |
+ */ | |
+ template <typename B> | |
+ ICUResult GetLanguage(B& aBuffer, const char* aLanguage) const { | |
+ static_assert(std::is_same<typename B::CharType, char16_t>::value); | |
+ return FillBufferWithICUDisplayNames( | |
+ aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) { | |
+ return uldn_localeDisplayName(mULocaleDisplayNames.GetConst(), | |
+ aLanguage, target, length, status); | |
+ }); | |
+ }; | |
+ | |
+ /** | |
+ * Get the display names for Apply the DisplayNames::Type::Region. | |
+ */ | |
+ template <typename B> | |
+ ICUResult GetRegion(B& aBuffer, const char* aRegion) const { | |
+ static_assert(std::is_same<typename B::CharType, char16_t>::value); | |
+ | |
+ return FillBufferWithICUDisplayNames( | |
+ aBuffer, [&](UChar* chars, uint32_t size, UErrorCode* status) { | |
+ return uldn_regionDisplayName( | |
+ mULocaleDisplayNames.GetConst(), aRegion, chars, | |
+ AssertedCast<int32_t, uint32_t>(size), status); | |
+ }); | |
+ } | |
+ | |
+ /** | |
+ * Get the display names for Apply the DisplayNames::Type::Currency. | |
+ * | |
+ * Note that this function requires a `const char16_t` for the currency. This | |
+ * is done to match the underlying ICU4C call. This may change if we move to | |
+ * ICU4X. | |
+ */ | |
+ template <typename B> | |
+ ICUResult GetCurrency(B& aBuffer, const char16_t* aCurrency) const { | |
+ static_assert(std::is_same<typename B::CharType, char16_t>::value); | |
+ UCurrNameStyle style; | |
+ switch (mOptions.style) { | |
+ case Style::Long: | |
+ style = UCURR_LONG_NAME; | |
+ break; | |
+ case Style::Short: | |
+ style = UCURR_SYMBOL_NAME; | |
+ break; | |
+ case Style::Narrow: | |
+ style = UCURR_NARROW_SYMBOL_NAME; | |
+ break; | |
+ } | |
+ | |
+ int32_t length = 0; | |
+ UErrorCode status = U_ZERO_ERROR; | |
+ const char16_t* name = ucurr_getName(aCurrency, mLocale.data(), style, | |
+ nullptr, &length, &status); | |
+ if (U_FAILURE(status)) { | |
+ return Err(ICUError::InternalError); | |
+ } | |
+ | |
+ if (status == U_USING_DEFAULT_WARNING) { | |
+ // A resource bundle lookup returned a result from the root locale. | |
+ // Do not write to the buffer. | |
+ if (aBuffer.length() != 0) { | |
+ // Ensure an empty string is in the buffer. | |
+ aBuffer.written(0); | |
+ } | |
+ return Ok(); | |
+ } | |
+ | |
+ // Write the name, which is a static string, out to the buffer. This has a | |
+ // small performance cost compared to just returning the reference to the | |
+ // static string, but ensures a consistent DisplayNames::Of API. | |
+ size_t amount = length; | |
+ aBuffer.reserve(amount); | |
+ for (size_t i = 0; i < amount; i++) { | |
+ aBuffer.data()[i] = name[i]; | |
+ } | |
+ aBuffer.written(amount); | |
+ | |
+ return Ok(); | |
+ } | |
+ | |
+ // Vector to hold a "und-Latn" style language code + script. | |
+ // Total length: 9 ("und-" 4) + ("Latn" 4) + ("\0" 1) | |
+ using ScriptLocaleVector = Vector<char, 9>; | |
+ | |
+ /** | |
+ * Get the display names for Apply the DisplayNames::Type::Script. | |
+ */ | |
+ template <typename B> | |
+ ICUResult GetScript(B& aBuffer, const char* aScript) const { | |
+ static_assert(std::is_same<typename B::CharType, char16_t>::value); | |
+ | |
+ if (mOptions.style == DisplayNames::Style::Long) { | |
+ // |uldn_scriptDisplayName| doesn't use the stand-alone form for script | |
+ // subtags, so we're using |uloc_getDisplayScript| instead. (This only | |
+ // applies to the long form.) | |
+ // | |
+ // ICU bug: https://unicode-org.atlassian.net/browse/ICU-9301 | |
+ // | |
+ // |uloc_getDisplayScript| expects a full locale identifier as its input. | |
+ // Manually append the script. This could be handled more gracefully with | |
+ // full language tag support. See Bug | |
+ // | |
+ // Total length: 9 ("und-" 4) + ("Latn" 4) + ("\0" 1) | |
+ ScriptLocaleVector locale{}; | |
+ if (!ConvertScriptToLocale(locale, aScript)) { | |
+ // In case the locale is not valid, do not write to the buffer to allow | |
+ // for fallbacking. | |
+ return Ok(); | |
+ } | |
+ | |
+ return FillBufferWithICUDisplayNames( | |
+ aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) { | |
+ return uloc_getDisplayScript(locale.begin(), mLocale.data(), target, | |
+ length, status); | |
+ }); | |
+ } | |
+ | |
+ return FillBufferWithICUDisplayNames( | |
+ aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) { | |
+ return uldn_scriptDisplayName(mULocaleDisplayNames.GetConst(), | |
+ aScript, target, length, status); | |
+ }); | |
+ }; | |
+ | |
+ [[nodiscard]] static bool ConvertScriptToLocale(ScriptLocaleVector& aLocale, | |
+ const char* aScript); | |
+ | |
+ Options mOptions; | |
+ std::string mLocale; | |
+ ICUPointer<ULocaleDisplayNames> mULocaleDisplayNames = | |
+ ICUPointer<ULocaleDisplayNames>(nullptr); | |
+}; | |
+ | |
+} // namespace mozilla::intl | |
+#endif | |
diff --git a/intl/components/src/ICU4CGlue.cpp b/intl/components/src/ICU4CGlue.cpp | |
--- a/intl/components/src/ICU4CGlue.cpp | |
+++ b/intl/components/src/ICU4CGlue.cpp | |
@@ -1,19 +1,23 @@ | |
/* 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/ICU4CGlue.h" | |
namespace mozilla::intl { | |
+ICUError ToICUError(UErrorCode status) { | |
+ if (status == U_MEMORY_ALLOCATION_ERROR) { | |
+ return ICUError::OutOfMemory; | |
+ } | |
+ return ICUError::InternalError; | |
+} | |
+ | |
ICUResult ToICUResult(UErrorCode status) { | |
if (U_SUCCESS(status)) { | |
return Ok(); | |
} | |
- if (status == U_MEMORY_ALLOCATION_ERROR) { | |
- return Err(ICUError::OutOfMemory); | |
- } | |
- return Err(ICUError::InternalError); | |
+ return Err(ToICUError(status)); | |
} | |
} // namespace mozilla::intl | |
diff --git a/intl/components/src/ICU4CGlue.h b/intl/components/src/ICU4CGlue.h | |
--- a/intl/components/src/ICU4CGlue.h | |
+++ b/intl/components/src/ICU4CGlue.h | |
@@ -23,16 +23,21 @@ enum class ICUError : uint8_t { | |
/** | |
* Error type when a method call can only result in an internal ICU error. | |
*/ | |
struct InternalError {}; | |
using ICUResult = Result<Ok, ICUError>; | |
/** | |
+ * Convert a UErrorCode to ICUError. | |
+ */ | |
+ICUError ToICUError(UErrorCode status); | |
+ | |
+/** | |
* Convert a UErrorCode to ICUResult. | |
*/ | |
ICUResult ToICUResult(UErrorCode status); | |
/** | |
* The ICU status can complain about a string not being terminated, but this | |
* is fine for this API, as it deals with the mozilla::Span that has a pointer | |
* and a length. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment