1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "intl_plural_rules.h"
17
18 #include "i18n_hilog.h"
19 #include "locale_helper.h"
20 #include "utils.h"
21
22 namespace OHOS {
23 namespace Global {
24 namespace I18n {
25 bool IntlPluralRules::initAvailableLocales = false;
26 std::mutex IntlPluralRules::getLocalesMutex;
27 std::set<std::string> IntlPluralRules::availableLocales = {};
28
29 const int32_t IntlPluralRules::STRING_SEPARATOR_LENGTH = 4;
30 const int32_t IntlPluralRules::MAX_DIGITS_VALUE = 21;
31 const int32_t IntlPluralRules::MAX_FRACTION_DIGITS_VALUE = 20;
32 const int32_t IntlPluralRules::MNFD_DEFAULT = 0;
33 const int32_t IntlPluralRules::MXFD_DEFAULT = 3;
34
35 const std::string IntlPluralRules::LOCALE_MATCHER_TAG = "localeMatcher";
36 const std::string IntlPluralRules::TYPE_TAG = "type";
37 const std::string IntlPluralRules::MIN_INTEGER_DIGITS = "minimumIntegerDigits";
38 const std::string IntlPluralRules::MIN_SIGNALE_DIGITS = "minimumSignificantDigits";
39 const std::string IntlPluralRules::MAX_SIGNALE_DIGITS = "maximumSignificantDigits";
40 const std::string IntlPluralRules::MIN_FRACTION_DIGITS = "minimumFractionDigits";
41 const std::string IntlPluralRules::MAX_FRACTION_DIGITS = "maximumFractionDigits";
42
43 const std::unordered_set<std::string> IntlPluralRules::LOCALE_MATCHER_VALUE = {
44 "best fit", "lookup"
45 };
46
47 const std::unordered_set<std::string> IntlPluralRules::TYPE_VALUE = {
48 "cardinal", "ordinal"
49 };
50
51 const std::unordered_map<std::string, UPluralType> IntlPluralRules::TYPE_TO_ICU_TYPE = {
52 { "ordinal", UPLURAL_TYPE_ORDINAL },
53 { "cardinal", UPLURAL_TYPE_CARDINAL },
54 };
55
SupportedLocalesOf(const std::vector<std::string> & requestLocales,const std::unordered_map<std::string,std::string> & options,ErrorMessage & errorMessage)56 std::vector<std::string> IntlPluralRules::SupportedLocalesOf(const std::vector<std::string>& requestLocales,
57 const std::unordered_map<std::string, std::string>& options, ErrorMessage& errorMessage)
58 {
59 std::vector<std::string> undefined = {};
60 I18nErrorCode status = I18nErrorCode::SUCCESS;
61 auto requestedLocales = LocaleHelper::CanonicalizeLocaleList(requestLocales, status);
62 if (status != I18nErrorCode::SUCCESS) {
63 errorMessage.type = ErrorType::RANGE_ERROR;
64 errorMessage.message = "invalid locale";
65 HILOG_ERROR_I18N("IntlPluralRules::SupportedLocalesOf: Call CanonicalizeLocaleList failed.");
66 return undefined;
67 }
68
69 auto iter = options.find(LOCALE_MATCHER_TAG);
70 if (iter != options.end()) {
71 if (!IsValidOptionName(LOCALE_MATCHER_VALUE, iter->second)) {
72 errorMessage.type = ErrorType::RANGE_ERROR;
73 errorMessage.message = "getStringOption failed";
74 return undefined;
75 }
76 }
77 return LocaleHelper::LookupSupportedLocales(GetAvailableLocales(), requestedLocales);
78 }
79
IntlPluralRules(const std::vector<std::string> & localeTags,const std::unordered_map<std::string,std::string> & options,ErrorMessage & errorMessage)80 IntlPluralRules::IntlPluralRules(const std::vector<std::string>& localeTags,
81 const std::unordered_map<std::string, std::string>& options, ErrorMessage& errorMessage)
82 {
83 I18nErrorCode status = I18nErrorCode::SUCCESS;
84 std::vector<std::string> requestedLocales = LocaleHelper::CanonicalizeLocaleList(localeTags, status);
85 if (status != I18nErrorCode::SUCCESS) {
86 errorMessage.type = ErrorType::RANGE_ERROR;
87 errorMessage.message = "invalid locale";
88 HILOG_ERROR_I18N("IntlPluralRules::IntlPluralRules: CanonicalizeLocaleList failed.");
89 return;
90 }
91 if (!ParseOptions(options, errorMessage)) {
92 HILOG_ERROR_I18N("IntlPluralRules::IntlPluralRules: Parse configs failed.");
93 return;
94 }
95 if (!ResolveLocale(GetAvailableLocales(), requestedLocales, errorMessage)) {
96 HILOG_ERROR_I18N("IntlPluralRules::IntlPluralRules: resolve Locale failed.");
97 return;
98 }
99 if (!InitIntlPluralRules(options, errorMessage)) {
100 HILOG_ERROR_I18N("IntlPluralRules::IntlPluralRules: init IntlPluralRules failed.");
101 return;
102 }
103 initSuccess = true;
104 }
105
InitIntlPluralRules(const std::unordered_map<std::string,std::string> & options,ErrorMessage & errorMessage)106 bool IntlPluralRules::InitIntlPluralRules(const std::unordered_map<std::string, std::string>& options,
107 ErrorMessage& errorMessage)
108 {
109 icuNumberFormatter = icu::number::NumberFormatter::withLocale(icuLocale).roundingMode(UNUM_ROUND_HALFUP);
110 icuNumberFormatter =
111 icuNumberFormatter.integerWidth(icu::number::IntegerWidth::zeroFillTo(minimumIntegerDigits));
112 if (roundingType != RoundingType::COMPACTROUNDING) {
113 icu::number::Precision precision =
114 icu::number::Precision::minMaxFraction(minimumFractionDigits, maximumFractionDigits);
115 if (minimumSignificantDigits != 0) {
116 precision =
117 icu::number::Precision::minMaxSignificantDigits(minimumSignificantDigits, maximumSignificantDigits);
118 }
119 icuNumberFormatter = icuNumberFormatter.precision(precision);
120 }
121 auto typeIter = TYPE_TO_ICU_TYPE.find(type);
122 if (typeIter == TYPE_TO_ICU_TYPE.end()) {
123 HILOG_ERROR_I18N("InitIntlPluralRules: this branch is unreachable.");
124 return false;
125 }
126 UPluralType icuType = typeIter->second;
127 UErrorCode icuStatus = U_ZERO_ERROR;
128 icuPluralRules.reset(icu::PluralRules::forLocale(icuLocale, icuType, icuStatus));
129 if (U_FAILURE(icuStatus) || icuPluralRules == nullptr) {
130 errorMessage.type = ErrorType::RANGE_ERROR;
131 errorMessage.message = "cannot create icuPluralRules";
132 return false;
133 }
134 return true;
135 }
136
ParseOptions(const std::unordered_map<std::string,std::string> & options,ErrorMessage & errorMessage)137 bool IntlPluralRules::ParseOptions(const std::unordered_map<std::string, std::string>& options,
138 ErrorMessage& errorMessage)
139 {
140 auto localeMatcherIter = options.find(LOCALE_MATCHER_TAG);
141 if (localeMatcherIter != options.end()) {
142 if (!IsValidOptionName(LOCALE_MATCHER_VALUE, localeMatcherIter->second)) {
143 errorMessage.type = ErrorType::RANGE_ERROR;
144 errorMessage.message = "getStringOption failed";
145 HILOG_ERROR_I18N("IntlPluralRules::ParseOptions: localeMatcher is invalid.");
146 return false;
147 }
148 localeMatcher = localeMatcherIter->second;
149 } else {
150 localeMatcher = "best fit";
151 }
152
153 auto typeIter = options.find(TYPE_TAG);
154 if (typeIter != options.end()) {
155 if (!IsValidOptionName(TYPE_VALUE, typeIter->second)) {
156 errorMessage.type = ErrorType::RANGE_ERROR;
157 errorMessage.message = "getStringOption failed";
158 HILOG_ERROR_I18N("IntlPluralRules::ParseOptions: type is invalid.");
159 return false;
160 }
161 type = typeIter->second;
162 } else {
163 type = "cardinal";
164 }
165
166 if (!SetNumberFormatDigitOptions(options, MNFD_DEFAULT, MXFD_DEFAULT, errorMessage)) {
167 return false;
168 }
169 return true;
170 }
171
GetNextLocale(icu::StringEnumeration * locales,std::string & validLocale,int32_t * len)172 bool IntlPluralRules::GetNextLocale(icu::StringEnumeration* locales, std::string& validLocale, int32_t* len)
173 {
174 UErrorCode icuStatus = U_ZERO_ERROR;
175 const char* locale = locales->next(len, icuStatus);
176 if (!U_SUCCESS(icuStatus) || locale == nullptr) {
177 validLocale = "";
178 return false;
179 }
180 validLocale = std::string(locale);
181 return true;
182 }
183
GetAvailableLocales()184 std::set<std::string> IntlPluralRules::GetAvailableLocales()
185 {
186 if (initAvailableLocales) {
187 return availableLocales;
188 }
189 std::lock_guard<std::mutex> getLocalesLock(getLocalesMutex);
190 if (initAvailableLocales) {
191 return availableLocales;
192 }
193 UErrorCode icuStatus = U_ZERO_ERROR;
194 std::unique_ptr<icu::StringEnumeration> locales(icu::PluralRules::getAvailableLocales(icuStatus));
195 if (U_FAILURE(icuStatus)) {
196 HILOG_ERROR_I18N("IntlPluralRules::GetAvailableLocales: icu getAvailableLocales failed.");
197 return availableLocales;
198 }
199 std::string validLocale;
200 int32_t len = 0;
201 while (GetNextLocale(locales.get(), validLocale, &len)) {
202 if (len >= STRING_SEPARATOR_LENGTH) {
203 std::replace(validLocale.begin(), validLocale.end(), '_', '-');
204 }
205 availableLocales.insert(validLocale);
206 }
207 initAvailableLocales = true;
208 return availableLocales;
209 }
210
ResolveLocale(const std::set<std::string> & availableLocales,const std::vector<std::string> & requestedLocales,ErrorMessage & errorMessage)211 bool IntlPluralRules::ResolveLocale(const std::set<std::string>& availableLocales,
212 const std::vector<std::string>& requestedLocales, ErrorMessage& errorMessage)
213 {
214 bool initLocale = false;
215 std::string availableLocale = LocaleHelper::LookupMatcher(availableLocales, requestedLocales);
216 if (!availableLocale.empty()) {
217 UErrorCode icuStatus = U_ZERO_ERROR;
218 icuLocale = icu::Locale::forLanguageTag(icu::StringPiece(availableLocale), icuStatus);
219 if (U_SUCCESS(icuStatus)) {
220 initLocale = true;
221 }
222 }
223 if (!initLocale) {
224 UErrorCode icuStatus = U_ZERO_ERROR;
225 std::string defaultLocale = LocaleHelper::DefaultLocale();
226 icuLocale = icu::Locale::forLanguageTag(icu::StringPiece(defaultLocale), icuStatus);
227 if (U_FAILURE(icuStatus)) {
228 HILOG_ERROR_I18N("IntlPluralRules::ResolveLocale: Create defaultLocale failed: %{public}s.",
229 defaultLocale.c_str());
230 errorMessage.type = ErrorType::RANGE_ERROR;
231 errorMessage.message = "invalid locale";
232 return false;
233 }
234 }
235 UErrorCode icuStatus = U_ZERO_ERROR;
236 localeStr = icuLocale.toLanguageTag<std::string>(icuStatus);
237 if (U_FAILURE(icuStatus)) {
238 HILOG_ERROR_I18N("IntlPluralRules::ResolveLocale: Get languageTag from icuLocale failed.");
239 errorMessage.type = ErrorType::RANGE_ERROR;
240 errorMessage.message = "invalid locale";
241 return false;
242 }
243 return true;
244 }
245
IsValidOptionName(const std::unordered_set<std::string> & optionValues,const std::string & value)246 bool IntlPluralRules::IsValidOptionName(const std::unordered_set<std::string>& optionValues, const std::string& value)
247 {
248 auto iter = optionValues.find(value);
249 if (iter == optionValues.end()) {
250 return false;
251 }
252 return true;
253 }
254
GetDefaultNumberOption(const std::unordered_map<std::string,std::string> & options,const std::string & key,int32_t minimum,int32_t maximum,int32_t fallback,ErrorMessage & errorMessage)255 int32_t IntlPluralRules::GetDefaultNumberOption(const std::unordered_map<std::string, std::string>& options,
256 const std::string& key, int32_t minimum, int32_t maximum, int32_t fallback, ErrorMessage& errorMessage)
257 {
258 auto iter = options.find(key);
259 if (iter == options.end()) {
260 return fallback;
261 }
262 int32_t numberStatus = 0;
263 double numberResult = ConvertString2Double(iter->second, numberStatus);
264 if (numberStatus != 0 || numberResult < minimum || numberResult > maximum) {
265 errorMessage.type = ErrorType::RANGE_ERROR;
266 errorMessage.message = "";
267 HILOG_ERROR_I18N("GetDefaultNumberOption: out of range: %{public}lf", numberResult);
268 return fallback;
269 }
270 return std::floor(numberResult);
271 }
272
SetSignificantDigitsOptions(const std::unordered_map<std::string,std::string> & options,ErrorMessage & errorMessage)273 bool IntlPluralRules::SetSignificantDigitsOptions(const std::unordered_map<std::string, std::string>& options,
274 ErrorMessage& errorMessage)
275 {
276 roundingType = RoundingType::SIGNIFICANTDIGITS;
277 minimumSignificantDigits = GetDefaultNumberOption(options, MIN_SIGNALE_DIGITS, 1, MAX_DIGITS_VALUE, 1,
278 errorMessage);
279 if (errorMessage.type != ErrorType::NO_ERROR) {
280 return false;
281 }
282 maximumSignificantDigits = GetDefaultNumberOption(options, MAX_SIGNALE_DIGITS, minimumSignificantDigits,
283 MAX_DIGITS_VALUE, MAX_DIGITS_VALUE, errorMessage);
284 if (errorMessage.type != ErrorType::NO_ERROR) {
285 return false;
286 }
287 return true;
288 }
289
SetFractionDigitsOptions(const std::unordered_map<std::string,std::string> & options,int32_t mnfdDefault,int32_t mxfdDefault,ErrorMessage & errorMessage)290 bool IntlPluralRules::SetFractionDigitsOptions(const std::unordered_map<std::string, std::string>& options,
291 int32_t mnfdDefault, int32_t mxfdDefault, ErrorMessage& errorMessage)
292 {
293 roundingType = RoundingType::FRACTIONDIGITS;
294 auto mxfdIter = options.find(MAX_FRACTION_DIGITS);
295 if (mxfdIter != options.end()) {
296 int32_t mxfd = 0;
297 mxfd = GetDefaultNumberOption(options, MAX_FRACTION_DIGITS, 0, MAX_FRACTION_DIGITS_VALUE, mxfdDefault,
298 errorMessage);
299 if (errorMessage.type != ErrorType::NO_ERROR) {
300 return false;
301 }
302 mnfdDefault = std::min(mnfdDefault, mxfd);
303 }
304 minimumFractionDigits = GetDefaultNumberOption(options, MIN_FRACTION_DIGITS,
305 0, MAX_FRACTION_DIGITS_VALUE, mnfdDefault, errorMessage);
306 if (errorMessage.type != ErrorType::NO_ERROR) {
307 return false;
308 }
309 int32_t mxfdActualDefault = std::max(minimumFractionDigits, mxfdDefault);
310 maximumFractionDigits = GetDefaultNumberOption(options, MAX_FRACTION_DIGITS,
311 minimumFractionDigits, MAX_FRACTION_DIGITS_VALUE, mxfdActualDefault, errorMessage);
312 if (errorMessage.type != ErrorType::NO_ERROR) {
313 return false;
314 }
315 return true;
316 }
317
SetNumberFormatDigitOptions(const std::unordered_map<std::string,std::string> & options,int32_t mnfdDefault,int32_t mxfdDefault,ErrorMessage & errorMessage)318 bool IntlPluralRules::SetNumberFormatDigitOptions(const std::unordered_map<std::string, std::string>& options,
319 int32_t mnfdDefault, int32_t mxfdDefault, ErrorMessage& errorMessage)
320 {
321 minimumFractionDigits = 0;
322 maximumFractionDigits = 0;
323 minimumSignificantDigits = 0;
324 maximumSignificantDigits = 0;
325 minimumIntegerDigits = GetDefaultNumberOption(options, MIN_INTEGER_DIGITS, 1, MAX_DIGITS_VALUE, 1, errorMessage);
326 if (errorMessage.type != ErrorType::NO_ERROR) {
327 return false;
328 }
329 auto mnsdIter = options.find(MIN_SIGNALE_DIGITS);
330 auto mxsdIter = options.find(MAX_SIGNALE_DIGITS);
331 if (mnsdIter != options.end() || mxsdIter != options.end()) {
332 if (!SetSignificantDigitsOptions(options, errorMessage)) {
333 return false;
334 }
335 return true;
336 }
337 auto mnfdIter = options.find(MIN_FRACTION_DIGITS);
338 auto mxfdIter = options.find(MAX_FRACTION_DIGITS);
339 if (mnfdIter != options.end() || mxfdIter != options.end()) {
340 if (!SetFractionDigitsOptions(options, mnfdDefault, mxfdDefault, errorMessage)) {
341 return false;
342 }
343 } else {
344 roundingType = RoundingType::FRACTIONDIGITS;
345 minimumFractionDigits = mnfdDefault;
346 maximumFractionDigits = mxfdDefault;
347 }
348 return true;
349 }
350
Select(double number,ErrorMessage & errorMessage) const351 std::string IntlPluralRules::Select(double number, ErrorMessage& errorMessage) const
352 {
353 if (!initSuccess || !icuPluralRules) {
354 HILOG_ERROR_I18N("IntlPluralRules::Select: Init failed.");
355 return "";
356 }
357 UErrorCode icuStatus = U_ZERO_ERROR;
358 icu::number::FormattedNumber formatted = icuNumberFormatter.formatDouble(number, icuStatus);
359 if (U_FAILURE(icuStatus)) {
360 errorMessage.type = ErrorType::RANGE_ERROR;
361 errorMessage.message = "invalid resolve number";
362 HILOG_ERROR_I18N("IntlPluralRules::Select: out of range: %{public}lf", number);
363 return "";
364 }
365 icu::UnicodeString uString = icuPluralRules->select(formatted, icuStatus);
366 if (U_FAILURE(icuStatus)) {
367 errorMessage.type = ErrorType::RANGE_ERROR;
368 errorMessage.message = "invalid resolve number";
369 HILOG_ERROR_I18N("IntlPluralRules::Select: out of range: %{public}lf", number);
370 return "";
371 }
372 std::string result;
373 uString.toUTF8String(result);
374 return result;
375 }
376
GetPluralCategories(ResolvedValue & options) const377 bool IntlPluralRules::GetPluralCategories(ResolvedValue& options) const
378 {
379 std::vector<std::string> result;
380 UErrorCode icuStatus = U_ZERO_ERROR;
381 std::unique_ptr<icu::StringEnumeration> categories(icuPluralRules->getKeywords(icuStatus));
382 if (U_FAILURE(icuStatus)) {
383 HILOG_ERROR_I18N("IntlPluralRules::GetPluralCategories: get icu getKeywords failed.");
384 return false;
385 }
386 int32_t count = categories->count(icuStatus);
387 if (U_FAILURE(icuStatus)) {
388 HILOG_ERROR_I18N("IntlPluralRules::GetPluralCategories: get icu categories count failed.");
389 return false;
390 }
391 for (int32_t i = 0; i < count; i++) {
392 const icu::UnicodeString *category = categories->snext(icuStatus);
393 if (U_FAILURE(icuStatus)) {
394 HILOG_ERROR_I18N("IntlPluralRules::GetPluralCategories: get icu category failed.");
395 return false;
396 }
397 std::string value;
398 category->toUTF8String(value);
399 result.push_back(value);
400 }
401 options.pluralCategories = result;
402 return true;
403 }
404
ResolvedOptions() const405 IntlPluralRules::ResolvedValue IntlPluralRules::ResolvedOptions() const
406 {
407 ResolvedValue options;
408 if (!initSuccess || !icuPluralRules) {
409 HILOG_ERROR_I18N("IntlPluralRules::ResolvedOptions: Init failed.");
410 return options;
411 }
412 if (!GetPluralCategories(options)) {
413 return options;
414 }
415 options.locale = localeStr;
416 options.type = type;
417 options.minimumIntegerDigits = minimumIntegerDigits;
418 options.roundingType = roundingType;
419 if (roundingType == RoundingType::SIGNIFICANTDIGITS) {
420 options.minimumDigits = minimumSignificantDigits;
421 options.maximumDigits = maximumSignificantDigits;
422 } else {
423 options.minimumDigits = minimumFractionDigits;
424 options.maximumDigits = maximumFractionDigits;
425 }
426 return options;
427 }
428 } // namespace I18n
429 } // namespace Global
430 } // namespace OHOS
431