/* * Copyright (c) 2024-2025, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include namespace Unicode { Vector available_keyword_values(Utf16View locale, Utf16View key) { if (key == "ca"sv) return available_calendars(locale); if (key == "co"sv) return available_collations(locale); if (key == "hc"sv) return available_hour_cycles(locale); if (key == "kf"sv) return available_collation_case_orderings(); if (key == "kn"sv) return available_collation_numeric_orderings(); if (key == "nu"sv) return available_number_systems(locale); TODO(); } static bool is_available_calendar(char const* value, size_t value_length) { // "islamic" and "islamic-rgsa" are deprecated calendar types that DateTimeFormat resolves to other calendars, // so they should not be advertised as available. return !StringView { value, value_length }.is_one_of("islamic"sv, "islamic-rgsa"sv); } Vector const& available_calendars() { static NeverDestroyed> calendars { []() -> Vector { auto calendars = available_calendars(Utf16View { "und"sv }); quick_sort(calendars); return calendars; }() }; return *calendars; } Vector available_calendars(Utf16View locale) { UErrorCode status = U_ZERO_ERROR; auto locale_data = LocaleData::for_locale(locale.bytes()); if (!locale_data.has_value()) return {}; auto keywords = adopt_own_if_nonnull(icu::Calendar::getKeywordValuesForLocale("calendar", locale_data->locale(), 0, status)); if (icu_failure(status)) return {}; Vector result; while (true) { i32 length = 0; auto const* value = keywords->next(&length, status); if (icu_failure(status) || value == nullptr) break; if (!is_available_calendar(value, static_cast(length))) continue; if (auto const* bcp47_value = uloc_toUnicodeLocaleType("ca", value)) result.append(Utf16String::from_ascii_without_validation(StringView { bcp47_value, strlen(bcp47_value) }.bytes())); } return result; } Vector const& available_currencies() { static NeverDestroyed> currencies { []() -> Vector { UErrorCode status = U_ZERO_ERROR; auto* currencies = ucurr_openISOCurrencies(UCURR_ALL, &status); ScopeGuard guard { [&]() { uenum_close(currencies); } }; if (icu_failure(status)) return {}; Vector result; while (true) { i32 length = 0; char const* next = uenum_next(currencies, &length, &status); if (icu_failure(status)) return {}; if (next == nullptr) break; // https://unicode-org.atlassian.net/browse/ICU-21687 if (StringView currency { next, static_cast(length) }; currency != "LSM"sv) result.append(Utf16String::from_utf8(currency)); } quick_sort(result); return result; }() }; return *currencies; } Vector const& available_collation_case_orderings() { static NeverDestroyed> case_orderings { [] { return Vector { Utf16String::from_ascii_without_validation("false"sv.bytes()), Utf16String::from_ascii_without_validation("lower"sv.bytes()), Utf16String::from_ascii_without_validation("upper"sv.bytes()), }; }() }; return *case_orderings; } Vector const& available_collation_numeric_orderings() { static NeverDestroyed> numeric_orderings { [] { return Vector { Utf16String::from_ascii_without_validation("false"sv.bytes()), Utf16String::from_ascii_without_validation("true"sv.bytes()), }; }() }; return *numeric_orderings; } Vector const& available_collations() { static NeverDestroyed> collations { []() -> Vector { UErrorCode status = U_ZERO_ERROR; auto keywords = adopt_own_if_nonnull(icu::Collator::getKeywordValues("collation", status)); if (icu_failure(status)) return {}; Vector collations; while (true) { i32 length = 0; auto const* value = keywords->next(&length, status); if (icu_failure(status) || value == nullptr) break; // https://tc39.es/ecma402/#sec-properties-of-intl-collator-instances // the values "standard" and "search" are not allowed if (StringView { value, static_cast(length) }.is_one_of("standard"sv, "search"sv)) continue; if (auto const* bcp47_value = uloc_toUnicodeLocaleType("co", value)) collations.append(Utf16String::from_ascii_without_validation(StringView { bcp47_value, strlen(bcp47_value) }.bytes())); } quick_sort(collations); return collations; }() }; return *collations; } Vector available_collations(Utf16View locale) { UErrorCode status = U_ZERO_ERROR; auto locale_data = LocaleData::for_locale(locale.bytes()); if (!locale_data.has_value()) return {}; auto keywords = adopt_own_if_nonnull(icu::Collator::getKeywordValuesForLocale("collation", locale_data->locale(), true, status)); if (icu_failure(status)) return {}; Vector collations; while (true) { i32 length = 0; auto const* value = keywords->next(&length, status); if (icu_failure(status) || value == nullptr) break; // https://tc39.es/ecma402/#sec-properties-of-intl-collator-instances // the values "standard" and "search" are not allowed if (StringView { value, static_cast(length) }.is_one_of("standard"sv, "search"sv)) continue; if (auto const* bcp47_value = uloc_toUnicodeLocaleType("co", value)) collations.append(Utf16String::from_ascii_without_validation(StringView { bcp47_value, strlen(bcp47_value) }.bytes())); } auto default_collation = Utf16String::from_ascii_without_validation("default"sv.bytes()); if (!collations.contains_slow(default_collation)) collations.prepend(default_collation); return collations; } Vector const& available_hour_cycles() { static NeverDestroyed> hour_cycles { [] { return Vector { Utf16String::from_ascii_without_validation("h11"sv.bytes()), Utf16String::from_ascii_without_validation("h12"sv.bytes()), Utf16String::from_ascii_without_validation("h23"sv.bytes()), Utf16String::from_ascii_without_validation("h24"sv.bytes()), }; }() }; return *hour_cycles; } Vector available_hour_cycles(Utf16View locale) { auto preferred_hour_cycle = default_hour_cycle(locale); if (!preferred_hour_cycle.has_value()) return available_hour_cycles(); Vector hour_cycles; hour_cycles.append(available_hour_cycles()[to_underlying(*preferred_hour_cycle)]); for (auto const& hour_cycle : available_hour_cycles()) { if (hour_cycle != hour_cycles[0]) hour_cycles.append(hour_cycle); } return hour_cycles; } Vector const& available_number_systems() { static NeverDestroyed> number_systems { []() -> Vector { UErrorCode status = U_ZERO_ERROR; auto keywords = adopt_own_if_nonnull(icu::NumberingSystem::getAvailableNames(status)); if (icu_failure(status)) return {}; Vector number_systems; while (true) { i32 length = 0; auto const* keyword = keywords->next(&length, status); if (icu_failure(status) || keyword == nullptr) break; auto system = adopt_own_if_nonnull(icu::NumberingSystem::createInstanceByName(keyword, status)); if (icu_failure(status)) return {}; if (!static_cast(system->isAlgorithmic())) { if (auto const* bcp47_value = uloc_toUnicodeLocaleType("nu", keyword)) number_systems.append(Utf16String::from_ascii_without_validation(StringView { bcp47_value, strlen(bcp47_value) }.bytes())); } } quick_sort(number_systems); return number_systems; }() }; return *number_systems; } Vector available_number_systems(Utf16View locale) { auto locale_data = LocaleData::for_locale(locale.bytes()); if (!locale_data.has_value()) return {}; Vector number_systems; auto const* preferred_number_system = locale_data->numbering_system().getName(); number_systems.append(Utf16String::from_ascii_without_validation(StringView { preferred_number_system, strlen(preferred_number_system) }.bytes())); for (auto const& number_system : available_number_systems()) { if (number_system != number_systems[0]) number_systems.append(number_system); } return number_systems; } }