From 52b3cd46c76fd4be18aeb8422a08a073484b9fad Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Sat, 29 Aug 2026 12:01:42 +0200 Subject: More module implementation partition units --- server/src/locale.cppm | 227 +++---------------------------------------------- 1 file changed, 11 insertions(+), 216 deletions(-) (limited to 'server/src/locale.cppm') diff --git a/server/src/locale.cppm b/server/src/locale.cppm index da80f76..263de1d 100644 --- a/server/src/locale.cppm +++ b/server/src/locale.cppm @@ -11,78 +11,20 @@ import :util; export namespace blocale = boost::locale; export namespace routemon { + using lformat = blocale::format; using blocale::gettext; using blocale::translate; + } // namespace routemon namespace routemon::locale { -struct locale_priority -{ - float weight; - std::size_t original_index; -}; - -auto operator<(locale_priority const& lhs, locale_priority const& rhs) -> bool -{ - if (lhs.weight != rhs.weight) - return lhs.weight > rhs.weight; - return lhs.original_index < rhs.original_index; -} - -struct icu_locale_hash -{ - std::size_t operator()(icu::Locale const& l) const noexcept - { - static_assert(sizeof(std::int32_t) < sizeof(std::size_t)); - std::int32_t hash = l.hashCode(); - if (hash < 0) - { - return static_cast(std::numeric_limits::max()) - + static_cast(-hash) + 1; - } - else - { - return static_cast(hash); - } - } -}; - -using icu_locale_priority_map = - std::unordered_map; -using icu_priority_locale = std::pair; - -auto operator<(icu_priority_locale const& lhs, icu_priority_locale const& rhs) - -> bool -{ - return lhs.second < rhs.second; -} - -class icu_priority_locale_vec_iterator : public icu::Locale::Iterator -{ - std::size_t i_ = 0uz; - std::vector ls_; - -public: - explicit icu_priority_locale_vec_iterator( - std::vector&& ls) - : ls_(std::move(ls)) - { - } - - auto hasNext() const -> UBool override { return i_ < ls_.size(); } - - auto next() -> icu::Locale const& override { return ls_[i_++].first; } - - ~icu_priority_locale_vec_iterator() override = default; -}; - export template concept locale_input_range = std::ranges::input_range - && std::same_as< - std::locale const&, std::ranges::range_const_reference_t>; + && std:: + same_as>; // Helps select a locale based on the Accept-Language header in an // HTTP request. @@ -122,75 +64,11 @@ export class selector } // Trimming optional whitespace as defined in RFC 9110, ยง 12.4.2. - static auto ltrim_ows(std::string_view s) -> std::string_view - { - if (auto i = s.find_first_not_of(" \t"); i != std::string_view::npos) - s.remove_prefix(i); - return s; - } - static auto rtrim_ows(std::string_view s) -> std::string_view - { - if (auto i = s.find_last_not_of(" \t"); i != std::string_view::npos) - return s.substr(0, i + 1); - return s; - } - static auto trim_ows(std::string_view s) -> std::string_view - { - return rtrim_ows(ltrim_ows(s)); - } + static auto ltrim_ows(std::string_view s) -> std::string_view; + static auto rtrim_ows(std::string_view s) -> std::string_view; + static auto trim_ows(std::string_view s) -> std::string_view; - auto from_icu_locale(icu::Locale const& l) const -> std::locale - { - auto posix_name = std::string{l.getLanguage()}; - if (l.getScript() && std::strlen(l.getScript()) > 0) - { - posix_name += "_"; - posix_name += l.getScript(); - } - if (l.getCountry() && std::strlen(l.getCountry()) > 0) - { - posix_name += "_"; - posix_name += l.getCountry(); - } - posix_name += ".UTF-8"; - auto added_at = false; - if (l.getVariant() && std::strlen(l.getVariant()) > 0) - { - added_at = true; - posix_name += "@"; - posix_name += l.getVariant(); - } - auto ec = UErrorCode::U_ZERO_ERROR; - auto* keywords = l.createKeywords(ec); - if (U_FAILURE(ec)) - throw std::runtime_error{"failed to create keywords"}; - if (keywords) - { - std::int32_t kw_len = 0; - char const* kw = nullptr; - while (kw = keywords->next(&kw_len, ec), !U_FAILURE(ec) && kw) - { - auto value = - l.getKeywordValue(icu::StringPiece(kw, kw_len), ec); - if (!added_at) - { - posix_name += "@"; - added_at = true; - } - else - { - posix_name += ";"; - } - posix_name += kw; - posix_name += "="; - posix_name += value; - } - if (U_FAILURE(ec)) - throw std::runtime_error{"failed to iterate over keywords"}; - delete keywords; - } - return lgen_->generate(posix_name); - } + auto from_icu_locale(icu::Locale const& l) const -> std::locale; public: // Note: lgen must live at least as long as the selector constructed here! @@ -202,94 +80,11 @@ public: { } - auto select(std::string_view accept_language) const -> std::locale - { - using namespace std::literals::string_view_literals; - // NOTE: can also contain a *;q=0.1 - // q should have at most 3 digits after period - auto dlpm = icu_locale_priority_map{}; - for (auto const [i, lang_prio] : - accept_language | std::views::split(","sv) | std::views::enumerate) - { - auto [lang_range_ut, mweight_ut] = - util::split_on(std::string_view{lang_prio}, ';'); - auto lang_range_str = trim_ows(lang_range_ut); - auto mweight_str = mweight_ut.transform(trim_ows); - if (lang_range_str == "*") - break; - - auto ec = UErrorCode::U_ZERO_ERROR; - auto icu_locale = icu::Locale::forLanguageTag(lang_range_str, ec); - if (U_FAILURE(ec) || icu_locale.isBogus()) - continue; // ignore this locale - - auto weight = 1.0f; - if (mweight_str && mweight_str->starts_with("q=")) - { - auto weight_str = mweight_str->substr(2, 4); - if (auto mweight = - util::parse_float(weight_str, std::chars_format::fixed); - mweight && 0.0f < *mweight && *mweight < 1.0f) - { - weight = *mweight; - } - } - - if (weight > 0.0f) - { - dlpm[icu_locale] = { - .weight = weight, - .original_index = static_cast(i), - }; - } - else - { - dlpm.erase(icu_locale); - } - } - - auto desired_locales = - std::vector{dlpm.begin(), dlpm.end()}; - std::sort(desired_locales.begin(), desired_locales.end()); - auto it = icu_priority_locale_vec_iterator{std::move(desired_locales)}; - auto ec = UErrorCode::U_ZERO_ERROR; - auto res = matcher_.getBestMatchResult(it, ec); - if (U_FAILURE(ec)) - return default_; - auto resolved = res.makeResolvedLocale(ec); // TODO: maybe don't? - if (U_FAILURE(ec)) - return from_icu_locale(*res.getSupportedLocale()); - return from_icu_locale(resolved); - } + auto select(std::string_view accept_language) const -> std::locale; }; -export auto to_bcp47_lang_tag(std::locale locale) -> std::optional -{ - auto const& locale_info = std::use_facet(locale); - auto ec = UErrorCode::U_ZERO_ERROR; - auto bcp47_lang_tag = - icu::Locale{locale_info.name().c_str()}.toLanguageTag(ec); - if (U_FAILURE(ec)) - return std::nullopt; - return bcp47_lang_tag; -} - -#ifdef LOCALEDIR -#define LOCALEDIR_AUX_XSTR(s) LOCALEDIR_AUX_STR(s) -#define LOCALEDIR_AUX_STR(s) #s -constexpr auto messages_path = std::string_view{LOCALEDIR_AUX_XSTR(LOCALEDIR)}; -#undef LOCALEDIR_AUX_STR -#undef LOCALEDIR_AUX_XSTR -#else // ifdef LOCALEDIR -constexpr auto messages_path = std::string_view{"locale/dev"}; -#endif // ifdef LOCALEDIR +auto to_bcp47_lang_tag(std::locale locale) -> std::optional; -export auto make_generator() -> std::shared_ptr -{ - auto lgen = std::make_shared(); - lgen->add_messages_path(std::string{messages_path}); - lgen->add_messages_domain("routemon"); - return std::static_pointer_cast(lgen); -} +export auto make_generator() -> std::shared_ptr; } // namespace routemon::locale -- cgit v1.3