• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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