1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 05:27:45 +00:00

LibLocale: Move locale source files to the LibLocale folder

These are still included in LibUnicode, but this updates their location
and the include paths of other files which include them.
This commit is contained in:
Timothy Flynn 2022-09-02 12:11:30 -04:00 committed by Tim Flynn
parent ff48220dca
commit 43a3471298
48 changed files with 145 additions and 135 deletions

View file

@ -3,11 +3,11 @@ include(${SerenityOS_SOURCE_DIR}/Meta/CMake/unicode_data.cmake)
set(SOURCES
CharacterTypes.cpp
CurrencyCode.cpp
DateTimeFormat.cpp
Locale.cpp
NumberFormat.cpp
PluralRules.cpp
RelativeTimeFormat.cpp
../LibLocale/DateTimeFormat.cpp
../LibLocale/Locale.cpp
../LibLocale/NumberFormat.cpp
../LibLocale/PluralRules.cpp
../LibLocale/RelativeTimeFormat.cpp
${UNICODE_DATA_SOURCES}
)

View file

@ -10,8 +10,8 @@
#include <AK/Types.h>
#include <AK/Utf16View.h>
#include <AK/Utf8View.h>
#include <LibLocale/Locale.h>
#include <LibUnicode/CharacterTypes.h>
#include <LibUnicode/Locale.h>
#if ENABLE_UNICODE_DATA
# include <LibUnicode/UnicodeData.h>

View file

