diff options
Diffstat (limited to 'server/src/locale.cppm')
| -rw-r--r-- | server/src/locale.cppm | 424 |
1 files changed, 237 insertions, 187 deletions
diff --git a/server/src/locale.cppm b/server/src/locale.cppm index 65ae2ea..8fd867b 100644 --- a/server/src/locale.cppm +++ b/server/src/locale.cppm | |||
| @@ -11,235 +11,285 @@ import :util; | |||
| 11 | export namespace blocale = boost::locale; | 11 | export namespace blocale = boost::locale; |
| 12 | 12 | ||
| 13 | export namespace routemon { | 13 | export namespace routemon { |
| 14 | using lformat = blocale::format; | 14 | using lformat = blocale::format; |
| 15 | using blocale::translate; | 15 | using blocale::gettext; |
| 16 | using blocale::gettext; | 16 | using blocale::translate; |
| 17 | } // namespace routemon | 17 | } // namespace routemon |
| 18 | 18 | ||
| 19 | namespace routemon::locale { | 19 | namespace routemon::locale { |
| 20 | 20 | ||
| 21 | struct locale_priority { | 21 | struct locale_priority |
| 22 | float weight; | 22 | { |
| 23 | std::size_t original_index; | 23 | float weight; |
| 24 | }; | 24 | std::size_t original_index; |
| 25 | }; | ||
| 25 | 26 | ||
| 26 | auto operator<(locale_priority const& lhs, locale_priority const& rhs) -> bool { | 27 | auto operator<(locale_priority const& lhs, locale_priority const& rhs) -> bool |
| 27 | if (lhs.weight != rhs.weight) | 28 | { |
| 28 | return lhs.weight > rhs.weight; | 29 | if (lhs.weight != rhs.weight) |
| 29 | return lhs.original_index < rhs.original_index; | 30 | return lhs.weight > rhs.weight; |
| 30 | } | 31 | return lhs.original_index < rhs.original_index; |
| 32 | } | ||
| 31 | 33 | ||
| 32 | struct icu_locale_hash { | 34 | struct icu_locale_hash |
| 33 | std::size_t operator()(icu::Locale const& l) const noexcept { | 35 | { |
| 34 | static_assert(sizeof(std::int32_t) < sizeof(std::size_t)); | 36 | std::size_t operator()(icu::Locale const& l) const noexcept |
| 35 | std::int32_t hash = l.hashCode(); | 37 | { |
| 36 | if (hash < 0) { | 38 | static_assert(sizeof(std::int32_t) < sizeof(std::size_t)); |
| 37 | return static_cast<std::size_t>(std::numeric_limits<int>::max()) + static_cast<std::size_t>(-hash) + 1; | 39 | std::int32_t hash = l.hashCode(); |
| 38 | } else { | 40 | if (hash < 0) |
| 39 | return static_cast<std::size_t>(hash); | 41 | { |
| 40 | } | 42 | return static_cast<std::size_t>(std::numeric_limits<int>::max()) |
| 43 | + static_cast<std::size_t>(-hash) + 1; | ||
| 44 | } | ||
| 45 | else | ||
| 46 | { | ||
| 47 | return static_cast<std::size_t>(hash); | ||
| 41 | } | 48 | } |
| 42 | }; | 49 | } |
| 50 | }; | ||
| 43 | 51 | ||
| 44 | using icu_locale_priority_map = std::unordered_map<icu::Locale, locale_priority, icu_locale_hash>; | 52 | using icu_locale_priority_map = |
| 45 | using icu_priority_locale = std::pair<icu::Locale, locale_priority>; | 53 | std::unordered_map<icu::Locale, locale_priority, icu_locale_hash>; |
| 54 | using icu_priority_locale = std::pair<icu::Locale, locale_priority>; | ||
| 46 | 55 | ||
| 47 | auto operator<(icu_priority_locale const& lhs, icu_priority_locale const& rhs) -> bool { | 56 | auto operator<(icu_priority_locale const& lhs, icu_priority_locale const& rhs) |
| 48 | return lhs.second < rhs.second; | 57 | -> bool |
| 49 | } | 58 | { |
| 59 | return lhs.second < rhs.second; | ||
| 60 | } | ||
| 50 | 61 | ||
| 51 | class icu_priority_locale_vec_iterator : public icu::Locale::Iterator { | 62 | class icu_priority_locale_vec_iterator : public icu::Locale::Iterator |
| 52 | std::size_t i_ = 0uz; | 63 | { |
| 53 | std::vector<icu_priority_locale> ls_; | 64 | std::size_t i_ = 0uz; |
| 65 | std::vector<icu_priority_locale> ls_; | ||
| 54 | 66 | ||
| 55 | public: | 67 | public: |
| 56 | explicit icu_priority_locale_vec_iterator(std::vector<icu_priority_locale>&& ls) | 68 | explicit icu_priority_locale_vec_iterator( |
| 57 | : ls_(std::move(ls)) | 69 | std::vector<icu_priority_locale>&& ls) |
| 58 | {} | 70 | : ls_(std::move(ls)) |
| 71 | { | ||
| 72 | } | ||
| 59 | 73 | ||
| 60 | auto hasNext() const -> UBool override { | 74 | auto hasNext() const -> UBool override { return i_ < ls_.size(); } |
| 61 | return i_ < ls_.size(); | ||
| 62 | } | ||
| 63 | 75 | ||
| 64 | auto next() -> icu::Locale const& override { | 76 | auto next() -> icu::Locale const& override { return ls_[i_++].first; } |
| 65 | return ls_[i_++].first; | ||
| 66 | } | ||
| 67 | 77 | ||
| 68 | ~icu_priority_locale_vec_iterator() override = default; | 78 | ~icu_priority_locale_vec_iterator() override = default; |
| 69 | }; | 79 | }; |
| 70 | 80 | ||
| 71 | export template<class T> | 81 | export template <class T> |
| 72 | concept locale_input_range = | 82 | concept locale_input_range = |
| 73 | std::ranges::input_range<T> && | 83 | std::ranges::input_range<T> |
| 74 | std::same_as<std::locale const&, std::ranges::range_const_reference_t<T>>; | 84 | && std::same_as< |
| 85 | std::locale const&, std::ranges::range_const_reference_t<T>>; | ||
| 75 | 86 | ||
| 76 | // Helps select a locale based on the Accept-Language header in an | 87 | // Helps select a locale based on the Accept-Language header in an |
| 77 | // HTTP request. | 88 | // HTTP request. |
| 78 | export class selector { | 89 | export class selector |
| 79 | std::locale default_; | 90 | { |
| 80 | icu::LocaleMatcher matcher_; | 91 | std::locale default_; |
| 81 | std::shared_ptr<blocale::generator const> lgen_; | 92 | icu::LocaleMatcher matcher_; |
| 93 | std::shared_ptr<blocale::generator const> lgen_; | ||
| 82 | 94 | ||
| 83 | auto make_matcher(locale_input_range auto supported_locales, std::locale default_locale) { | 95 | auto make_matcher( |
| 84 | auto builder = icu::LocaleMatcher::Builder{}; | 96 | locale_input_range auto supported_locales, std::locale default_locale) |
| 85 | for (auto const& supported_locale : supported_locales) { | 97 | { |
| 86 | auto const& supported_locale_info = std::use_facet<blocale::info>(supported_locale); | 98 | auto builder = icu::LocaleMatcher::Builder{}; |
| 87 | auto supported_icu_locale = icu::Locale{supported_locale_info.name().c_str()}; | 99 | for (auto const& supported_locale : supported_locales) |
| 88 | if (supported_icu_locale.isBogus()) | 100 | { |
| 89 | throw std::runtime_error{"supported locale gives rise to bogus ICU locale"}; | 101 | auto const& supported_locale_info = |
| 90 | builder.addSupportedLocale(supported_icu_locale); | 102 | std::use_facet<blocale::info>(supported_locale); |
| 91 | } | 103 | auto supported_icu_locale = |
| 92 | auto const& default_locale_info = std::use_facet<blocale::info>(default_locale); | 104 | icu::Locale{supported_locale_info.name().c_str()}; |
| 93 | auto default_icu_locale = icu::Locale{default_locale_info.name().c_str()}; | 105 | if (supported_icu_locale.isBogus()) |
| 94 | if (default_icu_locale.isBogus()) | 106 | throw std::runtime_error{ |
| 95 | throw std::runtime_error{"default locale gives rise to bogus ICU locale"}; | 107 | "supported locale gives rise to bogus ICU locale" |
| 96 | builder.setDefaultLocale(&default_icu_locale); | 108 | }; |
| 97 | auto ec = UErrorCode::U_ZERO_ERROR; | 109 | builder.addSupportedLocale(supported_icu_locale); |
| 98 | auto matcher = builder.build(ec); | ||
| 99 | if (U_FAILURE(ec)) | ||
| 100 | throw std::runtime_error{"failed to build icu::LocaleMatcher"}; | ||
| 101 | return matcher; | ||
| 102 | } | 110 | } |
| 111 | auto const& default_locale_info = | ||
| 112 | std::use_facet<blocale::info>(default_locale); | ||
| 113 | auto default_icu_locale = icu::Locale{default_locale_info.name().c_str()}; | ||
| 114 | if (default_icu_locale.isBogus()) | ||
| 115 | throw std::runtime_error{"default locale gives rise to bogus ICU locale"}; | ||
| 116 | builder.setDefaultLocale(&default_icu_locale); | ||
| 117 | auto ec = UErrorCode::U_ZERO_ERROR; | ||
| 118 | auto matcher = builder.build(ec); | ||
| 119 | if (U_FAILURE(ec)) | ||
| 120 | throw std::runtime_error{"failed to build icu::LocaleMatcher"}; | ||
| 121 | return matcher; | ||
| 122 | } | ||
| 123 | |||
| 124 | // Trimming optional whitespace as defined in RFC 9110, § 12.4.2. | ||
| 125 | static auto ltrim_ows(std::string_view s) -> std::string_view | ||
| 126 | { | ||
| 127 | if (auto i = s.find_first_not_of(" \t"); i != std::string_view::npos) | ||
| 128 | s.remove_prefix(i); | ||
| 129 | return s; | ||
| 130 | } | ||
| 131 | static auto rtrim_ows(std::string_view s) -> std::string_view | ||
| 132 | { | ||
| 133 | if (auto i = s.find_last_not_of(" \t"); i != std::string_view::npos) | ||
| 134 | return s.substr(0, i + 1); | ||
| 135 | return s; | ||
| 136 | } | ||
| 137 | static auto trim_ows(std::string_view s) -> std::string_view | ||
| 138 | { | ||
| 139 | return rtrim_ows(ltrim_ows(s)); | ||
| 140 | } | ||
| 103 | 141 | ||
| 104 | // Trimming optional whitespace as defined in RFC 9110, § 12.4.2. | 142 | auto from_icu_locale(icu::Locale const& l) const -> std::locale |
| 105 | static auto ltrim_ows(std::string_view s) -> std::string_view { | 143 | { |
| 106 | if (auto i = s.find_first_not_of(" \t"); i != std::string_view::npos) | 144 | auto posix_name = std::string{l.getLanguage()}; |
| 107 | s.remove_prefix(i); | 145 | if (l.getScript() && std::strlen(l.getScript()) > 0) |
| 108 | return s; | 146 | { |
| 147 | posix_name += "_"; | ||
| 148 | posix_name += l.getScript(); | ||
| 109 | } | 149 | } |
| 110 | static auto rtrim_ows(std::string_view s) -> std::string_view { | 150 | if (l.getCountry() && std::strlen(l.getCountry()) > 0) |
| 111 | if (auto i = s.find_last_not_of(" \t"); i != std::string_view::npos) | 151 | { |
| 112 | return s.substr(0, i + 1); | 152 | posix_name += "_"; |
| 113 | return s; | 153 | posix_name += l.getCountry(); |
| 114 | } | 154 | } |
| 115 | static auto trim_ows(std::string_view s) -> std::string_view { | 155 | posix_name += ".UTF-8"; |
| 116 | return rtrim_ows(ltrim_ows(s)); | 156 | auto added_at = false; |
| 157 | if (l.getVariant() && std::strlen(l.getVariant()) > 0) | ||
| 158 | { | ||
| 159 | added_at = true; | ||
| 160 | posix_name += "@"; | ||
| 161 | posix_name += l.getVariant(); | ||
| 117 | } | 162 | } |
| 118 | 163 | auto ec = UErrorCode::U_ZERO_ERROR; | |
| 119 | auto from_icu_locale(icu::Locale const& l) const -> std::locale { | 164 | auto* keywords = l.createKeywords(ec); |
| 120 | auto posix_name = std::string{l.getLanguage()}; | 165 | if (U_FAILURE(ec)) |
| 121 | if (l.getScript() && std::strlen(l.getScript()) > 0) { | 166 | throw std::runtime_error{"failed to create keywords"}; |
| 122 | posix_name += "_"; | 167 | if (keywords) |
| 123 | posix_name += l.getScript(); | 168 | { |
| 124 | } | 169 | std::int32_t kw_len = 0; |
| 125 | if (l.getCountry() && std::strlen(l.getCountry()) > 0) { | 170 | char const* kw = nullptr; |
| 126 | posix_name += "_"; | 171 | while (kw = keywords->next(&kw_len, ec), !U_FAILURE(ec) && kw) |
| 127 | posix_name += l.getCountry(); | 172 | { |
| 128 | } | 173 | auto value = |
| 129 | posix_name += ".UTF-8"; | 174 | l.getKeywordValue<std::string>(icu::StringPiece(kw, kw_len), ec); |
| 130 | auto added_at = false; | 175 | if (!added_at) |
| 131 | if (l.getVariant() && std::strlen(l.getVariant()) > 0) { | 176 | { |
| 132 | added_at = true; | 177 | posix_name += "@"; |
| 133 | posix_name += "@"; | 178 | added_at = true; |
| 134 | posix_name += l.getVariant(); | ||
| 135 | } | ||
| 136 | auto ec = UErrorCode::U_ZERO_ERROR; | ||
| 137 | auto* keywords = l.createKeywords(ec); | ||
| 138 | if (U_FAILURE(ec)) | ||
| 139 | throw std::runtime_error{"failed to create keywords"}; | ||
| 140 | if (keywords) { | ||
| 141 | std::int32_t kw_len = 0; | ||
| 142 | char const* kw = nullptr; | ||
| 143 | while (kw = keywords->next(&kw_len, ec), !U_FAILURE(ec) && kw) { | ||
| 144 | auto value = l.getKeywordValue<std::string>(icu::StringPiece(kw, kw_len), ec); | ||
| 145 | if (!added_at) { | ||
| 146 | posix_name += "@"; | ||
| 147 | added_at = true; | ||
| 148 | } else { | ||
| 149 | posix_name += ";"; | ||
| 150 | } | ||
| 151 | posix_name += kw; | ||
| 152 | posix_name += "="; | ||
| 153 | posix_name += value; | ||
| 154 | } | 179 | } |
| 155 | if (U_FAILURE(ec)) | 180 | else |
| 156 | throw std::runtime_error{"failed to iterate over keywords"}; | 181 | { |
| 157 | delete keywords; | 182 | posix_name += ";"; |
| 183 | } | ||
| 184 | posix_name += kw; | ||
| 185 | posix_name += "="; | ||
| 186 | posix_name += value; | ||
| 158 | } | 187 | } |
| 159 | return lgen_->generate(posix_name); | 188 | if (U_FAILURE(ec)) |
| 189 | throw std::runtime_error{"failed to iterate over keywords"}; | ||
| 190 | delete keywords; | ||
| 160 | } | 191 | } |
| 192 | return lgen_->generate(posix_name); | ||
| 193 | } | ||
| 161 | 194 | ||
| 162 | public: | 195 | public: |
| 163 | // Note: lgen must live at least as long as the selector constructed here! | 196 | // Note: lgen must live at least as long as the selector constructed here! |
| 164 | // It is unfortunately not possible to copy/move a blocale::generator. | 197 | // It is unfortunately not possible to copy/move a blocale::generator. |
| 165 | explicit selector(locale_input_range auto locales, std::locale default_, std::shared_ptr<blocale::generator const> lgen) | 198 | explicit selector( |
| 166 | : default_{default_}, matcher_{make_matcher(locales, default_)}, lgen_{lgen} | 199 | locale_input_range auto locales, std::locale default_, |
| 167 | {} | 200 | std::shared_ptr<blocale::generator const> lgen) |
| 201 | : default_{default_}, matcher_{make_matcher(locales, default_)}, lgen_{lgen} | ||
| 202 | { | ||
| 203 | } | ||
| 168 | 204 | ||
| 169 | auto select(std::string_view accept_language) const -> std::locale { | 205 | auto select(std::string_view accept_language) const -> std::locale |
| 170 | using namespace std::literals::string_view_literals; | 206 | { |
| 171 | // NOTE: can also contain a *;q=0.1 | 207 | using namespace std::literals::string_view_literals; |
| 172 | // q should have at most 3 digits after period | 208 | // NOTE: can also contain a *;q=0.1 |
| 173 | auto dlpm = icu_locale_priority_map{}; | 209 | // q should have at most 3 digits after period |
| 174 | for (auto const [i, lang_prio] : accept_language | std::views::split(","sv) | std::views::enumerate) { | 210 | auto dlpm = icu_locale_priority_map{}; |
| 175 | auto [lang_range_ut, mweight_ut] = util::split_on(std::string_view{lang_prio}, ';'); | 211 | for (auto const [i, lang_prio] : |
| 176 | auto lang_range_str = trim_ows(lang_range_ut); | 212 | accept_language | std::views::split(","sv) | std::views::enumerate) |
| 177 | auto mweight_str = mweight_ut.transform(trim_ows); | 213 | { |
| 178 | if (lang_range_str == "*") | 214 | auto [lang_range_ut, mweight_ut] = |
| 179 | break; | 215 | util::split_on(std::string_view{lang_prio}, ';'); |
| 216 | auto lang_range_str = trim_ows(lang_range_ut); | ||
| 217 | auto mweight_str = mweight_ut.transform(trim_ows); | ||
| 218 | if (lang_range_str == "*") | ||
| 219 | break; | ||
| 180 | 220 | ||
| 181 | auto ec = UErrorCode::U_ZERO_ERROR; | 221 | auto ec = UErrorCode::U_ZERO_ERROR; |
| 182 | auto icu_locale = icu::Locale::forLanguageTag(lang_range_str, ec); | 222 | auto icu_locale = icu::Locale::forLanguageTag(lang_range_str, ec); |
| 183 | if (U_FAILURE(ec) || icu_locale.isBogus()) | 223 | if (U_FAILURE(ec) || icu_locale.isBogus()) |
| 184 | continue; // ignore this locale | 224 | continue; // ignore this locale |
| 185 | 225 | ||
| 186 | auto weight = 1.0f; | 226 | auto weight = 1.0f; |
| 187 | if (mweight_str && mweight_str->starts_with("q=")) { | 227 | if (mweight_str && mweight_str->starts_with("q=")) |
| 188 | auto weight_str = mweight_str->substr(2, 4); | 228 | { |
| 189 | if (auto mweight = util::parse_float(weight_str, std::chars_format::fixed); | 229 | auto weight_str = mweight_str->substr(2, 4); |
| 190 | mweight && 0.0f < *mweight && *mweight < 1.0f) { | 230 | if (auto mweight = |
| 191 | weight = *mweight; | 231 | util::parse_float(weight_str, std::chars_format::fixed); |
| 192 | } | 232 | mweight && 0.0f < *mweight && *mweight < 1.0f) |
| 233 | { | ||
| 234 | weight = *mweight; | ||
| 193 | } | 235 | } |
| 236 | } | ||
| 194 | 237 | ||
| 195 | if (weight > 0.0f) { | 238 | if (weight > 0.0f) |
| 196 | dlpm[icu_locale] = { | 239 | { |
| 240 | dlpm[icu_locale] = { | ||
| 197 | .weight = weight, | 241 | .weight = weight, |
| 198 | .original_index = static_cast<std::size_t>(i), | 242 | .original_index = static_cast<std::size_t>(i), |
| 199 | }; | 243 | }; |
| 200 | } else { | 244 | } |
| 201 | dlpm.erase(icu_locale); | 245 | else |
| 202 | } | 246 | { |
| 247 | dlpm.erase(icu_locale); | ||
| 203 | } | 248 | } |
| 204 | |||
| 205 | auto desired_locales = std::vector<icu_priority_locale>{dlpm.begin(), dlpm.end()}; | ||
| 206 | std::sort(desired_locales.begin(), desired_locales.end()); | ||
| 207 | auto it = icu_priority_locale_vec_iterator{std::move(desired_locales)}; | ||
| 208 | auto ec = UErrorCode::U_ZERO_ERROR; | ||
| 209 | auto res = matcher_.getBestMatchResult(it, ec); | ||
| 210 | if (U_FAILURE(ec)) | ||
| 211 | return default_; | ||
| 212 | auto resolved = res.makeResolvedLocale(ec); // TODO: maybe don't? | ||
| 213 | if (U_FAILURE(ec)) | ||
| 214 | return from_icu_locale(*res.getSupportedLocale()); | ||
| 215 | return from_icu_locale(resolved); | ||
| 216 | } | 249 | } |
| 217 | }; | ||
| 218 | 250 | ||
| 219 | export auto to_bcp47_lang_tag(std::locale locale) -> std::optional<std::string> { | 251 | auto desired_locales = |
| 220 | auto const& locale_info = std::use_facet<blocale::info>(locale); | 252 | std::vector<icu_priority_locale>{dlpm.begin(), dlpm.end()}; |
| 253 | std::sort(desired_locales.begin(), desired_locales.end()); | ||
| 254 | auto it = icu_priority_locale_vec_iterator{std::move(desired_locales)}; | ||
| 221 | auto ec = UErrorCode::U_ZERO_ERROR; | 255 | auto ec = UErrorCode::U_ZERO_ERROR; |
| 222 | auto bcp47_lang_tag = icu::Locale{locale_info.name().c_str()}.toLanguageTag<std::string>(ec); | 256 | auto res = matcher_.getBestMatchResult(it, ec); |
| 257 | if (U_FAILURE(ec)) | ||
| 258 | return default_; | ||
| 259 | auto resolved = res.makeResolvedLocale(ec); // TODO: maybe don't? | ||
| 223 | if (U_FAILURE(ec)) | 260 | if (U_FAILURE(ec)) |
| 224 | return std::nullopt; | 261 | return from_icu_locale(*res.getSupportedLocale()); |
| 225 | return bcp47_lang_tag; | 262 | return from_icu_locale(resolved); |
| 226 | } | 263 | } |
| 264 | }; | ||
| 265 | |||
| 266 | export auto to_bcp47_lang_tag(std::locale locale) -> std::optional<std::string> | ||
| 267 | { | ||
| 268 | auto const& locale_info = std::use_facet<blocale::info>(locale); | ||
| 269 | auto ec = UErrorCode::U_ZERO_ERROR; | ||
| 270 | auto bcp47_lang_tag = | ||
| 271 | icu::Locale{locale_info.name().c_str()}.toLanguageTag<std::string>(ec); | ||
| 272 | if (U_FAILURE(ec)) | ||
| 273 | return std::nullopt; | ||
| 274 | return bcp47_lang_tag; | ||
| 275 | } | ||
| 227 | 276 | ||
| 228 | #ifdef LOCALEDIR | 277 | #ifdef LOCALEDIR |
| 229 | # define LOCALEDIR_AUX_XSTR(s) LOCALEDIR_AUX_STR(s) | 278 | #define LOCALEDIR_AUX_XSTR(s) LOCALEDIR_AUX_STR(s) |
| 230 | # define LOCALEDIR_AUX_STR(s) #s | 279 | #define LOCALEDIR_AUX_STR(s) #s |
| 231 | constexpr auto messages_path = std::string_view{LOCALEDIR_AUX_XSTR(LOCALEDIR)}; | 280 | constexpr auto messages_path = std::string_view{LOCALEDIR_AUX_XSTR(LOCALEDIR)}; |
| 232 | # undef LOCALEDIR_AUX_STR | 281 | #undef LOCALEDIR_AUX_STR |
| 233 | # undef LOCALEDIR_AUX_XSTR | 282 | #undef LOCALEDIR_AUX_XSTR |
| 234 | #else // ifdef LOCALEDIR | 283 | #else // ifdef LOCALEDIR |
| 235 | constexpr auto messages_path = std::string_view{"locale/dev"}; | 284 | constexpr auto messages_path = std::string_view{"locale/dev"}; |
| 236 | #endif // ifdef LOCALEDIR | 285 | #endif // ifdef LOCALEDIR |
| 237 | 286 | ||
| 238 | export auto make_generator() -> std::shared_ptr<blocale::generator const> { | 287 | export auto make_generator() -> std::shared_ptr<blocale::generator const> |
| 239 | auto lgen = std::make_shared<blocale::generator>(); | 288 | { |
| 240 | lgen->add_messages_path(std::string{messages_path}); | 289 | auto lgen = std::make_shared<blocale::generator>(); |
| 241 | lgen->add_messages_domain("routemon"); | 290 | lgen->add_messages_path(std::string{messages_path}); |
| 242 | return std::static_pointer_cast<blocale::generator const>(lgen); | 291 | lgen->add_messages_domain("routemon"); |
| 243 | } | 292 | return std::static_pointer_cast<blocale::generator const>(lgen); |
| 293 | } | ||
| 244 | 294 | ||
| 245 | } // namespace routemon::locale | 295 | } // namespace routemon::locale |