2024-06-15 21:23:53 -03:00
|
|
|
/*
|
2025-06-02 15:15:05 -03:00
|
|
|
* Copyright (c) 2024-2025, Tim Flynn <trflynn89@ladybird.org>
|
2024-06-15 21:23:53 -03:00
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
2026-06-04 05:40:32 -03:00
|
|
|
#include <AK/NeverDestroyed.h>
|
2024-06-15 21:23:53 -03:00
|
|
|
#include <AK/QuickSort.h>
|
|
|
|
|
#include <AK/ScopeGuard.h>
|
2024-06-23 10:14:27 -03:00
|
|
|
#include <LibUnicode/DateTimeFormat.h>
|
|
|
|
|
#include <LibUnicode/ICU.h>
|
|
|
|
|
#include <LibUnicode/UnicodeKeywords.h>
|
2024-06-15 21:23:53 -03:00
|
|
|
|
|
|
|
|
#include <unicode/calendar.h>
|
|
|
|
|
#include <unicode/coll.h>
|
|
|
|
|
#include <unicode/locid.h>
|
|
|
|
|
#include <unicode/numsys.h>
|
|
|
|
|
#include <unicode/ucurr.h>
|
|
|
|
|
|
2024-06-23 10:14:27 -03:00
|
|
|
namespace Unicode {
|
2024-06-15 21:23:53 -03:00
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> available_keyword_values(Utf16View locale, Utf16View key)
|
2024-06-15 21:23:53 -03:00
|
|
|
{
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
static bool is_available_calendar(char const* value, size_t value_length)
|
2024-06-15 21:23:53 -03:00
|
|
|
{
|
2026-06-21 14:03:10 -03:00
|
|
|
// "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<Utf16String> const& available_calendars()
|
|
|
|
|
{
|
|
|
|
|
static NeverDestroyed<Vector<Utf16String>> calendars { []() -> Vector<Utf16String> {
|
|
|
|
|
auto calendars = available_calendars(Utf16View { "und"sv });
|
2024-06-15 21:23:53 -03:00
|
|
|
|
|
|
|
|
quick_sort(calendars);
|
|
|
|
|
return calendars;
|
2026-06-04 05:40:32 -03:00
|
|
|
}() };
|
2024-06-15 21:23:53 -03:00
|
|
|
|
2026-06-04 05:40:32 -03:00
|
|
|
return *calendars;
|
2024-06-15 21:23:53 -03:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> available_calendars(Utf16View locale)
|
2024-06-15 21:23:53 -03:00
|
|
|
{
|
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
auto locale_data = LocaleData::for_locale(locale.bytes());
|
2024-06-15 21:23:53 -03:00
|
|
|
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 {};
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> 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<size_t>(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;
|
2024-06-15 21:23:53 -03:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> const& available_currencies()
|
2024-06-15 21:23:53 -03:00
|
|
|
{
|
2026-06-21 14:03:10 -03:00
|
|
|
static NeverDestroyed<Vector<Utf16String>> currencies { []() -> Vector<Utf16String> {
|
2024-06-15 21:23:53 -03:00
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
|
|
|
|
|
|
auto* currencies = ucurr_openISOCurrencies(UCURR_ALL, &status);
|
|
|
|
|
ScopeGuard guard { [&]() { uenum_close(currencies); } };
|
|
|
|
|
|
|
|
|
|
if (icu_failure(status))
|
|
|
|
|
return {};
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> result;
|
2024-06-15 21:23:53 -03:00
|
|
|
|
|
|
|
|
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<size_t>(length) }; currency != "LSM"sv)
|
2026-06-21 14:03:10 -03:00
|
|
|
result.append(Utf16String::from_utf8(currency));
|
2024-06-15 21:23:53 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
quick_sort(result);
|
|
|
|
|
return result;
|
2026-06-04 05:40:32 -03:00
|
|
|
}() };
|
2024-06-15 21:23:53 -03:00
|
|
|
|
2026-06-04 05:40:32 -03:00
|
|
|
return *currencies;
|
2024-06-15 21:23:53 -03:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> const& available_collation_case_orderings()
|
2024-06-15 21:23:53 -03:00
|
|
|
{
|
2026-06-21 14:03:10 -03:00
|
|
|
static NeverDestroyed<Vector<Utf16String>> case_orderings { [] {
|
|
|
|
|
return Vector<Utf16String> {
|
|
|
|
|
Utf16String::from_ascii_without_validation("false"sv.bytes()),
|
|
|
|
|
Utf16String::from_ascii_without_validation("lower"sv.bytes()),
|
|
|
|
|
Utf16String::from_ascii_without_validation("upper"sv.bytes()),
|
|
|
|
|
};
|
|
|
|
|
}() };
|
2026-06-04 05:40:32 -03:00
|
|
|
return *case_orderings;
|
2024-06-15 21:23:53 -03:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> const& available_collation_numeric_orderings()
|
2024-06-15 21:23:53 -03:00
|
|
|
{
|
2026-06-21 14:03:10 -03:00
|
|
|
static NeverDestroyed<Vector<Utf16String>> numeric_orderings { [] {
|
|
|
|
|
return Vector<Utf16String> {
|
|
|
|
|
Utf16String::from_ascii_without_validation("false"sv.bytes()),
|
|
|
|
|
Utf16String::from_ascii_without_validation("true"sv.bytes()),
|
|
|
|
|
};
|
|
|
|
|
}() };
|
|
|
|
|
return *numeric_orderings;
|
2024-06-15 21:23:53 -03:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> const& available_collations()
|
2024-06-15 21:23:53 -03:00
|
|
|
{
|
2026-06-21 14:03:10 -03:00
|
|
|
static NeverDestroyed<Vector<Utf16String>> collations { []() -> Vector<Utf16String> {
|
2025-06-02 15:35:12 -03:00
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
|
|
|
|
|
|
auto keywords = adopt_own_if_nonnull(icu::Collator::getKeywordValues("collation", status));
|
|
|
|
|
if (icu_failure(status))
|
|
|
|
|
return {};
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> collations;
|
|
|
|
|
while (true) {
|
|
|
|
|
i32 length = 0;
|
|
|
|
|
auto const* value = keywords->next(&length, status);
|
|
|
|
|
|
|
|
|
|
if (icu_failure(status) || value == nullptr)
|
|
|
|
|
break;
|
|
|
|
|
|
2025-06-02 15:35:12 -03:00
|
|
|
// https://tc39.es/ecma402/#sec-properties-of-intl-collator-instances
|
|
|
|
|
// the values "standard" and "search" are not allowed
|
2026-06-21 14:03:10 -03:00
|
|
|
if (StringView { value, static_cast<size_t>(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()));
|
|
|
|
|
}
|
2025-06-02 15:35:12 -03:00
|
|
|
|
|
|
|
|
quick_sort(collations);
|
|
|
|
|
return collations;
|
2026-06-04 05:40:32 -03:00
|
|
|
}() };
|
2025-06-02 15:35:12 -03:00
|
|
|
|
2026-06-04 05:40:32 -03:00
|
|
|
return *collations;
|
2024-06-15 21:23:53 -03:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> available_collations(Utf16View locale)
|
2024-06-15 21:23:53 -03:00
|
|
|
{
|
2025-06-02 15:35:12 -03:00
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
auto locale_data = LocaleData::for_locale(locale.bytes());
|
2025-06-02 15:35:12 -03:00
|
|
|
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 {};
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> collations;
|
|
|
|
|
while (true) {
|
|
|
|
|
i32 length = 0;
|
|
|
|
|
auto const* value = keywords->next(&length, status);
|
|
|
|
|
|
|
|
|
|
if (icu_failure(status) || value == nullptr)
|
|
|
|
|
break;
|
|
|
|
|
|
2025-06-02 15:35:12 -03:00
|
|
|
// https://tc39.es/ecma402/#sec-properties-of-intl-collator-instances
|
|
|
|
|
// the values "standard" and "search" are not allowed
|
2026-06-21 14:03:10 -03:00
|
|
|
if (StringView { value, static_cast<size_t>(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()));
|
|
|
|
|
}
|
2025-06-02 15:35:12 -03:00
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
auto default_collation = Utf16String::from_ascii_without_validation("default"sv.bytes());
|
|
|
|
|
if (!collations.contains_slow(default_collation))
|
|
|
|
|
collations.prepend(default_collation);
|
2025-06-02 15:35:12 -03:00
|
|
|
|
|
|
|
|
return collations;
|
2024-06-15 21:23:53 -03:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> const& available_hour_cycles()
|
2024-06-15 21:23:53 -03:00
|
|
|
{
|
2026-06-21 14:03:10 -03:00
|
|
|
static NeverDestroyed<Vector<Utf16String>> hour_cycles { [] {
|
|
|
|
|
return Vector<Utf16String> {
|
|
|
|
|
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()),
|
|
|
|
|
};
|
|
|
|
|
}() };
|
2026-06-04 05:40:32 -03:00
|
|
|
return *hour_cycles;
|
2024-06-15 21:23:53 -03:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> available_hour_cycles(Utf16View locale)
|
2024-06-15 21:23:53 -03:00
|
|
|
{
|
|
|
|
|
auto preferred_hour_cycle = default_hour_cycle(locale);
|
|
|
|
|
if (!preferred_hour_cycle.has_value())
|
|
|
|
|
return available_hour_cycles();
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> hour_cycles;
|
|
|
|
|
hour_cycles.append(available_hour_cycles()[to_underlying(*preferred_hour_cycle)]);
|
2024-06-15 21:23:53 -03:00
|
|
|
|
|
|
|
|
for (auto const& hour_cycle : available_hour_cycles()) {
|
|
|
|
|
if (hour_cycle != hour_cycles[0])
|
|
|
|
|
hour_cycles.append(hour_cycle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hour_cycles;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> const& available_number_systems()
|
2024-06-15 21:23:53 -03:00
|
|
|
{
|
2026-06-21 14:03:10 -03:00
|
|
|
static NeverDestroyed<Vector<Utf16String>> number_systems { []() -> Vector<Utf16String> {
|
2024-06-15 21:23:53 -03:00
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
|
|
|
|
|
|
auto keywords = adopt_own_if_nonnull(icu::NumberingSystem::getAvailableNames(status));
|
|
|
|
|
if (icu_failure(status))
|
|
|
|
|
return {};
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> number_systems;
|
|
|
|
|
while (true) {
|
|
|
|
|
i32 length = 0;
|
|
|
|
|
auto const* keyword = keywords->next(&length, status);
|
|
|
|
|
|
|
|
|
|
if (icu_failure(status) || keyword == nullptr)
|
|
|
|
|
break;
|
|
|
|
|
|
2024-06-15 21:23:53 -03:00
|
|
|
auto system = adopt_own_if_nonnull(icu::NumberingSystem::createInstanceByName(keyword, status));
|
|
|
|
|
if (icu_failure(status))
|
2026-06-21 14:03:10 -03:00
|
|
|
return {};
|
2024-06-15 21:23:53 -03:00
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
if (!static_cast<bool>(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()));
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-15 21:23:53 -03:00
|
|
|
|
|
|
|
|
quick_sort(number_systems);
|
|
|
|
|
return number_systems;
|
2026-06-04 05:40:32 -03:00
|
|
|
}() };
|
2024-06-15 21:23:53 -03:00
|
|
|
|
2026-06-04 05:40:32 -03:00
|
|
|
return *number_systems;
|
2024-06-15 21:23:53 -03:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> available_number_systems(Utf16View locale)
|
2024-06-15 21:23:53 -03:00
|
|
|
{
|
2026-06-21 14:03:10 -03:00
|
|
|
auto locale_data = LocaleData::for_locale(locale.bytes());
|
2024-06-15 21:23:53 -03:00
|
|
|
if (!locale_data.has_value())
|
|
|
|
|
return {};
|
|
|
|
|
|
2026-06-21 14:03:10 -03:00
|
|
|
Vector<Utf16String> number_systems;
|
2024-06-15 21:23:53 -03:00
|
|
|
|
|
|
|
|
auto const* preferred_number_system = locale_data->numbering_system().getName();
|
2026-06-21 14:03:10 -03:00
|
|
|
number_systems.append(Utf16String::from_ascii_without_validation(StringView { preferred_number_system, strlen(preferred_number_system) }.bytes()));
|
2024-06-15 21:23:53 -03:00
|
|
|
|
|
|
|
|
for (auto const& number_system : available_number_systems()) {
|
|
|
|
|
if (number_system != number_systems[0])
|
|
|
|
|
number_systems.append(number_system);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return number_systems;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|