@ -1,337 +0,0 @@
/*
* Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Array.h>
#include <AK/StringBuilder.h>
#include <LibUnicode/DateTimeFormat.h>
#include <LibUnicode/Locale.h>
#include <LibUnicode/NumberFormat.h>
#include <stdlib.h>
namespace Locale {
HourCycle hour_cycle_from_string(StringView hour_cycle)
{
if (hour_cycle == "h11"sv)
return HourCycle::H11;
else if (hour_cycle == "h12"sv)
return HourCycle::H12;
else if (hour_cycle == "h23"sv)
return HourCycle::H23;
else if (hour_cycle == "h24"sv)
return HourCycle::H24;
VERIFY_NOT_REACHED();
}
StringView hour_cycle_to_string(HourCycle hour_cycle)
{
switch (hour_cycle) {
case HourCycle::H11:
return "h11"sv;
case HourCycle::H12:
return "h12"sv;
case HourCycle::H23:
return "h23"sv;
case HourCycle::H24:
return "h24"sv;
default:
VERIFY_NOT_REACHED();
}
}
CalendarPatternStyle calendar_pattern_style_from_string(StringView style)
{
if (style == "narrow"sv)
return CalendarPatternStyle::Narrow;
if (style == "short"sv)
return CalendarPatternStyle::Short;
if (style == "long"sv)
return CalendarPatternStyle::Long;
if (style == "numeric"sv)
return CalendarPatternStyle::Numeric;
if (style == "2-digit"sv)
return CalendarPatternStyle::TwoDigit;
if (style == "shortOffset"sv)
return CalendarPatternStyle::ShortOffset;
if (style == "longOffset"sv)
return CalendarPatternStyle::LongOffset;
if (style == "shortGeneric"sv)
return CalendarPatternStyle::ShortGeneric;
if (style == "longGeneric"sv)
return CalendarPatternStyle::LongGeneric;
VERIFY_NOT_REACHED();
}
StringView calendar_pattern_style_to_string(CalendarPatternStyle style)
{
switch (style) {
case CalendarPatternStyle::Narrow:
return "narrow"sv;
case CalendarPatternStyle::Short:
return "short"sv;
case CalendarPatternStyle::Long:
return "long"sv;
case CalendarPatternStyle::Numeric:
return "numeric"sv;
case CalendarPatternStyle::TwoDigit:
return "2-digit"sv;
case CalendarPatternStyle::ShortOffset:
return "shortOffset"sv;
case CalendarPatternStyle::LongOffset:
return "longOffset"sv;
case CalendarPatternStyle::ShortGeneric:
return "shortGeneric"sv;
case CalendarPatternStyle::LongGeneric:
return "longGeneric"sv;
default:
VERIFY_NOT_REACHED();
}
}
Optional<HourCycleRegion> __attribute__((weak)) hour_cycle_region_from_string(StringView) { return {}; }
Vector<HourCycle> __attribute__((weak)) get_regional_hour_cycles(StringView) { return {}; }
template<typename GetRegionalValues>
static auto find_regional_values_for_locale(StringView locale, GetRegionalValues&& get_regional_values)
{
auto has_value = [](auto const& container) {
if constexpr (requires { container.has_value(); })
return container.has_value();
else
return !container.is_empty();
};
if (auto regional_values = get_regional_values(locale); has_value(regional_values))
return regional_values;
auto return_default_values = [&]() { return get_regional_values("001"sv); };
auto language = parse_unicode_language_id(locale);
if (!language.has_value())
return return_default_values();
if (!language->region.has_value())
language = add_likely_subtags(*language);
if (!language.has_value() || !language->region.has_value())
return return_default_values();
if (auto regional_values = get_regional_values(*language->region); has_value(regional_values))
return regional_values;
return return_default_values();
}
// https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
Vector<HourCycle> get_locale_hour_cycles(StringView locale)
{
return find_regional_values_for_locale(locale, get_regional_hour_cycles);
}
Optional<HourCycle> get_default_regional_hour_cycle(StringView locale)
{
if (auto hour_cycles = get_locale_hour_cycles(locale); !hour_cycles.is_empty())
return hour_cycles.first();
return {};
}
Optional<MinimumDaysRegion> __attribute__((weak)) minimum_days_region_from_string(StringView) { return {}; }
Optional<u8> __attribute__((weak)) get_regional_minimum_days(StringView) { return {}; }
Optional<u8> get_locale_minimum_days(StringView locale)
{
return find_regional_values_for_locale(locale, get_regional_minimum_days);
}
Optional<FirstDayRegion> __attribute__((weak)) first_day_region_from_string(StringView) { return {}; }
Optional<Weekday> __attribute__((weak)) get_regional_first_day(StringView) { return {}; }
Optional<Weekday> get_locale_first_day(StringView locale)
{
return find_regional_values_for_locale(locale, get_regional_first_day);
}
Optional<WeekendStartRegion> __attribute__((weak)) weekend_start_region_from_string(StringView) { return {}; }
Optional<Weekday> __attribute__((weak)) get_regional_weekend_start(StringView) { return {}; }
Optional<Weekday> get_locale_weekend_start(StringView locale)
{
return find_regional_values_for_locale(locale, get_regional_weekend_start);
}
Optional<WeekendEndRegion> __attribute__((weak)) weekend_end_region_from_string(StringView) { return {}; }
Optional<Weekday> __attribute__((weak)) get_regional_weekend_end(StringView) { return {}; }
Optional<Weekday> get_locale_weekend_end(StringView locale)
{
return find_regional_values_for_locale(locale, get_regional_weekend_end);
}
String combine_skeletons(StringView first, StringView second)
{
// https://unicode.org/reports/tr35/tr35-dates.html#availableFormats_appendItems
constexpr auto field_order = Array {
"G"sv, // Era
"yYuUr"sv, // Year
"ML"sv, // Month
"dDFg"sv, // Day
"Eec"sv, // Weekday
"abB"sv, // Period
"hHKk"sv, // Hour
"m"sv, // Minute
"sSA"sv, // Second
"zZOvVXx"sv, // Zone
};
StringBuilder builder;
auto append_from_skeleton = [&](auto skeleton, auto ch) {
auto first_index = skeleton.find(ch);
if (!first_index.has_value())
return false;
auto last_index = skeleton.find_last(ch);
builder.append(skeleton.substring_view(*first_index, *last_index - *first_index + 1));
return true;
};
for (auto fields : field_order) {
for (auto ch : fields) {
if (append_from_skeleton(first, ch))
break;
if (append_from_skeleton(second, ch))
break;
}
}
return builder.build();
}
Optional<CalendarFormat> __attribute__((weak)) get_calendar_date_format(StringView, StringView) { return {}; }
Optional<CalendarFormat> __attribute__((weak)) get_calendar_time_format(StringView, StringView) { return {}; }
Optional<CalendarFormat> __attribute__((weak)) get_calendar_date_time_format(StringView, StringView) { return {}; }
Optional<CalendarFormat> get_calendar_format(StringView locale, StringView calendar, CalendarFormatType type)
{
switch (type) {
case CalendarFormatType::Date:
return get_calendar_date_format(locale, calendar);
case CalendarFormatType::Time:
return get_calendar_time_format(locale, calendar);
case CalendarFormatType::DateTime:
return get_calendar_date_time_format(locale, calendar);
default:
VERIFY_NOT_REACHED();
}
}
Vector<CalendarPattern> __attribute__((weak)) get_calendar_available_formats(StringView, StringView) { return {}; }
Optional<CalendarRangePattern> __attribute__((weak)) get_calendar_default_range_format(StringView, StringView) { return {}; }
Vector<CalendarRangePattern> __attribute__((weak)) get_calendar_range_formats(StringView, StringView, StringView) { return {}; }
Vector<CalendarRangePattern> __attribute__((weak)) get_calendar_range12_formats(StringView, StringView, StringView) { return {}; }
Optional<StringView> __attribute__((weak)) get_calendar_era_symbol(StringView, StringView, CalendarPatternStyle, Era) { return {}; }
Optional<StringView> __attribute__((weak)) get_calendar_month_symbol(StringView, StringView, CalendarPatternStyle, Month) { return {}; }
Optional<StringView> __attribute__((weak)) get_calendar_weekday_symbol(StringView, StringView, CalendarPatternStyle, Weekday) { return {}; }
Optional<StringView> __attribute__((weak)) get_calendar_day_period_symbol(StringView, StringView, CalendarPatternStyle, DayPeriod) { return {}; }
Optional<StringView> __attribute__((weak)) get_calendar_day_period_symbol_for_hour(StringView, StringView, CalendarPatternStyle, u8) { return {}; }
Optional<StringView> __attribute__((weak)) get_time_zone_name(StringView, StringView, CalendarPatternStyle, TimeZone::InDST) { return {}; }
Optional<TimeZoneFormat> __attribute__((weak)) get_time_zone_format(StringView) { return {}; }
static Optional<String> format_time_zone_offset(StringView locale, CalendarPatternStyle style, i64 offset_seconds)
{
auto formats = get_time_zone_format(locale);
if (!formats.has_value())
return {};
auto number_system = get_preferred_keyword_value_for_locale(locale, "nu"sv);
if (!number_system.has_value())
return {};
if (offset_seconds == 0)
return formats->gmt_zero_format;
auto sign = offset_seconds > 0 ? formats->symbol_ahead_sign : formats->symbol_behind_sign;
auto separator = offset_seconds > 0 ? formats->symbol_ahead_separator : formats->symbol_behind_separator;
offset_seconds = llabs(offset_seconds);
auto offset_hours = offset_seconds / 3'600;
offset_seconds %= 3'600;
auto offset_minutes = offset_seconds / 60;
offset_seconds %= 60;
StringBuilder builder;
builder.append(sign);
switch (style) {
// The long format always uses 2-digit hours field and minutes field, with optional 2-digit seconds field.
case CalendarPatternStyle::LongOffset:
builder.appendff("{:02}{}{:02}", offset_hours, separator, offset_minutes);
if (offset_seconds > 0)
builder.appendff("{}{:02}", separator, offset_seconds);
break;
// The short format is intended for the shortest representation and uses hour fields without leading zero, with optional 2-digit minutes and seconds fields.
case CalendarPatternStyle::ShortOffset:
builder.appendff("{}", offset_hours);
if (offset_minutes > 0) {
builder.appendff("{}{:02}", separator, offset_minutes);
if (offset_seconds > 0)
builder.appendff("{}{:02}", separator, offset_seconds);
}
break;
default:
VERIFY_NOT_REACHED();
}
// The digits used for hours, minutes and seconds fields in this format are the locale's default decimal digits.
auto result = replace_digits_for_number_system(*number_system, builder.build());
return formats->gmt_format.replace("{0}"sv, result, ReplaceMode::FirstOnly);
}
// https://unicode.org/reports/tr35/tr35-dates.html#Time_Zone_Format_Terminology
String format_time_zone(StringView locale, StringView time_zone, CalendarPatternStyle style, AK::Time time)
{
auto offset = TimeZone::get_time_zone_offset(time_zone, time);
if (!offset.has_value())
return time_zone;
switch (style) {
case CalendarPatternStyle::Short:
case CalendarPatternStyle::Long:
case CalendarPatternStyle::ShortGeneric:
case CalendarPatternStyle::LongGeneric:
if (auto name = get_time_zone_name(locale, time_zone, style, offset->in_dst); name.has_value())
return *name;
break;
case CalendarPatternStyle::ShortOffset:
case CalendarPatternStyle::LongOffset:
return format_time_zone_offset(locale, style, offset->seconds).value_or(time_zone);
default:
VERIFY_NOT_REACHED();
}
// If more styles are added, consult the following table to ensure always falling back to GMT offset is still correct:
// https://unicode.org/reports/tr35/tr35-dates.html#dfst-zone
switch (style) {
case CalendarPatternStyle::Short:
case CalendarPatternStyle::ShortGeneric:
return format_time_zone(locale, time_zone, CalendarPatternStyle::ShortOffset, time);
case CalendarPatternStyle::Long:
case CalendarPatternStyle::LongGeneric:
return format_time_zone(locale, time_zone, CalendarPatternStyle::LongOffset, time);
default:
VERIFY_NOT_REACHED();
}
}
}

View file

@ -1,232 +0,0 @@
/*
* Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Time.h>
#include <AK/Types.h>
#include <AK/Vector.h>
#include <LibTimeZone/TimeZone.h>
#include <LibUnicode/Forward.h>
namespace Locale {
enum class Era : u8 {
BC,
AD,
};
enum class Month : u8 {
January,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December,
};
enum class Weekday : u8 {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
};
enum class DayPeriod : u8 {
AM,
PM,
Noon,
Morning1,
Morning2,
Afternoon1,
Afternoon2,
Evening1,
Evening2,
Night1,
Night2,
};
enum class HourCycle : u8 {
H11,
H12,
H23,
H24,
};
enum class CalendarPatternStyle : u8 {
Narrow,
Short,
Long,
Numeric,
TwoDigit,
ShortOffset,
LongOffset,
ShortGeneric,
LongGeneric,
};
struct CalendarPattern {
enum class Field {
Era,
Year,
Month,
Weekday,
Day,
DayPeriod,
Hour,
Minute,
Second,
FractionalSecondDigits,
TimeZoneName,
};
template<typename Callback>
void for_each_calendar_field_zipped_with(CalendarPattern const& other, Callback&& callback)
{
callback(era, other.era, Field::Era);
callback(year, other.year, Field::Year);
callback(month, other.month, Field::Month);
callback(weekday, other.weekday, Field::Weekday);
callback(day, other.day, Field::Day);
callback(day_period, other.day_period, Field::DayPeriod);
callback(hour, other.hour, Field::Hour);
callback(minute, other.minute, Field::Minute);
callback(second, other.second, Field::Second);
callback(fractional_second_digits, other.fractional_second_digits, Field::FractionalSecondDigits);
callback(time_zone_name, other.time_zone_name, Field::TimeZoneName);
}
String skeleton {};
String pattern {};
Optional<String> pattern12 {};
Optional<HourCycle> hour_cycle {};
// https://unicode.org/reports/tr35/tr35-dates.html#Calendar_Fields
Optional<CalendarPatternStyle> era {};
Optional<CalendarPatternStyle> year {};
Optional<CalendarPatternStyle> month {};
Optional<CalendarPatternStyle> weekday {};
Optional<CalendarPatternStyle> day {};
Optional<CalendarPatternStyle> day_period {};
Optional<CalendarPatternStyle> hour {};
Optional<CalendarPatternStyle> minute {};
Optional<CalendarPatternStyle> second {};
Optional<u8> fractional_second_digits {};
Optional<CalendarPatternStyle> time_zone_name {};
};
struct CalendarRangePattern : public CalendarPattern {
enum class Field {
Era,
Year,
Month,
Day,
AmPm,
DayPeriod,
Hour,
Minute,
Second,
FractionalSecondDigits,
};
Optional<Field> field {};
String start_range {};
StringView separator {};
String end_range {};
};
enum class CalendarFormatType : u8 {
Date,
Time,
DateTime,
};
struct CalendarFormat {
CalendarPattern full_format {};
CalendarPattern long_format {};
CalendarPattern medium_format {};
CalendarPattern short_format {};
};
enum class CalendarSymbol : u8 {
DayPeriod,
Era,
Month,
Weekday,
};
struct TimeZoneFormat {
StringView symbol_ahead_sign {};
StringView symbol_ahead_separator {};
StringView symbol_behind_sign {};
StringView symbol_behind_separator {};
StringView gmt_format {};
StringView gmt_zero_format {};
};
HourCycle hour_cycle_from_string(StringView hour_cycle);
StringView hour_cycle_to_string(HourCycle hour_cycle);
CalendarPatternStyle calendar_pattern_style_from_string(StringView style);
StringView calendar_pattern_style_to_string(CalendarPatternStyle style);
Optional<HourCycleRegion> hour_cycle_region_from_string(StringView hour_cycle_region);
Vector<HourCycle> get_regional_hour_cycles(StringView region);
Vector<HourCycle> get_locale_hour_cycles(StringView locale);
Optional<HourCycle> get_default_regional_hour_cycle(StringView locale);
Optional<MinimumDaysRegion> minimum_days_region_from_string(StringView minimum_days_region);
Optional<u8> get_regional_minimum_days(StringView region);
Optional<u8> get_locale_minimum_days(StringView region);
Optional<FirstDayRegion> first_day_region_from_string(StringView first_day_region);
Optional<Weekday> get_regional_first_day(StringView region);
Optional<Weekday> get_locale_first_day(StringView region);
Optional<WeekendStartRegion> weekend_start_region_from_string(StringView weekend_start_region);
Optional<Weekday> get_regional_weekend_start(StringView region);
Optional<Weekday> get_locale_weekend_start(StringView region);
Optional<WeekendEndRegion> weekend_end_region_from_string(StringView weekend_end_region);
Optional<Weekday> get_regional_weekend_end(StringView region);
Optional<Weekday> get_locale_weekend_end(StringView region);
String combine_skeletons(StringView first, StringView second);
Optional<CalendarFormat> get_calendar_date_format(StringView locale, StringView calendar);
Optional<CalendarFormat> get_calendar_time_format(StringView locale, StringView calendar);
Optional<CalendarFormat> get_calendar_date_time_format(StringView locale, StringView calendar);
Optional<CalendarFormat> get_calendar_format(StringView locale, StringView calendar, CalendarFormatType type);
Vector<CalendarPattern> get_calendar_available_formats(StringView locale, StringView calendar);
Optional<CalendarRangePattern> get_calendar_default_range_format(StringView locale, StringView calendar);
Vector<CalendarRangePattern> get_calendar_range_formats(StringView locale, StringView calendar, StringView skeleton);
Vector<CalendarRangePattern> get_calendar_range12_formats(StringView locale, StringView calendar, StringView skeleton);
Optional<StringView> get_calendar_era_symbol(StringView locale, StringView calendar, CalendarPatternStyle style, Era value);
Optional<StringView> get_calendar_month_symbol(StringView locale, StringView calendar, CalendarPatternStyle style, Month value);
Optional<StringView> get_calendar_weekday_symbol(StringView locale, StringView calendar, CalendarPatternStyle style, Weekday value);
Optional<StringView> get_calendar_day_period_symbol(StringView locale, StringView calendar, CalendarPatternStyle style, DayPeriod value);
Optional<StringView> get_calendar_day_period_symbol_for_hour(StringView locale, StringView calendar, CalendarPatternStyle style, u8 hour);
String format_time_zone(StringView locale, StringView time_zone, CalendarPatternStyle style, AK::Time time);
Optional<StringView> get_time_zone_name(StringView locale, StringView time_zone, CalendarPatternStyle style, TimeZone::InDST in_dst);
Optional<TimeZoneFormat> get_time_zone_format(StringView locale);
}

View file

@ -22,57 +22,3 @@ struct CurrencyCode;
struct SpecialCasing;
}
namespace Locale {
enum class CalendarFormatType : u8;
enum class CalendarPatternStyle : u8;
enum class CalendarSymbol : u8;
enum class CharacterOrder : u8;
enum class CompactNumberFormatType : u8;
enum class Condition : u8;
enum class Currency : u16;
enum class DateField : u8;
enum class DayPeriod : u8;
enum class Era : u8;
enum class FirstDayRegion : u8;
enum class HourCycle : u8;
enum class HourCycleRegion : u16;
enum class Key : u8;
enum class KeywordCalendar : u8;
enum class KeywordCollation : u8;
enum class KeywordColCaseFirst : u8;
enum class KeywordColNumeric : u8;
enum class KeywordHours : u8;
enum class KeywordNumbers : u8;
enum class Language : u16;
enum class ListPatternType : u8;
enum class Locale : u16;
enum class MinimumDaysRegion : u8;
enum class Month : u8;
enum class NumericSymbol : u8;
enum class PluralCategory : u8;
enum class ScriptTag : u8;
enum class StandardNumberFormatType : u8;
enum class Style : u8;
enum class Territory : u8;
enum class Weekday : u8;
enum class WeekendEndRegion : u8;
enum class WeekendStartRegion : u8;
struct CalendarFormat;
struct CalendarPattern;
struct CalendarRangePattern;
struct Keyword;
struct LanguageID;
struct ListPatterns;
struct LocaleExtension;
struct LocaleID;
struct NumberFormat;
struct NumberGroupings;
struct OtherExtension;
struct PluralOperands;
struct TransformedExtension;
struct TransformedField;
}

View file

@ -1,979 +0,0 @@
/*
* Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/AllOf.h>
#include <AK/GenericLexer.h>
#include <AK/QuickSort.h>
#include <AK/StringBuilder.h>
#include <LibUnicode/CharacterTypes.h>
#include <LibUnicode/DateTimeFormat.h>
#include <LibUnicode/Locale.h>
namespace Locale {
static bool is_key(StringView key)
{
// key = alphanum alpha
if (key.length() != 2)
return false;
return is_ascii_alphanumeric(key[0]) && is_ascii_alpha(key[1]);
}
static bool is_single_type(StringView type)
{
// type = alphanum{3,8} (sep alphanum{3,8})*
// Note: Consecutive types are not handled here, that is left to the caller.
if ((type.length() < 3) || (type.length() > 8))
return false;
return all_of(type, is_ascii_alphanumeric);
}
static bool is_attribute(StringView type)
{
// attribute = alphanum{3,8}
if ((type.length() < 3) || (type.length() > 8))
return false;
return all_of(type, is_ascii_alphanumeric);
}
static bool is_transformed_key(StringView key)
{
// tkey = alpha digit
if (key.length() != 2)
return false;
return is_ascii_alpha(key[0]) && is_ascii_digit(key[1]);
}
static bool is_single_transformed_value(StringView value)
{
// tvalue = (sep alphanum{3,8})+
// Note: Consecutive values are not handled here, that is left to the caller.
if ((value.length() < 3) || (value.length() > 8))
return false;
return all_of(value, is_ascii_alphanumeric);
}
static Optional<StringView> consume_next_segment(GenericLexer& lexer, bool with_separator = true)
{
constexpr auto is_separator = is_any_of("-_"sv);
if (with_separator) {
if (!lexer.next_is(is_separator))
return {};
lexer.ignore();
}
auto segment = lexer.consume_until(is_separator);
if (segment.is_empty()) {
lexer.retreat(with_separator);
return {};
}
return segment;
}
bool is_type_identifier(StringView identifier)
{
// type = alphanum{3,8} (sep alphanum{3,8})*
GenericLexer lexer { identifier };
while (true) {
auto type = consume_next_segment(lexer, lexer.tell() > 0);
if (!type.has_value())
break;
if (!is_single_type(*type))
return false;
}
return lexer.is_eof() && (lexer.tell() > 0);
}
static Optional<LanguageID> parse_unicode_language_id(GenericLexer& lexer)
{
// https://unicode.org/reports/tr35/#Unicode_language_identifier
//
// unicode_language_id = "root"
// OR
// unicode_language_id = ((unicode_language_subtag (sep unicode_script_subtag)?) | unicode_script_subtag)
// (sep unicode_region_subtag)?
// (sep unicode_variant_subtag)*
LanguageID language_id {};
if (lexer.consume_specific("root"sv)) {
language_id.is_root = true;
return language_id;
}
enum class ParseState {
ParsingLanguageOrScript,
ParsingScript,
ParsingRegion,
ParsingVariant,
Done,
};
auto state = ParseState::ParsingLanguageOrScript;
while (!lexer.is_eof() && (state != ParseState::Done)) {
auto segment = consume_next_segment(lexer, state != ParseState::ParsingLanguageOrScript);
if (!segment.has_value())
return {};
switch (state) {
case ParseState::ParsingLanguageOrScript:
if (is_unicode_language_subtag(*segment)) {
state = ParseState::ParsingScript;
language_id.language = *segment;
} else if (is_unicode_script_subtag(*segment)) {
state = ParseState::ParsingRegion;
language_id.script = *segment;
} else {
return {};
}
break;
case ParseState::ParsingScript:
if (is_unicode_script_subtag(*segment)) {
state = ParseState::ParsingRegion;
language_id.script = *segment;
break;
}
state = ParseState::ParsingRegion;
[[fallthrough]];
case ParseState::ParsingRegion:
if (is_unicode_region_subtag(*segment)) {
state = ParseState::ParsingVariant;
language_id.region = *segment;
break;
}
state = ParseState::ParsingVariant;
[[fallthrough]];
case ParseState::ParsingVariant:
if (is_unicode_variant_subtag(*segment)) {
language_id.variants.append(*segment);
} else {
lexer.retreat(segment->length() + 1);
state = ParseState::Done;
}
break;
default:
VERIFY_NOT_REACHED();
}
}
return language_id;
}
static Optional<LocaleExtension> parse_unicode_locale_extension(GenericLexer& lexer)
{
// https://unicode.org/reports/tr35/#unicode_locale_extensions
//
// unicode_locale_extensions = sep [uU] ((sep keyword)+ | (sep attribute)+ (sep keyword)*)
LocaleExtension locale_extension {};
enum class ParseState {
ParsingAttributeOrKeyword,
ParsingAttribute,
ParsingKeyword,
Done,
};
auto state = ParseState::ParsingAttributeOrKeyword;
while (!lexer.is_eof() && (state != ParseState::Done)) {
auto segment = consume_next_segment(lexer);
if (!segment.has_value())
return {};
if (state == ParseState::ParsingAttributeOrKeyword)
state = is_key(*segment) ? ParseState::ParsingKeyword : ParseState::ParsingAttribute;
switch (state) {
case ParseState::ParsingAttribute:
if (is_attribute(*segment)) {
locale_extension.attributes.append(*segment);
break;
}
state = ParseState::ParsingKeyword;
[[fallthrough]];
case ParseState::ParsingKeyword: {
// keyword = key (sep type)?
Keyword keyword { .key = *segment };
Vector<StringView> keyword_values;
if (!is_key(*segment)) {
lexer.retreat(segment->length() + 1);
state = ParseState::Done;
break;
}
while (true) {
auto type = consume_next_segment(lexer);
if (!type.has_value() || !is_single_type(*type)) {
if (type.has_value())
lexer.retreat(type->length() + 1);
break;
}
keyword_values.append(*type);
}
StringBuilder builder;
builder.join('-', keyword_values);
keyword.value = builder.build();
locale_extension.keywords.append(move(keyword));
break;
}
default:
VERIFY_NOT_REACHED();
}
}
if (locale_extension.attributes.is_empty() && locale_extension.keywords.is_empty())
return {};
return locale_extension;
}
static Optional<TransformedExtension> parse_transformed_extension(GenericLexer& lexer)
{
// https://unicode.org/reports/tr35/#transformed_extensions
//
// transformed_extensions = sep [tT] ((sep tlang (sep tfield)*) | (sep tfield)+)
TransformedExtension transformed_extension {};
enum class ParseState {
ParsingLanguageOrField,
ParsingLanguage,
ParsingField,
Done,
};
auto state = ParseState::ParsingLanguageOrField;
while (!lexer.is_eof() && (state != ParseState::Done)) {
auto segment = consume_next_segment(lexer);
if (!segment.has_value())
return {};
if (state == ParseState::ParsingLanguageOrField)
state = is_unicode_language_subtag(*segment) ? ParseState::ParsingLanguage : ParseState::ParsingField;
switch (state) {
case ParseState::ParsingLanguage:
lexer.retreat(segment->length());
if (auto language_id = parse_unicode_language_id(lexer); language_id.has_value()) {
transformed_extension.language = language_id.release_value();
state = ParseState::ParsingField;
break;
}
return {};
case ParseState::ParsingField: {
// tfield = tkey tvalue;
TransformedField field { .key = *segment };
Vector<StringView> field_values;
if (!is_transformed_key(*segment)) {
lexer.retreat(segment->length() + 1);
state = ParseState::Done;
break;
}
while (true) {
auto value = consume_next_segment(lexer);
if (!value.has_value() || !is_single_transformed_value(*value)) {
if (value.has_value())
lexer.retreat(value->length() + 1);
break;
}
field_values.append(*value);
}
if (field_values.is_empty())
return {};
StringBuilder builder;
builder.join('-', field_values);
field.value = builder.build();
transformed_extension.fields.append(move(field));
break;
}
default:
VERIFY_NOT_REACHED();
}
}
if (!transformed_extension.language.has_value() && transformed_extension.fields.is_empty())
return {};
return transformed_extension;
}
static Optional<OtherExtension> parse_other_extension(char key, GenericLexer& lexer)
{
// https://unicode.org/reports/tr35/#other_extensions
//
// other_extensions = sep [alphanum-[tTuUxX]] (sep alphanum{2,8})+ ;
OtherExtension other_extension { .key = key };
Vector<StringView> other_values;
if (!is_ascii_alphanumeric(key) || (key == 'x') || (key == 'X'))
return {};
while (true) {
auto segment = consume_next_segment(lexer);
if (!segment.has_value())
break;
if ((segment->length() < 2) || (segment->length() > 8) || !all_of(*segment, is_ascii_alphanumeric)) {
lexer.retreat(segment->length() + 1);
break;
}
other_values.append(*segment);
}
if (other_values.is_empty())
return {};
StringBuilder builder;
builder.join('-', other_values);
other_extension.value = builder.build();
return other_extension;
}
static Optional<Extension> parse_extension(GenericLexer& lexer)
{
// https://unicode.org/reports/tr35/#extensions
//
// extensions = unicode_locale_extensions | transformed_extensions | other_extensions
size_t starting_position = lexer.tell();
if (auto header = consume_next_segment(lexer); header.has_value() && (header->length() == 1)) {
switch (char key = (*header)[0]) {
case 'u':
case 'U':
if (auto extension = parse_unicode_locale_extension(lexer); extension.has_value())
return Extension { extension.release_value() };
break;
case 't':
case 'T':
if (auto extension = parse_transformed_extension(lexer); extension.has_value())
return Extension { extension.release_value() };
break;
default:
if (auto extension = parse_other_extension(key, lexer); extension.has_value())
return Extension { extension.release_value() };
break;
}
}
lexer.retreat(lexer.tell() - starting_position);
return {};
}
static Vector<String> parse_private_use_extensions(GenericLexer& lexer)
{
// https://unicode.org/reports/tr35/#pu_extensions
//
// pu_extensions = = sep [xX] (sep alphanum{1,8})+ ;
size_t starting_position = lexer.tell();
auto header = consume_next_segment(lexer);
if (!header.has_value())
return {};
auto parse_values = [&]() -> Vector<String> {
Vector<String> extensions;
while (true) {
auto segment = consume_next_segment(lexer);
if (!segment.has_value())
break;
if ((segment->length() < 1) || (segment->length() > 8) || !all_of(*segment, is_ascii_alphanumeric)) {
lexer.retreat(segment->length() + 1);
break;
}
extensions.append(*segment);
}
return extensions;
};
if ((header->length() == 1) && (((*header)[0] == 'x') || ((*header)[0] == 'X'))) {
if (auto extensions = parse_values(); !extensions.is_empty())
return extensions;
}
lexer.retreat(lexer.tell() - starting_position);
return {};
}
Optional<LanguageID> parse_unicode_language_id(StringView language)
{
GenericLexer lexer { language };
auto language_id = parse_unicode_language_id(lexer);
if (!lexer.is_eof())
return {};
return language_id;
}
Optional<LocaleID> parse_unicode_locale_id(StringView locale)
{
GenericLexer lexer { locale };
// https://unicode.org/reports/tr35/#Unicode_locale_identifier
//
// unicode_locale_id = unicode_language_id
// extensions*
// pu_extensions?
auto language_id = parse_unicode_language_id(lexer);
if (!language_id.has_value())
return {};
LocaleID locale_id { language_id.release_value() };
while (true) {
auto extension = parse_extension(lexer);
if (!extension.has_value())
break;
locale_id.extensions.append(extension.release_value());
}
locale_id.private_use_extensions = parse_private_use_extensions(lexer);
if (!lexer.is_eof())
return {};
return locale_id;
}
static void perform_hard_coded_key_value_substitutions(StringView key, String& value)
{
// FIXME: In the XML export of CLDR, there are some aliases defined in the following files:
// https://github.com/unicode-org/cldr-staging/blob/master/production/common/bcp47/calendar.xml
// https://github.com/unicode-org/cldr-staging/blob/master/production/common/bcp47/collation.xml
// https://github.com/unicode-org/cldr-staging/blob/master/production/common/bcp47/measure.xml
// https://github.com/unicode-org/cldr-staging/blob/master/production/common/bcp47/timezone.xml
// https://github.com/unicode-org/cldr-staging/blob/master/production/common/bcp47/transform.xml
//
// There isn't yet a counterpart in the JSON export. See: https://unicode-org.atlassian.net/browse/CLDR-14571
if (key == "ca"sv) {
if (value == "islamicc"sv)
value = "islamic-civil"sv;
else if (value == "ethiopic-amete-alem"sv)
value = "ethioaa"sv;
} else if (key.is_one_of("kb"sv, "kc"sv, "kh"sv, "kk"sv, "kn"sv) && (value == "yes"sv)) {
value = "true"sv;
} else if (key == "ks"sv) {
if (value == "primary"sv)
value = "level1"sv;
else if (value == "tertiary"sv)
value = "level3"sv;
// Note: There are also aliases for "secondary", "quaternary", "quarternary", and "identical",
// but those are semantically incorrect values (they are too long), so they can be skipped.
} else if ((key == "m0"sv) && (value == "names"sv)) {
value = "prprname"sv;
} else if ((key == "ms"sv) && (value == "imperial"sv)) {
value = "uksystem"sv;
} else if (key == "tz"sv) {
// Formatter disabled because this block is easier to read / check against timezone.xml as one-liners.
// clang-format off
if (value == "aqams"sv) value = "nzakl"sv;
else if (value == "cnckg"sv) value = "cnsha"sv;
else if (value == "cnhrb"sv) value = "cnsha"sv;
else if (value == "cnkhg"sv) value = "cnurc"sv;
else if (value == "cuba"sv) value = "cuhav"sv;
else if (value == "egypt"sv) value = "egcai"sv;
else if (value == "eire"sv) value = "iedub"sv;
else if (value == "est"sv) value = "utcw05"sv;
else if (value == "gmt0"sv) value = "gmt"sv;
else if (value == "hongkong"sv) value = "hkhkg"sv;
else if (value == "hst"sv) value = "utcw10"sv;
else if (value == "iceland"sv) value = "isrey"sv;
else if (value == "iran"sv) value = "irthr"sv;
else if (value == "israel"sv) value = "jeruslm"sv;
else if (value == "jamaica"sv) value = "jmkin"sv;
else if (value == "japan"sv) value = "jptyo"sv;
else if (value == "kwajalein"sv) value = "mhkwa"sv;
else if (value == "libya"sv) value = "lytip"sv;
else if (value == "mst"sv) value = "utcw07"sv;
else if (value == "navajo"sv) value = "usden"sv;
else if (value == "poland"sv) value = "plwaw"sv;
else if (value == "portugal"sv) value = "ptlis"sv;
else if (value == "prc"sv) value = "cnsha"sv;
else if (value == "roc"sv) value = "twtpe"sv;
else if (value == "rok"sv) value = "krsel"sv;
else if (value == "singapore"sv) value = "sgsin"sv;
else if (value == "turkey"sv) value = "trist"sv;
else if (value == "uct"sv) value = "utc"sv;
else if (value == "usnavajo"sv) value = "usden"sv;
else if (value == "zulu"sv) value = "utc"sv;
// clang-format on
}
}
void canonicalize_unicode_extension_values(StringView key, String& value, bool remove_true)
{
value = value.to_lowercase();
perform_hard_coded_key_value_substitutions(key, value);
// Note: The spec says to remove "true" type and tfield values but that is believed to be a bug in the spec
// because, for tvalues, that would result in invalid syntax:
// https://unicode-org.atlassian.net/browse/CLDR-14318
// This has also been noted by test262:
// https://github.com/tc39/test262/blob/18bb955771669541c56c28748603f6afdb2e25ff/test/intl402/Intl/getCanonicalLocales/transformed-ext-canonical.js
if (remove_true && (value == "true"sv)) {
value = {};
return;
}
if (key.is_one_of("sd"sv, "rg"sv)) {
if (auto alias = resolve_subdivision_alias(value); alias.has_value()) {
auto aliases = alias->split_view(' ');
// FIXME: Subdivision subtags do not appear in the CLDR likelySubtags.json file.
// Implement the spec's recommendation of using just the first alias for now,
// but we should determine if there's anything else needed here.
value = aliases[0].to_string();
}
}
}
static void transform_unicode_locale_id_to_canonical_syntax(LocaleID& locale_id)
{
auto canonicalize_language = [&](LanguageID& language_id, bool force_lowercase) {
language_id.language = language_id.language->to_lowercase();
if (language_id.script.has_value())
language_id.script = language_id.script->to_titlecase();
if (language_id.region.has_value())
language_id.region = language_id.region->to_uppercase();
for (auto& variant : language_id.variants)
variant = variant.to_lowercase();
resolve_complex_language_aliases(language_id);
if (auto alias = resolve_language_alias(*language_id.language); alias.has_value()) {
auto language_alias = parse_unicode_language_id(*alias);
VERIFY(language_alias.has_value());
language_id.language = move(language_alias->language);
if (!language_id.script.has_value() && language_alias->script.has_value())
language_id.script = move(language_alias->script);
if (!language_id.region.has_value() && language_alias->region.has_value())
language_id.region = move(language_alias->region);
if (language_id.variants.is_empty() && !language_alias->variants.is_empty())
language_id.variants = move(language_alias->variants);
}
if (language_id.script.has_value()) {
if (auto alias = resolve_script_tag_alias(*language_id.script); alias.has_value())
language_id.script = move(*alias);
}
if (language_id.region.has_value()) {
if (auto alias = resolve_territory_alias(*language_id.region); alias.has_value())
language_id.region = resolve_most_likely_territory_alias(language_id, *alias);
}
quick_sort(language_id.variants);
for (auto& variant : language_id.variants) {
variant = variant.to_lowercase();
if (auto alias = resolve_variant_alias(variant); alias.has_value())
variant = move(*alias);
}
if (force_lowercase) {
if (language_id.script.has_value())
language_id.script = language_id.script->to_lowercase();
if (language_id.region.has_value())
language_id.region = language_id.region->to_lowercase();
}
};
canonicalize_language(locale_id.language_id, false);
quick_sort(locale_id.extensions, [](auto const& left, auto const& right) {
auto key = [](auto const& extension) {
return extension.visit(
[](LocaleExtension const&) { return 'u'; },
[](TransformedExtension const&) { return 't'; },
[](OtherExtension const& ext) { return static_cast<char>(to_ascii_lowercase(ext.key)); });
};
return key(left) < key(right);
});
for (auto& extension : locale_id.extensions) {
extension.visit(
[&](LocaleExtension& ext) {
for (auto& attribute : ext.attributes)
attribute = attribute.to_lowercase();
for (auto& keyword : ext.keywords) {
keyword.key = keyword.key.to_lowercase();
canonicalize_unicode_extension_values(keyword.key, keyword.value, true);
}
quick_sort(ext.attributes);
quick_sort(ext.keywords, [](auto const& a, auto const& b) { return a.key < b.key; });
},
[&](TransformedExtension& ext) {
if (ext.language.has_value())
canonicalize_language(*ext.language, true);
for (auto& field : ext.fields) {
field.key = field.key.to_lowercase();
canonicalize_unicode_extension_values(field.key, field.value, false);
}
quick_sort(ext.fields, [](auto const& a, auto const& b) { return a.key < b.key; });
},
[&](OtherExtension& ext) {
ext.key = static_cast<char>(to_ascii_lowercase(ext.key));
ext.value = ext.value.to_lowercase();
});
}
for (auto& extension : locale_id.private_use_extensions)
extension = extension.to_lowercase();
}
Optional<String> canonicalize_unicode_locale_id(LocaleID& locale_id)
{
// https://unicode.org/reports/tr35/#Canonical_Unicode_Locale_Identifiers
StringBuilder builder;
auto append_sep_and_string = [&](Optional<String> const& string) {
if (!string.has_value() || string->is_empty())
return;
builder.appendff("-{}", *string);
};
if (!locale_id.language_id.language.has_value())
return {};
transform_unicode_locale_id_to_canonical_syntax(locale_id);
builder.append(locale_id.language_id.language->to_lowercase());
append_sep_and_string(locale_id.language_id.script);
append_sep_and_string(locale_id.language_id.region);
for (auto const& variant : locale_id.language_id.variants)
append_sep_and_string(variant);
for (auto const& extension : locale_id.extensions) {
extension.visit(
[&](LocaleExtension const& ext) {
builder.append("-u"sv);
for (auto const& attribute : ext.attributes)
append_sep_and_string(attribute);
for (auto const& keyword : ext.keywords) {
append_sep_and_string(keyword.key);
append_sep_and_string(keyword.value);
}
},
[&](TransformedExtension const& ext) {
builder.append("-t"sv);
if (ext.language.has_value()) {
append_sep_and_string(ext.language->language);
append_sep_and_string(ext.language->script);
append_sep_and_string(ext.language->region);
for (auto const& variant : ext.language->variants)
append_sep_and_string(variant);
}
for (auto const& field : ext.fields) {
append_sep_and_string(field.key);
append_sep_and_string(field.value);
}
},
[&](OtherExtension const& ext) {
builder.appendff("-{:c}", to_ascii_lowercase(ext.key));
append_sep_and_string(ext.value);
});
}
if (!locale_id.private_use_extensions.is_empty()) {
builder.append("-x"sv);
for (auto const& extension : locale_id.private_use_extensions)
append_sep_and_string(extension);
}
return builder.build();
}
String const& default_locale()
{
static String locale = "en"sv;
return locale;
}
bool is_locale_available(StringView locale)
{
return locale_from_string(locale).has_value();
}
Style style_from_string(StringView style)
{
if (style == "narrow"sv)
return Style::Narrow;
if (style == "short"sv)
return Style::Short;
if (style == "long"sv)
return Style::Long;
VERIFY_NOT_REACHED();
}
StringView style_to_string(Style style)
{
switch (style) {
case Style::Narrow:
return "narrow"sv;
case Style::Short:
return "short"sv;
case Style::Long:
return "long"sv;
default:
VERIFY_NOT_REACHED();
}
}
Span<StringView const> __attribute__((weak)) get_available_keyword_values(StringView) { return {}; }
Span<StringView const> __attribute__((weak)) get_available_calendars() { return {}; }
Span<StringView const> __attribute__((weak)) get_available_collation_case_orderings() { return {}; }
Span<StringView const> __attribute__((weak)) get_available_collation_numeric_orderings() { return {}; }
Span<StringView const> __attribute__((weak)) get_available_collation_types() { return {}; }
Span<StringView const> __attribute__((weak)) get_available_currencies() { return {}; }
Span<StringView const> __attribute__((weak)) get_available_hour_cycles() { return {}; }
Span<StringView const> __attribute__((weak)) get_available_number_systems() { return {}; }
Optional<Locale> __attribute__((weak)) locale_from_string(StringView) { return {}; }
Optional<Language> __attribute__((weak)) language_from_string(StringView) { return {}; }
Optional<Territory> __attribute__((weak)) territory_from_string(StringView) { return {}; }
Optional<ScriptTag> __attribute__((weak)) script_tag_from_string(StringView) { return {}; }
Optional<Currency> __attribute__((weak)) currency_from_string(StringView) { return {}; }
Optional<DateField> __attribute__((weak)) date_field_from_string(StringView) { return {}; }
Optional<ListPatternType> __attribute__((weak)) list_pattern_type_from_string(StringView) { return {}; }
Optional<Key> __attribute__((weak)) key_from_string(StringView) { return {}; }
Optional<KeywordCalendar> __attribute__((weak)) keyword_ca_from_string(StringView) { return {}; }
Optional<KeywordCollation> __attribute__((weak)) keyword_co_from_string(StringView) { return {}; }
Optional<KeywordHours> __attribute__((weak)) keyword_hc_from_string(StringView) { return {}; }
Optional<KeywordColCaseFirst> __attribute__((weak)) keyword_kf_from_string(StringView) { return {}; }
Optional<KeywordColNumeric> __attribute__((weak)) keyword_kn_from_string(StringView) { return {}; }
Optional<KeywordNumbers> __attribute__((weak)) keyword_nu_from_string(StringView) { return {}; }
Vector<StringView> __attribute__((weak)) get_keywords_for_locale(StringView, StringView) { return {}; }
Optional<StringView> __attribute__((weak)) get_preferred_keyword_value_for_locale(StringView, StringView) { return {}; }
Optional<DisplayPattern> __attribute__((weak)) get_locale_display_patterns(StringView) { return {}; }
Optional<StringView> __attribute__((weak)) get_locale_language_mapping(StringView, StringView) { return {}; }
Optional<StringView> __attribute__((weak)) get_locale_territory_mapping(StringView, StringView) { return {}; }
Optional<StringView> __attribute__((weak)) get_locale_script_mapping(StringView, StringView) { return {}; }
Optional<StringView> __attribute__((weak)) get_locale_long_currency_mapping(StringView, StringView) { return {}; }
Optional<StringView> __attribute__((weak)) get_locale_short_currency_mapping(StringView, StringView) { return {}; }
Optional<StringView> __attribute__((weak)) get_locale_narrow_currency_mapping(StringView, StringView) { return {}; }
Optional<StringView> __attribute__((weak)) get_locale_numeric_currency_mapping(StringView, StringView) { return {}; }
Optional<StringView> __attribute__((weak)) get_locale_calendar_mapping(StringView, StringView) { return {}; }
Optional<StringView> __attribute__((weak)) get_locale_long_date_field_mapping(StringView, StringView) { return {}; }
Optional<StringView> __attribute__((weak)) get_locale_short_date_field_mapping(StringView, StringView) { return {}; }
Optional<StringView> __attribute__((weak)) get_locale_narrow_date_field_mapping(StringView, StringView) { return {}; }
// https://www.unicode.org/reports/tr35/tr35-39/tr35-general.html#Display_Name_Elements
Optional<String> format_locale_for_display(StringView locale, LocaleID locale_id)
{
auto language_id = move(locale_id.language_id);
VERIFY(language_id.language.has_value());
auto patterns = get_locale_display_patterns(locale);
if (!patterns.has_value())
return {};
auto primary_tag = get_locale_language_mapping(locale, *language_id.language).value_or(*language_id.language);
Optional<StringView> script;
Optional<StringView> region;
if (language_id.script.has_value())
script = get_locale_script_mapping(locale, *language_id.script).value_or(*language_id.script);
if (language_id.region.has_value())
region = get_locale_territory_mapping(locale, *language_id.region).value_or(*language_id.region);
Optional<String> secondary_tag;
if (script.has_value() && region.has_value())
secondary_tag = patterns->locale_separator.replace("{0}"sv, *script, ReplaceMode::FirstOnly).replace("{1}"sv, *region, ReplaceMode::FirstOnly);
else if (script.has_value())
secondary_tag = *script;
else if (region.has_value())
secondary_tag = *region;
if (!secondary_tag.has_value())
return primary_tag;
return patterns->locale_pattern.replace("{0}"sv, primary_tag, ReplaceMode::FirstOnly).replace("{1}"sv, *secondary_tag, ReplaceMode::FirstOnly);
}
Optional<ListPatterns> __attribute__((weak)) get_locale_list_patterns(StringView, StringView, Style) { return {}; }
Optional<CharacterOrder> __attribute__((weak)) character_order_from_string(StringView) { return {}; }
StringView __attribute__((weak)) character_order_to_string(CharacterOrder) { return {}; }
Optional<CharacterOrder> __attribute__((weak)) character_order_for_locale(StringView) { return {}; }
Optional<StringView> __attribute__((weak)) resolve_language_alias(StringView) { return {}; }
Optional<StringView> __attribute__((weak)) resolve_territory_alias(StringView) { return {}; }
Optional<StringView> __attribute__((weak)) resolve_script_tag_alias(StringView) { return {}; }
Optional<StringView> __attribute__((weak)) resolve_variant_alias(StringView) { return {}; }
Optional<StringView> __attribute__((weak)) resolve_subdivision_alias(StringView) { return {}; }
void __attribute__((weak)) resolve_complex_language_aliases(LanguageID&) { }
Optional<LanguageID> __attribute__((weak)) add_likely_subtags(LanguageID const&) { return {}; }
Optional<LanguageID> remove_likely_subtags(LanguageID const& language_id)
{
// https://www.unicode.org/reports/tr35/#Likely_Subtags
auto return_language_and_variants = [](auto language, auto variants) {
language.variants = move(variants);
return language;
};
// 1. First get max = AddLikelySubtags(inputLocale). If an error is signaled, return it.
auto maximized = add_likely_subtags(language_id);
if (!maximized.has_value())
return {};
// 2. Remove the variants from max.
auto variants = move(maximized->variants);
// 3. Get the components of the max (languagemax, scriptmax, regionmax).
auto language_max = maximized->language;
auto script_max = maximized->script;
auto region_max = maximized->region;
// 4. Then for trial in {languagemax, languagemax_regionmax, languagemax_scriptmax}:
// If AddLikelySubtags(trial) = max, then return trial + variants.
auto run_trial = [&](Optional<String> language, Optional<String> script, Optional<String> region) -> Optional<LanguageID> {
LanguageID trial { .language = move(language), .script = move(script), .region = move(region) };
if (add_likely_subtags(trial) == maximized)
return return_language_and_variants(move(trial), move(variants));
return {};
};
if (auto trial = run_trial(language_max, {}, {}); trial.has_value())
return trial;
if (auto trial = run_trial(language_max, {}, region_max); trial.has_value())
return trial;
if (auto trial = run_trial(language_max, script_max, {}); trial.has_value())
return trial;
// 5. If you do not get a match, return max + variants.
return return_language_and_variants(maximized.release_value(), move(variants));
}
Optional<String> __attribute__((weak)) resolve_most_likely_territory(LanguageID const&) { return {}; }
String resolve_most_likely_territory_alias(LanguageID const& language_id, StringView territory_alias)
{
auto aliases = territory_alias.split_view(' ');
if (aliases.size() > 1) {
auto territory = resolve_most_likely_territory(language_id);
if (territory.has_value() && aliases.contains_slow(*territory))
return territory.release_value();
}
return aliases[0].to_string();
}
String LanguageID::to_string() const
{
StringBuilder builder;
auto append_segment = [&](Optional<String> const& segment) {
if (!segment.has_value())
return;
if (!builder.is_empty())
builder.append('-');
builder.append(*segment);
};
append_segment(language);
append_segment(script);
append_segment(region);
for (auto const& variant : variants)
append_segment(variant);
return builder.build();
}
String LocaleID::to_string() const
{
StringBuilder builder;
auto append_segment = [&](Optional<String> const& segment) {
if (!segment.has_value() || segment->is_empty())
return;
if (!builder.is_empty())
builder.append('-');
builder.append(*segment);
};
append_segment(language_id.to_string());
for (auto const& extension : extensions) {
extension.visit(
[&](LocaleExtension const& ext) {
builder.append("-u"sv);
for (auto const& attribute : ext.attributes)
append_segment(attribute);
for (auto const& keyword : ext.keywords) {
append_segment(keyword.key);
append_segment(keyword.value);
}
},
[&](TransformedExtension const& ext) {
builder.append("-t"sv);
if (ext.language.has_value())
append_segment(ext.language->to_string());
for (auto const& field : ext.fields) {
append_segment(field.key);
append_segment(field.value);
}
},
[&](OtherExtension const& ext) {
builder.appendff("-{}", ext.key);
append_segment(ext.value);
});
}
if (!private_use_extensions.is_empty()) {
builder.append("-x"sv);
for (auto const& extension : private_use_extensions)
append_segment(extension);
}
return builder.build();
}
}

View file

@ -1,212 +0,0 @@
/*
* Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/CharacterTypes.h>
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibUnicode/Forward.h>
namespace Locale {
struct LanguageID {
String to_string() const;
bool operator==(LanguageID const&) const = default;
bool is_root { false };
Optional<String> language {};
Optional<String> script {};
Optional<String> region {};
Vector<String> variants {};
};
struct Keyword {
String key {};
String value {};
};
struct LocaleExtension {
Vector<String> attributes {};
Vector<Keyword> keywords {};
};
struct TransformedField {
String key {};
String value {};
};
struct TransformedExtension {
Optional<LanguageID> language {};
Vector<TransformedField> fields {};
};
struct OtherExtension {
char key {};
String value {};
};
using Extension = AK::Variant<LocaleExtension, TransformedExtension, OtherExtension>;
struct LocaleID {
String to_string() const;
template<typename ExtensionType>
Vector<Extension> remove_extension_type()
{
Vector<Extension> removed_extensions {};
auto tmp_extensions = move(extensions);
for (auto& extension : tmp_extensions) {
if (extension.has<ExtensionType>())
removed_extensions.append(move(extension));
else
extensions.append(move(extension));
}
return removed_extensions;
}
LanguageID language_id {};
Vector<Extension> extensions {};
Vector<String> private_use_extensions {};
};
enum class Style : u8 {
Long,
Short,
Narrow,
};
struct DisplayPattern {
StringView locale_pattern;
StringView locale_separator;
};
struct ListPatterns {
StringView start;
StringView middle;
StringView end;
StringView pair;
};
// Note: These methods only verify that the provided strings match the EBNF grammar of the
// Unicode identifier subtag (i.e. no validation is done that the tags actually exist).
constexpr bool is_unicode_language_subtag(StringView subtag)
{
// unicode_language_subtag = alpha{2,3} | alpha{5,8}
if ((subtag.length() < 2) || (subtag.length() == 4) || (subtag.length() > 8))
return false;
return all_of(subtag, is_ascii_alpha);
}
constexpr bool is_unicode_script_subtag(StringView subtag)
{
// unicode_script_subtag = alpha{4}
if (subtag.length() != 4)
return false;
return all_of(subtag, is_ascii_alpha);
}
constexpr bool is_unicode_region_subtag(StringView subtag)
{
// unicode_region_subtag = (alpha{2} | digit{3})
if (subtag.length() == 2)
return all_of(subtag, is_ascii_alpha);
if (subtag.length() == 3)
return all_of(subtag, is_ascii_digit);
return false;
}
constexpr bool is_unicode_variant_subtag(StringView subtag)
{
// unicode_variant_subtag = (alphanum{5,8} | digit alphanum{3})
if ((subtag.length() >= 5) && (subtag.length() <= 8))
return all_of(subtag, is_ascii_alphanumeric);
if (subtag.length() == 4)
return is_ascii_digit(subtag[0]) && all_of(subtag.substring_view(1), is_ascii_alphanumeric);
return false;
}
bool is_type_identifier(StringView);
Optional<LanguageID> parse_unicode_language_id(StringView);
Optional<LocaleID> parse_unicode_locale_id(StringView);
void canonicalize_unicode_extension_values(StringView key, String& value, bool remove_true);
Optional<String> canonicalize_unicode_locale_id(LocaleID&);
String const& default_locale();
bool is_locale_available(StringView locale);
Span<StringView const> get_available_keyword_values(StringView key);
Span<StringView const> get_available_calendars();
Span<StringView const> get_available_collation_case_orderings();
Span<StringView const> get_available_collation_numeric_orderings();
Span<StringView const> get_available_collation_types();
Span<StringView const> get_available_currencies();
Span<StringView const> get_available_hour_cycles();
Span<StringView const> get_available_number_systems();
Style style_from_string(StringView style);
StringView style_to_string(Style style);
Optional<Locale> locale_from_string(StringView locale);
Optional<Language> language_from_string(StringView language);
Optional<Territory> territory_from_string(StringView territory);
Optional<ScriptTag> script_tag_from_string(StringView script_tag);
Optional<Currency> currency_from_string(StringView currency);
Optional<DateField> date_field_from_string(StringView calendar);
Optional<ListPatternType> list_pattern_type_from_string(StringView list_pattern_type);
Optional<Key> key_from_string(StringView key);
Optional<KeywordCalendar> keyword_ca_from_string(StringView ca);
Optional<KeywordCollation> keyword_co_from_string(StringView co);
Optional<KeywordHours> keyword_hc_from_string(StringView hc);
Optional<KeywordColCaseFirst> keyword_kf_from_string(StringView kf);
Optional<KeywordColNumeric> keyword_kn_from_string(StringView kn);
Optional<KeywordNumbers> keyword_nu_from_string(StringView nu);
Vector<StringView> get_keywords_for_locale(StringView locale, StringView key);
Optional<StringView> get_preferred_keyword_value_for_locale(StringView locale, StringView key);
Optional<DisplayPattern> get_locale_display_patterns(StringView locale);
Optional<String> format_locale_for_display(StringView locale, LocaleID locale_id);
Optional<StringView> get_locale_language_mapping(StringView locale, StringView language);
Optional<StringView> get_locale_territory_mapping(StringView locale, StringView territory);
Optional<StringView> get_locale_script_mapping(StringView locale, StringView script);
Optional<StringView> get_locale_long_currency_mapping(StringView locale, StringView currency);
Optional<StringView> get_locale_short_currency_mapping(StringView locale, StringView currency);
Optional<StringView> get_locale_narrow_currency_mapping(StringView locale, StringView currency);
Optional<StringView> get_locale_numeric_currency_mapping(StringView locale, StringView currency);
Optional<StringView> get_locale_calendar_mapping(StringView locale, StringView calendar);
Optional<StringView> get_locale_long_date_field_mapping(StringView locale, StringView date_field);
Optional<StringView> get_locale_short_date_field_mapping(StringView locale, StringView date_field);
Optional<StringView> get_locale_narrow_date_field_mapping(StringView locale, StringView date_field);
Optional<ListPatterns> get_locale_list_patterns(StringView locale, StringView type, Style style);
Optional<CharacterOrder> character_order_from_string(StringView character_order);
StringView character_order_to_string(CharacterOrder character_order);
Optional<CharacterOrder> character_order_for_locale(StringView locale);
Optional<StringView> resolve_language_alias(StringView language);
Optional<StringView> resolve_territory_alias(StringView territory);
Optional<StringView> resolve_script_tag_alias(StringView script_tag);
Optional<StringView> resolve_variant_alias(StringView variant);
Optional<StringView> resolve_subdivision_alias(StringView subdivision);
void resolve_complex_language_aliases(LanguageID& language_id);
Optional<LanguageID> add_likely_subtags(LanguageID const& language_id);
Optional<LanguageID> remove_likely_subtags(LanguageID const& language_id);
Optional<String> resolve_most_likely_territory(LanguageID const& language_id);
String resolve_most_likely_territory_alias(LanguageID const& language_id, StringView territory_alias);
}

View file

@ -1,143 +0,0 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/CharacterTypes.h>
#include <AK/Utf8View.h>
#include <LibUnicode/CharacterTypes.h>
#include <LibUnicode/Locale.h>
#include <LibUnicode/NumberFormat.h>
#if ENABLE_UNICODE_DATA
# include <LibUnicode/UnicodeData.h>
#endif
namespace Locale {
Optional<StringView> __attribute__((weak)) get_number_system_symbol(StringView, StringView, NumericSymbol) { return {}; }
Optional<NumberGroupings> __attribute__((weak)) get_number_system_groupings(StringView, StringView) { return {}; }
Optional<NumberFormat> __attribute__((weak)) get_standard_number_system_format(StringView, StringView, StandardNumberFormatType) { return {}; }
Vector<NumberFormat> __attribute__((weak)) get_compact_number_system_formats(StringView, StringView, CompactNumberFormatType) { return {}; }
Vector<NumberFormat> __attribute__((weak)) get_unit_formats(StringView, StringView, Style) { return {}; }
Optional<Span<u32 const>> __attribute__((weak)) get_digits_for_number_system(StringView)
{
// Fall back to "latn" digits when Unicode data generation is disabled.
constexpr Array<u32, 10> digits { { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39 } };
return digits.span();
}
String replace_digits_for_number_system(StringView system, StringView number)
{
auto digits = get_digits_for_number_system(system);
if (!digits.has_value())
digits = get_digits_for_number_system("latn"sv);
VERIFY(digits.has_value());
StringBuilder builder;
for (auto ch : number) {
if (is_ascii_digit(ch)) {
u32 digit = digits->at(parse_ascii_digit(ch));
builder.append_code_point(digit);
} else {
builder.append(ch);
}
}
return builder.build();
}
#if ENABLE_UNICODE_DATA
static u32 last_code_point(StringView string)
{
Utf8View utf8_string { string };
u32 code_point = 0;
for (auto it = utf8_string.begin(); it != utf8_string.end(); ++it)
code_point = *it;
return code_point;
}
#endif
// https://www.unicode.org/reports/tr35/tr35-numbers.html#Currencies
Optional<String> augment_currency_format_pattern([[maybe_unused]] StringView currency_display, [[maybe_unused]] StringView base_pattern)
{
#if ENABLE_UNICODE_DATA
constexpr auto number_key = "{number}"sv;
constexpr auto currency_key = "{currency}"sv;
constexpr auto spacing = "\u00A0"sv; // No-Break Space (NBSP)
auto number_index = base_pattern.find(number_key);
VERIFY(number_index.has_value());
auto currency_index = base_pattern.find(currency_key);
VERIFY(currency_index.has_value());
Utf8View utf8_currency_display { currency_display };
Optional<String> currency_key_with_spacing;
if (*number_index < *currency_index) {
u32 last_pattern_code_point = last_code_point(base_pattern.substring_view(0, *currency_index));
if (!Unicode::code_point_has_general_category(last_pattern_code_point, Unicode::GeneralCategory::Separator)) {
u32 first_currency_code_point = *utf8_currency_display.begin();
if (!Unicode::code_point_has_general_category(first_currency_code_point, Unicode::GeneralCategory::Symbol))
currency_key_with_spacing = String::formatted("{}{}", spacing, currency_key);
}
} else {
u32 last_pattern_code_point = last_code_point(base_pattern.substring_view(0, *number_index));
if (!Unicode::code_point_has_general_category(last_pattern_code_point, Unicode::GeneralCategory::Separator)) {
u32 last_currency_code_point = last_code_point(currency_display);
if (!Unicode::code_point_has_general_category(last_currency_code_point, Unicode::GeneralCategory::Symbol))
currency_key_with_spacing = String::formatted("{}{}", currency_key, spacing);
}
}
if (currency_key_with_spacing.has_value())
return base_pattern.replace(currency_key, *currency_key_with_spacing, ReplaceMode::FirstOnly);
#endif
return {};
}
// https://unicode.org/reports/tr35/tr35-numbers.html#83-range-pattern-processing
Optional<String> augment_range_pattern([[maybe_unused]] StringView range_separator, [[maybe_unused]] StringView lower, [[maybe_unused]] StringView upper)
{
#if ENABLE_UNICODE_DATA
auto range_pattern_with_spacing = [&]() {
return String::formatted(" {} ", range_separator);
};
Utf8View utf8_range_separator { range_separator };
Utf8View utf8_upper { upper };
// NOTE: Our implementation does the prescribed checks backwards for simplicity.
// To determine whether to add spacing, the currently recommended heuristic is:
// 2. If the range pattern does not contain a character having the White_Space binary Unicode property after the {0} or before the {1} placeholders.
for (auto it = utf8_range_separator.begin(); it != utf8_range_separator.end(); ++it) {
if (Unicode::code_point_has_property(*it, Unicode::Property::White_Space))
return {};
}
// 1. If the lower string ends with a character other than a digit, or if the upper string begins with a character other than a digit.
if (auto it = utf8_upper.begin(); it != utf8_upper.end()) {
if (!Unicode::code_point_has_general_category(*it, Unicode::GeneralCategory::Decimal_Number))
return range_pattern_with_spacing();
}
if (!Unicode::code_point_has_general_category(last_code_point(lower), Unicode::GeneralCategory::Decimal_Number))
return range_pattern_with_spacing();
#endif
return {};
}
}

View file

@ -1,76 +0,0 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Vector.h>
#include <LibUnicode/Forward.h>
#include <LibUnicode/PluralRules.h>
namespace Locale {
struct NumberGroupings {
u8 minimum_grouping_digits { 0 };
u8 primary_grouping_size { 0 };
u8 secondary_grouping_size { 0 };
};
enum class StandardNumberFormatType : u8 {
Decimal,
Currency,
Accounting,
Percent,
Scientific,
};
enum class CompactNumberFormatType : u8 {
DecimalLong,
DecimalShort,
CurrencyUnit,
CurrencyShort,
};
struct NumberFormat {
u8 magnitude { 0 };
u8 exponent { 0 };
PluralCategory plurality { PluralCategory::Other };
StringView zero_format {};
StringView positive_format {};
StringView negative_format {};
Vector<StringView> identifiers {};
};
enum class NumericSymbol : u8 {
ApproximatelySign,
Decimal,
Exponential,
Group,
Infinity,
MinusSign,
NaN,
PercentSign,
PlusSign,
RangeSeparator,
TimeSeparator,
};
Optional<StringView> get_number_system_symbol(StringView locale, StringView system, NumericSymbol symbol);
Optional<NumberGroupings> get_number_system_groupings(StringView locale, StringView system);
Optional<Span<u32 const>> get_digits_for_number_system(StringView system);
String replace_digits_for_number_system(StringView system, StringView number);
Optional<NumberFormat> get_standard_number_system_format(StringView locale, StringView system, StandardNumberFormatType type);
Vector<NumberFormat> get_compact_number_system_formats(StringView locale, StringView system, CompactNumberFormatType type);
Vector<NumberFormat> get_unit_formats(StringView locale, StringView unit, Style style);
Optional<String> augment_currency_format_pattern(StringView currency_display, StringView base_pattern);
Optional<String> augment_range_pattern(StringView range_separator, StringView lower, StringView upper);
}

View file

@ -1,48 +0,0 @@
/*
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibUnicode/PluralRules.h>
namespace Locale {
PluralForm plural_form_from_string(StringView plural_form)
{
if (plural_form == "cardinal"sv)
return PluralForm::Cardinal;
if (plural_form == "ordinal"sv)
return PluralForm::Ordinal;
VERIFY_NOT_REACHED();
}
StringView plural_form_to_string(PluralForm plural_form)
{
switch (plural_form) {
case PluralForm::Cardinal:
return "cardinal"sv;
case PluralForm::Ordinal:
return "ordinal"sv;
default:
VERIFY_NOT_REACHED();
}
}
PluralCategory __attribute__((weak)) determine_plural_category(StringView, PluralForm, PluralOperands)
{
return PluralCategory::Other;
}
Span<PluralCategory const> __attribute__((weak)) available_plural_categories(StringView, PluralForm)
{
static constexpr Array<PluralCategory, 1> categories { { PluralCategory::Other } };
return categories.span();
}
PluralCategory __attribute__((weak)) determine_plural_range(StringView, PluralCategory, PluralCategory)
{
return PluralCategory::Other;
}
}

View file

@ -1,124 +0,0 @@
/*
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Assertions.h>
#include <AK/StringView.h>
#include <AK/Types.h>
#include <LibUnicode/Forward.h>
namespace Locale {
enum class PluralForm {
Cardinal,
Ordinal,
};
enum class PluralCategory : u8 {
Other,
Zero,
One,
Two,
Few,
Many,
// https://unicode.org/reports/tr35/tr35-numbers.html#Explicit_0_1_rules
ExactlyZero,
ExactlyOne,
};
// https://unicode.org/reports/tr35/tr35-numbers.html#Plural_Operand_Meanings
struct PluralOperands {
static constexpr StringView symbol_to_variable_name(char symbol)
{
if (symbol == 'n')
return "number"sv;
if (symbol == 'i')
return "integer_digits"sv;
if (symbol == 'f')
return "fraction_digits"sv;
if (symbol == 'v')
return "number_of_fraction_digits"sv;
if (symbol == 't')
return "fraction_digits_without_trailing"sv;
if (symbol == 'w')
return "number_of_fraction_digits_without_trailing"sv;
VERIFY_NOT_REACHED();
}
static constexpr bool symbol_requires_floating_point_modulus(char symbol)
{
// From TR-35: "The modulus (% or mod) is a remainder operation as defined in Java; for
// example, where n = 4.3 the result of n mod 3 is 1.3."
//
// So, this returns whether the symbol represents a decimal value, and thus requires fmod.
return symbol == 'n';
}
double number { 0 };
u64 integer_digits { 0 };
u64 fraction_digits { 0 };
u64 number_of_fraction_digits { 0 };
u64 fraction_digits_without_trailing { 0 };
u64 number_of_fraction_digits_without_trailing { 0 };
};
PluralForm plural_form_from_string(StringView plural_form);
StringView plural_form_to_string(PluralForm plural_form);
// NOTE: This must be defined inline to be callable from the code generators.
constexpr PluralCategory plural_category_from_string(StringView category)
{
if (category == "other"sv)
return PluralCategory::Other;
if (category == "zero"sv)
return PluralCategory::Zero;
if (category == "one"sv)
return PluralCategory::One;
if (category == "two"sv)
return PluralCategory::Two;
if (category == "few"sv)
return PluralCategory::Few;
if (category == "many"sv)
return PluralCategory::Many;
if (category == "0"sv)
return PluralCategory::ExactlyZero;
if (category == "1"sv)
return PluralCategory::ExactlyOne;
VERIFY_NOT_REACHED();
}
// NOTE: This must be defined inline to be callable from the code generators.
constexpr StringView plural_category_to_string(PluralCategory category)
{
switch (category) {
case PluralCategory::Other:
return "other"sv;
case PluralCategory::Zero:
return "zero"sv;
case PluralCategory::One:
return "one"sv;
case PluralCategory::Two:
return "two"sv;
case PluralCategory::Few:
return "few"sv;
case PluralCategory::Many:
return "many"sv;
case PluralCategory::ExactlyZero:
return "0"sv;
case PluralCategory::ExactlyOne:
return "1"sv;
}
VERIFY_NOT_REACHED();
}
PluralCategory determine_plural_category(StringView locale, PluralForm form, PluralOperands operands);
Span<PluralCategory const> available_plural_categories(StringView locale, PluralForm form);
PluralCategory determine_plural_range(StringView locale, PluralCategory start, PluralCategory end);
}

View file

@ -1,58 +0,0 @@
/*
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibUnicode/RelativeTimeFormat.h>
namespace Locale {
Optional<TimeUnit> time_unit_from_string(StringView time_unit)
{
if (time_unit == "second"sv)
return TimeUnit::Second;
if (time_unit == "minute"sv)
return TimeUnit::Minute;
if (time_unit == "hour"sv)
return TimeUnit::Hour;
if (time_unit == "day"sv)
return TimeUnit::Day;
if (time_unit == "week"sv)
return TimeUnit::Week;
if (time_unit == "month"sv)
return TimeUnit::Month;
if (time_unit == "quarter"sv)
return TimeUnit::Quarter;
if (time_unit == "year"sv)
return TimeUnit::Year;
return {};
}
StringView time_unit_to_string(TimeUnit time_unit)
{
switch (time_unit) {
case TimeUnit::Second:
return "second"sv;
case TimeUnit::Minute:
return "minute"sv;
case TimeUnit::Hour:
return "hour"sv;
case TimeUnit::Day:
return "day"sv;
case TimeUnit::Week:
return "week"sv;
case TimeUnit::Month:
return "month"sv;
case TimeUnit::Quarter:
return "quarter"sv;
case TimeUnit::Year:
return "year"sv;
default:
VERIFY_NOT_REACHED();
}
}
Vector<RelativeTimeFormat> __attribute__((weak)) get_relative_time_format_patterns(StringView, TimeUnit, StringView, Style) { return {}; }
}

View file

@ -1,39 +0,0 @@
/*
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Optional.h>
#include <AK/StringView.h>
#include <AK/Vector.h>
#include <LibUnicode/Forward.h>
#include <LibUnicode/Locale.h>
namespace Locale {
// These are just the subset of fields in the CLDR required for ECMA-402.
enum class TimeUnit {
Second,
Minute,
Hour,
Day,
Week,
Month,
Quarter,
Year,
};
struct RelativeTimeFormat {
PluralCategory plurality;
StringView pattern;
};
Optional<TimeUnit> time_unit_from_string(StringView time_unit);
StringView time_unit_to_string(TimeUnit time_unit);
Vector<RelativeTimeFormat> get_relative_time_format_patterns(StringView locale, TimeUnit time_unit, StringView tense_or_number, Style style);
}