diff options
Diffstat (limited to 'server/src/locale.cppm')
| -rw-r--r-- | server/src/locale.cppm | 65 |
1 files changed, 26 insertions, 39 deletions
diff --git a/server/src/locale.cppm b/server/src/locale.cppm index 263de1d..00a3d36 100644 --- a/server/src/locale.cppm +++ b/server/src/locale.cppm | |||
| @@ -26,65 +26,52 @@ concept locale_input_range = | |||
| 26 | && std:: | 26 | && std:: |
| 27 | same_as<std::locale const&, std::ranges::range_const_reference_t<T>>; | 27 | same_as<std::locale const&, std::ranges::range_const_reference_t<T>>; |
| 28 | 28 | ||
| 29 | class bcp47_tag : public std::locale::facet | ||
| 30 | { | ||
| 31 | std::string tag_; | ||
| 32 | |||
| 33 | public: | ||
| 34 | static std::locale::id id; | ||
| 35 | |||
| 36 | explicit bcp47_tag(std::locale locale); | ||
| 37 | ~bcp47_tag() override = default; | ||
| 38 | |||
| 39 | auto tag() const -> std::string_view; | ||
| 40 | }; | ||
| 41 | |||
| 29 | // Helps select a locale based on the Accept-Language header in an | 42 | // Helps select a locale based on the Accept-Language header in an |
| 30 | // HTTP request. | 43 | // HTTP request. |
| 31 | export class selector | 44 | export class selector |
| 32 | { | 45 | { |
| 46 | std::vector<std::locale> supported_; | ||
| 33 | std::locale default_; | 47 | std::locale default_; |
| 34 | icu::LocaleMatcher matcher_; | 48 | icu::LocaleMatcher matcher_; |
| 35 | std::shared_ptr<blocale::generator const> lgen_; | ||
| 36 | 49 | ||
| 37 | auto make_matcher( | 50 | static auto make_matcher(std::vector<std::locale> const& supported_locales) |
| 38 | locale_input_range auto supported_locales, std::locale default_locale) | 51 | -> icu::LocaleMatcher; |
| 39 | { | ||
| 40 | auto builder = icu::LocaleMatcher::Builder{}; | ||
| 41 | for (auto const& supported_locale : supported_locales) | ||
| 42 | { | ||
| 43 | auto const& supported_locale_info = | ||
| 44 | std::use_facet<blocale::info>(supported_locale); | ||
| 45 | auto supported_icu_locale = | ||
| 46 | icu::Locale{supported_locale_info.name().c_str()}; | ||
| 47 | if (supported_icu_locale.isBogus()) | ||
| 48 | throw std::runtime_error{ | ||
| 49 | "supported locale gives rise to bogus ICU locale" | ||
| 50 | }; | ||
| 51 | builder.addSupportedLocale(supported_icu_locale); | ||
| 52 | } | ||
| 53 | auto const& default_locale_info = | ||
| 54 | std::use_facet<blocale::info>(default_locale); | ||
| 55 | auto default_icu_locale = icu::Locale{default_locale_info.name().c_str()}; | ||
| 56 | if (default_icu_locale.isBogus()) | ||
| 57 | throw std::runtime_error{"default locale gives rise to bogus ICU locale"}; | ||
| 58 | builder.setDefaultLocale(&default_icu_locale); | ||
| 59 | auto ec = UErrorCode::U_ZERO_ERROR; | ||
| 60 | auto matcher = builder.build(ec); | ||
| 61 | if (U_FAILURE(ec)) | ||
| 62 | throw std::runtime_error{"failed to build icu::LocaleMatcher"}; | ||
| 63 | return matcher; | ||
| 64 | } | ||
| 65 | 52 | ||
| 66 | // Trimming optional whitespace as defined in RFC 9110, § 12.4.2. | 53 | // Trimming optional whitespace as defined in RFC 9110, § 12.4.2. |
| 67 | static auto ltrim_ows(std::string_view s) -> std::string_view; | 54 | static auto ltrim_ows(std::string_view s) -> std::string_view; |
| 68 | static auto rtrim_ows(std::string_view s) -> std::string_view; | 55 | static auto rtrim_ows(std::string_view s) -> std::string_view; |
| 69 | static auto trim_ows(std::string_view s) -> std::string_view; | 56 | static auto trim_ows(std::string_view s) -> std::string_view; |
| 70 | 57 | ||
| 71 | auto from_icu_locale(icu::Locale const& l) const -> std::locale; | ||
| 72 | |||
| 73 | public: | 58 | public: |
| 74 | // Note: lgen must live at least as long as the selector constructed here! | 59 | explicit selector(locale_input_range auto supported, std::locale default_) |
| 75 | // It is unfortunately not possible to copy/move a blocale::generator. | 60 | : supported_{ |
| 76 | explicit selector( | 61 | std::from_range, |
| 77 | locale_input_range auto locales, std::locale default_, | 62 | supported |
| 78 | std::shared_ptr<blocale::generator const> lgen) | 63 | | std::views::transform( |
| 79 | : default_{default_}, matcher_{make_matcher(locales, default_)}, lgen_{lgen} | 64 | [](std::locale const& locale) -> std::locale |
| 65 | { return std::locale{locale, new bcp47_tag{locale}}; }) | ||
| 66 | }, | ||
| 67 | default_{std::locale{default_, new bcp47_tag{default_}}}, | ||
| 68 | matcher_{make_matcher(supported_)} | ||
| 80 | { | 69 | { |
| 81 | } | 70 | } |
| 82 | 71 | ||
| 83 | auto select(std::string_view accept_language) const -> std::locale; | 72 | auto select(std::string_view accept_language) const -> std::locale; |
| 84 | }; | 73 | }; |
| 85 | 74 | ||
| 86 | auto to_bcp47_lang_tag(std::locale locale) -> std::optional<std::string>; | ||
| 87 | |||
| 88 | export auto make_generator() -> std::shared_ptr<blocale::generator const>; | 75 | export auto make_generator() -> std::shared_ptr<blocale::generator const>; |
| 89 | 76 | ||
| 90 | } // namespace routemon::locale | 77 | } // namespace routemon::locale |