From 510f88595f2a1e4c6fd93349ea901173c9d2eae0 Mon Sep 17 00:00:00 2001 From: sunyaozu Date: Fri, 28 Apr 2023 11:56:05 +0800 Subject: [PATCH] provide SystemLocaleManager interface Signed-off-by: sunyaozu --- frameworks/intl/BUILD.gn | 4 + frameworks/intl/include/locale_compare.h | 46 +++ frameworks/intl/include/locale_config.h | 7 + .../intl/include/system_locale_manager.h | 96 +++++++ frameworks/intl/include/taboo.h | 77 +++++ frameworks/intl/include/taboo_utils.h | 41 +++ frameworks/intl/src/locale_compare.cpp | 254 ++++++++++++++++ frameworks/intl/src/locale_config.cpp | 53 +++- frameworks/intl/src/system_locale_manager.cpp | 153 ++++++++++ frameworks/intl/src/taboo.cpp | 272 ++++++++++++++++++ frameworks/intl/src/taboo_utils.cpp | 50 ++++ interfaces/js/kits/include/i18n_addon.h | 15 + interfaces/js/kits/src/i18n_addon.cpp | 263 ++++++++++++++++- 13 files changed, 1324 insertions(+), 7 deletions(-) create mode 100644 frameworks/intl/include/locale_compare.h create mode 100644 frameworks/intl/include/system_locale_manager.h create mode 100644 frameworks/intl/include/taboo.h create mode 100644 frameworks/intl/include/taboo_utils.h create mode 100644 frameworks/intl/src/locale_compare.cpp create mode 100644 frameworks/intl/src/system_locale_manager.cpp create mode 100644 frameworks/intl/src/taboo.cpp create mode 100644 frameworks/intl/src/taboo_utils.cpp diff --git a/frameworks/intl/BUILD.gn b/frameworks/intl/BUILD.gn index 120a425e..93da8191 100644 --- a/frameworks/intl/BUILD.gn +++ b/frameworks/intl/BUILD.gn @@ -90,6 +90,7 @@ ohos_shared_library("intl_util") { "src/i18n_normalizer.cpp", "src/i18n_timezone.cpp", "src/index_util.cpp", + "src/locale_compare.cpp", "src/locale_config.cpp", "src/locale_info.cpp", "src/measure_data.cpp", @@ -97,6 +98,9 @@ ohos_shared_library("intl_util") { "src/phone_number_format.cpp", "src/plural_rules.cpp", "src/relative_time_format.cpp", + "src/system_locale_manager.cpp", + "src/taboo.cpp", + "src/taboo_utils.cpp", "src/utils.cpp", ] version_script = "libintl_util.map" diff --git a/frameworks/intl/include/locale_compare.h b/frameworks/intl/include/locale_compare.h new file mode 100644 index 00000000..99a49f78 --- /dev/null +++ b/frameworks/intl/include/locale_compare.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef OHOS_GLOBAL_I18N_LOCALE_COMPARE_H +#define OHOS_GLOBAL_I18N_LOCALE_COMPARE_H + +#include +#include +#include + +namespace OHOS { +namespace Global { +namespace I18n { +class LocaleCompare { +public: + static int32_t Compare(const std::string& localeTag1, const std::string& localeTag2); + +private: + static bool IsSameLanguage(const std::string& langTag1, const std::string& langTag2); + static bool IsSameEnglishScript(const std::string& scriptTag1, const std::string& scriptTag2); + static bool HasMapRelation(const std::string& languageTag, const std::string& localeTag1, + const std::string& localeTag2); + static std::string hantSegment; + static std::string latnSegment; + static std::string qaagSegment; + static std::set scriptLocales; + static std::map hantParent; + static std::map latnParent; + static std::map extendedHantParent; + static std::map extendedLatnParent; +}; +} // namespace I18n +} // namespace Global +} // namespace OHOS +#endif \ No newline at end of file diff --git a/frameworks/intl/include/locale_config.h b/frameworks/intl/include/locale_config.h index 223aec06..e3685d9a 100644 --- a/frameworks/intl/include/locale_config.h +++ b/frameworks/intl/include/locale_config.h @@ -50,6 +50,9 @@ public: static bool CheckPermission(); static bool SetUsingLocalDigit(bool flag); static bool GetUsingLocalDigit(); + static std::unordered_set GetBlockedLanguages(); + static std::unordered_set GetBlockedRegions(); + static std::unordered_set GetLanguageBlockedRegions(); private: static bool IsValidLanguage(const std::string &language); @@ -100,8 +103,12 @@ private: static std::string GetDsiplayLanguageWithDialect(const std::string &language, const std::string &displayLocale); static std::string ComputeLocale(const std::string &displayLocale); static void ReadLangData(const char *langDataPath); + static void ProcessForbiddenRegions(const std::unordered_set &forbiddenRegions); static std::unordered_set supportedLocales; static std::unordered_set supportedRegions; + static std::unordered_set blockedLanguages; + static std::unordered_set blockedRegions; + static std::unordered_map> blockedLanguageRegions; static std::unordered_set whiteLanguages; static std::map supportedDialectLocales; static std::unordered_map dialectMap; diff --git a/frameworks/intl/include/system_locale_manager.h b/frameworks/intl/include/system_locale_manager.h new file mode 100644 index 00000000..47cb0688 --- /dev/null +++ b/frameworks/intl/include/system_locale_manager.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OHOS_GLOBAL_SYSTEM_LOCALE_MANAGER_H +#define OHOS_GLOBAL_SYSTEM_LOCALE_MANAGER_H + +#include +#include +#include "collator.h" +#include "taboo_utils.h" + +namespace OHOS { +namespace Global { +namespace I18n { +enum SuggestionType { + // language or region is not suggested with system region or system language. + SUGGESTION_TYPE_NONE = 0, + + // language is suggested with system region or region is suggested with system language. + SUGGESTION_TYPE_RELATED = 1, + + // language is suggested with sim card region. + SUGGESTION_TYPE_SIM = 2, +}; + +struct LocaleItem { + // language or region code. + std::string id; + + // the suggestion type of language or region. + SuggestionType suggestionType; + + // the name of language or region in specified language. + std::string displayName; + + // the name of language or region in local language. + std::string localName; +}; + +struct SortOptions { + // locale use to sort language or region. + std::string localeTag; + + // whether to use local name to sort language or region. + bool isUseLocalName; + + // whether to put the suggested item at the top + bool isSuggestedFirst; +}; + +class SystemLocaleManager { +public: + SystemLocaleManager(); + ~SystemLocaleManager(); + + /** + * @brief sort input languages according to SortOptions and obtain sorted result. + * + * @param languages input language array to be sorted. + * @param options sort options. + * @return std::vector sort result. + */ + std::vector GetLanguageInfoArray(const std::vector &languages, + const SortOptions &options); + + /** + * @brief sort input countries according to SortOptions and obtain sorted result. + * + * @param regions input country array to be sorted. + * @param options sort options. + * @return std::vector sort result. + */ + std::vector GetCountryInfoArray(const std::vector &countries, const SortOptions &options); + +private: + void SortLocaleItemList(std::vector &localeItemList, const SortOptions &options); + std::unique_ptr tabooUtils; + static const char* SIM_COUNTRY_CODE_KEY; + static constexpr int CONFIG_LEN = 128; +}; +} // namespace I18n +} // namespace Global +} // namespace OHOS +#endif \ No newline at end of file diff --git a/frameworks/intl/include/taboo.h b/frameworks/intl/include/taboo.h new file mode 100644 index 00000000..80516dca --- /dev/null +++ b/frameworks/intl/include/taboo.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OHOS_GLOBAL_I18N_TABOO_H +#define OHOS_GLOBAL_I18N_TABOO_H + +#include +#include +#include +#include + +namespace OHOS { +namespace Global { +namespace I18n { +enum DataFileType { + CONFIG_FILE, + DATA_FILE +}; + +class Taboo { +public: + Taboo(); + Taboo(const std::string& path); + ~Taboo(); + std::string ReplaceCountryName(const std::string& region, const std::string& displayLanguage, + const std::string& name); + std::string ReplaceLanguageName(const std::string& language, const std::string& displayLanguage, + const std::string& name); + +private: + void ParseTabooData(const std::string& path, DataFileType fileType, const std::string& Locale = ""); + void ProcessTabooConfigData(const std::string& name, const std::string& value); + void ProcessTabooLocaleData(const std::string& locale, const std::string& name, const std::string& value); + void SplitValue(const std::string& value, std::set& collation); + std::vector QueryKeyFallBack(const std::string& key); + std::tuple LanguageFallBack(const std::string& language); + void ReadResourceList(); + std::string GetLanguageFromFileName(const std::string& fileName); + + std::set supportedRegions; // Indicates which regions support name replacement using taboo data. + std::set supportedLanguages; // Indicates which languages support name replacement using taboo data. + // cache the name replacement taboo data of different locale. + std::map> localeTabooData; + std::map resources; // Indicates which locales are supported to find taboo data. + + std::string tabooDataPath = ""; + std::string tabooConfigFileName = "taboo-config.xml"; + std::string tabooLocaleDataFileName = "taboo-data.xml"; + std::string filePathSplitor = "/"; + std::string supportedRegionsTag = "regions"; // supported regions key in taboo-config.xml + std::string supportedLanguagesTag = "languages"; // supported languages key in taboo-config.xml + std::string regionKey = "region_"; // start part of region name replacement data key. + std::string languageKey = "language_"; // start part of language name replacement data key. + bool isTabooDataExist = false; + + static const char* ROOT_TAG; + static const char* ITEM_TAG; + static const char* NAME_TAG; + static const char* VALUE_TAG; + char tabooDataSplitor = ','; +}; +} // namespace I18n +} // namespace Global +} // namespace OHOS +#endif \ No newline at end of file diff --git a/frameworks/intl/include/taboo_utils.h b/frameworks/intl/include/taboo_utils.h new file mode 100644 index 00000000..a84f5a69 --- /dev/null +++ b/frameworks/intl/include/taboo_utils.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OHOS_GLOBAL_I18N_TABOO_UTILS_H +#define OHOS_GLOBAL_I18N_TABOO_UTILS_H + +#include "taboo.h" + +namespace OHOS { +namespace Global { +namespace I18n { +class TabooUtils { +public: + TabooUtils(); + ~TabooUtils(); + std::string ReplaceCountryName(const std::string& region, const std::string& displayLanguage, + const std::string& name); + std::string ReplaceLanguageName(const std::string& language, const std::string& displayLanguage, + const std::string& name); + +private: + std::shared_ptr GetLatestTaboo(); + std::shared_ptr systemTaboo; + std::string systemTabooDataPath = "/system/etc/taboo/"; +}; +} // namespace I18n +} // namespace Global +} // namespace OHOS +#endif \ No newline at end of file diff --git a/frameworks/intl/src/locale_compare.cpp b/frameworks/intl/src/locale_compare.cpp new file mode 100644 index 00000000..30938b06 --- /dev/null +++ b/frameworks/intl/src/locale_compare.cpp @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hilog/log.h" +#include "locale_compare.h" +#include "ohos/init_data.h" +#include "unicode/locid.h" + +namespace OHOS { +namespace Global { +namespace I18n { +static constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, 0xD001E00, "LocaleCompare" }; +using namespace OHOS::HiviewDFX; +std::string LocaleCompare::hantSegment = "-Hant-"; +std::string LocaleCompare::latnSegment = "-Latn-"; +std::string LocaleCompare::qaagSegment = "-Qaag-"; +std::set LocaleCompare::scriptLocales { + "zh", "en", "es", "pt" +}; +std::map LocaleCompare::hantParent { + { "zh-MO", "zh-Hant-HK" } +}; +std::map LocaleCompare::latnParent { + { "en-150", "en-001" }, + { "en-AG", "en-001" }, + { "en-AI", "en-001" }, + { "en-AU", "en-001" }, + { "en-BB", "en-001" }, + { "en-BE", "en-001" }, + { "en-BM", "en-001" }, + { "en-BS", "en-001" }, + { "en-BZ", "en-001" }, + { "en-CC", "en-001" }, + { "en-CK", "en-001" }, + { "en-CX", "en-001" }, + { "en-DG", "en-001" }, + { "en-ER", "en-001" }, + { "en-FK", "en-001" }, + { "en-FM", "en-001" }, + { "en-GB", "en-001" }, + { "en-GD", "en-001" }, + { "en-GG", "en-001" }, + { "en-GI", "en-001" }, + { "en-GY", "en-001" }, + { "en-HK", "en-001" }, + { "en-IE", "en-001" }, + { "en-IM", "en-001" }, + { "en-IN", "en-001" }, + { "en-IO", "en-001" }, + { "en-JE", "en-001" }, + { "en-KI", "en-001" }, + { "en-KN", "en-001" }, + { "en-KY", "en-001" }, + { "en-LC", "en-001" }, + { "en-LR", "en-001" }, + { "en-LS", "en-001" }, + { "en-MM", "en-001" }, + { "en-MO", "en-001" }, + { "en-MS", "en-001" }, + { "en-MT", "en-001" }, + { "en-MY", "en-001" }, + { "en-NF", "en-001" }, + { "en-NR", "en-001" }, + { "en-NU", "en-001" }, + { "en-NZ", "en-001" }, + { "en-PG", "en-001" }, + { "en-PK", "en-001" }, + { "en-PN", "en-001" }, + { "en-PW", "en-001" }, + { "en-SB", "en-001" }, + { "en-SC", "en-001" }, + { "en-SD", "en-001" }, + { "en-SG", "en-001" }, + { "en-SH", "en-001" }, + { "en-SL", "en-001" }, + { "en-SS", "en-001" }, + { "en-SX", "en-001" }, + { "en-SZ", "en-001" }, + { "en-TC", "en-001" }, + { "en-TK", "en-001" }, + { "en-TT", "en-001" }, + { "en-TV", "en-001" }, + { "en-VC", "en-001" }, + { "en-VG", "en-001" }, + { "en-WS", "en-001" }, + { "en-ZG", "en-001" }, + { "es-AR", "es-419" }, + { "es-BO", "es-419" }, + { "es-BR", "es-419" }, + { "es-CL", "es-419" }, + { "es-CO", "es-419" }, + { "es-CR", "es-419" }, + { "es-CU", "es-419" }, + { "es-DO", "es-419" }, + { "es-EC", "es-419" }, + { "es-GT", "es-419" }, + { "es-HN", "es-419" }, + { "es-MX", "es-419" }, + { "es-NI", "es-419" }, + { "es-PA", "es-419" }, + { "es-PE", "es-419" }, + { "es-PR", "es-419" }, + { "es-PY", "es-419" }, + { "es-SV", "es-419" }, + { "es-US", "es-419" }, + { "es-UY", "es-419" }, + { "es-VE", "es-419" }, + { "pt-AO", "pt-PT" }, + { "pt-CH", "pt-PT" }, + { "pt-CV", "pt-PT" }, + { "pt-GQ", "pt-PT" }, + { "pt-GW", "pt-PT" }, + { "pt-LU", "pt-PT" }, + { "pt-MO", "pt-PT" }, + { "pt-MZ", "pt-PT" }, + { "pt-ST", "pt-PT" }, + { "pt-TL", "pt-PT" } +}; +std::map LocaleCompare::extendedHantParent {}; +std::map LocaleCompare::extendedLatnParent {}; + +int32_t LocaleCompare::Compare(const std::string& localeTag1, const std::string& localeTag2) +{ + UErrorCode status = U_ZERO_ERROR; + icu::Locale locale1 = icu::Locale::forLanguageTag(icu::StringPiece(localeTag1), status); + icu::Locale locale2 = icu::Locale::forLanguageTag(icu::StringPiece(localeTag2), status); + int32_t segmentScore = 3; + int32_t mapScore = 0; + int32_t score = 0; + std::string language1 = locale1.getLanguage(); + std::string language2 = locale2.getLanguage(); + if (IsSameLanguage(language1, language2)) { + score += segmentScore; + } else { + return -1; + } + std::string localeBaseName1 = locale1.getBaseName(); + std::string localeBaseName2 = locale2.getBaseName(); + if (HasMapRelation(language1, localeBaseName1, localeBaseName2)) { + return mapScore; + } + std::string region1 = locale1.getCountry(); + std::string region2 = locale2.getCountry(); + locale1.addLikelySubtags(status); + locale2.addLikelySubtags(status); + if (U_FAILURE(status)) { + HiLog::Error(LABEL, "LocaleCompare::Compare add likely subtags failed."); + return -1; + } + std::string script1 = locale1.getScript(); + std::string script2 = locale2.getScript(); + if (script1.compare(script2) == 0 || (language1.compare("en") == 0 && IsSameEnglishScript(script1, script2))) { + score += segmentScore; + if (region2.length() == 0) { + ++score; + } + } else { + return -1; + } + if (region1.length() != 0 && region1.compare(region2) == 0) { + score += segmentScore; + } + return score; +} + +bool LocaleCompare::IsSameLanguage(const std::string& langTag1, const std::string& langTag2) +{ + if (langTag1.compare(langTag2) == 0) { + return true; + } + if (langTag1.compare("tl") == 0 && langTag2.compare("fil") == 0) { + return true; + } + if (langTag1.compare("fil") == 0 && langTag2.compare("tl") == 0) { + return true; + } + return false; +} + +bool LocaleCompare::IsSameEnglishScript(const std::string& scriptTag1, const std::string& scriptTag2) +{ + if (scriptTag1.compare("Qaag") == 0 && scriptTag2.compare("Latn") == 0) { + return true; + } + if (scriptTag1.compare("Latn") == 0 && scriptTag2.compare("Qaag") == 0) { + return true; + } + return false; +} + +bool LocaleCompare::HasMapRelation(const std::string& languageTag, const std::string& localeTag1, + const std::string& localeTag2) +{ + if (scriptLocales.find(languageTag) == scriptLocales.end()) { + return false; + } + if (hantParent.find(localeTag1) != hantParent.end()) { + if (localeTag2.compare(hantParent[localeTag1]) == 0) { + return true; + } + } + if (latnParent.find(localeTag1) != latnParent.end()) { + if (localeTag2.compare(hantParent[localeTag1]) == 0) { + return true; + } + } + if (extendedHantParent.size() == 0) { + for (auto it = hantParent.begin(); it != hantParent.end(); ++it) { + std::string key = it->first; + size_t languageLength = key.find("-"); + std::string language = key.substr(0, languageLength); + std::string region = key.substr(languageLength + 1); + extendedHantParent[language + hantSegment + region] = it->second; + } + } + if (extendedHantParent.find(localeTag1) != extendedHantParent.end()) { + if (localeTag2.compare(extendedHantParent[localeTag1]) == 0) { + return true; + } + } + if (extendedLatnParent.size() == 0) { + for (auto it = latnParent.begin(); it != latnParent.end(); ++it) { + std::string key = it->first; + size_t languageLength = key.find("-"); + std::string language = key.substr(0, languageLength); + std::string region = key.substr(languageLength + 1); + extendedHantParent[language + latnSegment + region] = it->second; + if (language.compare("en") == 0) { + extendedLatnParent[language + qaagSegment + region] = it->second; + } + } + } + if (extendedLatnParent.find(localeTag1) != extendedLatnParent.end()) { + if (localeTag2.compare(extendedLatnParent[localeTag1]) == 0) { + return true; + } + } + return false; +} +} // namespace I18n +} // namespace Global +} // OHOS diff --git a/frameworks/intl/src/locale_config.cpp b/frameworks/intl/src/locale_config.cpp index a0b6e2a6..c071a703 100644 --- a/frameworks/intl/src/locale_config.cpp +++ b/frameworks/intl/src/locale_config.cpp @@ -73,6 +73,9 @@ const char *LocaleConfig::rootTag = "languages"; const char *LocaleConfig::secondRootTag = "lang"; unordered_set LocaleConfig::supportedLocales; unordered_set LocaleConfig::supportedRegions; +unordered_set LocaleConfig::blockedLanguages; +unordered_set LocaleConfig::blockedRegions; +unordered_map> LocaleConfig::blockedLanguageRegions; unordered_set LocaleConfig::whiteLanguages; unordered_map LocaleConfig::dialectMap { { "es-Latn-419", "es-Latn-419" }, @@ -371,7 +374,7 @@ bool LocaleConfig::SetSystemLanguage(const string &language) std::string oldLanguage = GetSystemLanguage(); if (SetParameter(LANGUAGE_KEY, language.data()) == 0) { bool isUpdateSuccess = UpdateSystemLocale(language); - if(isUpdateSuccess) { + if (isUpdateSuccess) { #ifdef SUPPORT_GRAPHICS auto appMgrClient = std::make_unique(); AppExecFwk::Configuration configuration; @@ -627,6 +630,24 @@ void LocaleConfig::GetListFromFile(const char *path, const char *resourceName, u xmlFreeDoc(doc); } +void LocaleConfig::ProcessForbiddenRegions(const unordered_set &forbiddenRegions) +{ + for (auto it = forbiddenRegions.begin(); it != forbiddenRegions.end(); ++it) { + size_t pos = it->rfind("-"); + std::string language = it->substr(0, pos); + std::string region = it->substr(pos + 1); + if (language.compare("*") == 0) { + blockedRegions.insert(region); + } else { + if (blockedLanguageRegions.find(language) == blockedLanguageRegions.end()) { + blockedLanguageRegions[language] = { region }; + } else { + blockedLanguageRegions[language].insert(region); + } + } + } +} + void LocaleConfig::Expunge(unordered_set &src, const unordered_set &another) { for (auto iter = src.begin(); iter != src.end();) { @@ -644,11 +665,11 @@ bool LocaleConfig::InitializeLists() GetListFromFile(SUPPORTED_REGIONS_PATH, SUPPORTED_REGIONS_NAME, supportedRegions); unordered_set forbiddenRegions; GetListFromFile(FORBIDDEN_REGIONS_PATH, FORBIDDEN_REGIONS_NAME, forbiddenRegions); - Expunge(supportedRegions, forbiddenRegions); + ProcessForbiddenRegions(forbiddenRegions); + Expunge(supportedRegions, blockedRegions); GetListFromFile(WHITE_LANGUAGES_PATH, WHITE_LANGUAGES_NAME, whiteLanguages); - unordered_set forbiddenLanguages; - GetListFromFile(FORBIDDEN_LANGUAGES_PATH, FORBIDDEN_LANGUAGES_NAME, forbiddenLanguages); - Expunge(whiteLanguages, forbiddenLanguages); + GetListFromFile(FORBIDDEN_LANGUAGES_PATH, FORBIDDEN_LANGUAGES_NAME, blockedLanguages); + Expunge(whiteLanguages, blockedLanguages); GetListFromFile(SUPPORTED_LOCALES_PATH, SUPPORTED_LOCALES_NAME, supportedLocales); return true; } @@ -1114,7 +1135,7 @@ bool LocaleConfig::UpdateSystemLocale(const std::string &language) return false; } std::string region = systemLocale.getCountry(); - + std::string extendParam; size_t pos = systemLocaleTag.find("-u-"); if (pos < systemLocaleTag.length()) { @@ -1134,6 +1155,26 @@ bool LocaleConfig::UpdateSystemLocale(const std::string &language) } return SetSystemLocale(finalLocaleTag); } + +std::unordered_set LocaleConfig::GetBlockedLanguages() +{ + return blockedLanguages; +} + +std::unordered_set LocaleConfig::GetBlockedRegions() +{ + return blockedRegions; +} + +std::unordered_set LocaleConfig::GetLanguageBlockedRegions() +{ + std::string systemLanguage = LocaleConfig::GetSystemLanguage(); + if (blockedLanguageRegions.find(systemLanguage) != blockedLanguageRegions.end()) { + return blockedLanguageRegions[systemLanguage]; + } + std::unordered_set emptyResult; + return emptyResult; +} } // namespace I18n } // namespace Global } // namespace OHOS diff --git a/frameworks/intl/src/system_locale_manager.cpp b/frameworks/intl/src/system_locale_manager.cpp new file mode 100644 index 00000000..05185f55 --- /dev/null +++ b/frameworks/intl/src/system_locale_manager.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hilog/log.h" +#include "locale_config.h" +#include "system_locale_manager.h" +#include "utils.h" + +namespace OHOS { +namespace Global { +namespace I18n { +static constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, 0xD001E00, "SystemLocaleManager" }; +using namespace OHOS::HiviewDFX; + +const char* SystemLocaleManager::SIM_COUNTRY_CODE_KEY = "telephony.sim.countryCode0"; + +SystemLocaleManager::SystemLocaleManager() +{ + tabooUtils = std::make_unique(); +} + +SystemLocaleManager::~SystemLocaleManager() +{ +} + +/** + * Language arrays are sorted according to the following steps: + * 1. Remove blocked languages. + * 2. Compute language locale displayName; If options.isUseLocalName is true, compute language local displayName. + * replace display name with taboo data. + * 3. Judge whether language is suggested with system region and sim card region. + * 4. Sort the languages use locale displayName, local displyName and suggestion infomation. + */ +std::vector SystemLocaleManager::GetLanguageInfoArray(const std::vector &languages, + const SortOptions &options) +{ + std::unordered_set blockedLanguages = LocaleConfig::GetBlockedLanguages(); + std::vector localeItemList; + for (auto it = languages.begin(); it != languages.end(); ++it) { + if (blockedLanguages.find(*it) != blockedLanguages.end()) { + continue; + } + std::string languageDisplayName = LocaleConfig::GetDisplayLanguage(*it, options.localeTag, true); + languageDisplayName = tabooUtils->ReplaceLanguageName(*it, options.localeTag, languageDisplayName); + std::string languageNativeName; + if (options.isUseLocalName) { + languageNativeName = LocaleConfig::GetDisplayLanguage(*it, *it, true); + languageNativeName = tabooUtils->ReplaceLanguageName(*it, *it, languageNativeName); + } + bool isSuggestedWithSystemRegion = LocaleConfig::IsSuggested(*it, LocaleConfig::GetSystemRegion()); + std::string simRegion = ReadSystemParameter(SIM_COUNTRY_CODE_KEY, CONFIG_LEN); + bool isSuggestedWithSimRegion = false; + if (simRegion.length() > 0) { + isSuggestedWithSimRegion = LocaleConfig::IsSuggested(*it, simRegion); + } + SuggestionType suggestionType = SuggestionType::SUGGESTION_TYPE_NONE; + if (isSuggestedWithSimRegion) { + suggestionType = SuggestionType::SUGGESTION_TYPE_SIM; + } else if (isSuggestedWithSystemRegion) { + suggestionType = SuggestionType::SUGGESTION_TYPE_RELATED; + } + LocaleItem item { *it, suggestionType, languageDisplayName, languageNativeName }; + localeItemList.push_back(item); + } + SortLocaleItemList(localeItemList, options); + return localeItemList; +} + +/** + * Region arrays are sorted according to the following steps: + * 1. Remove blocked regions and blocked regions under system Language. + * 2. Compute region locale displayName; replace display name with taboo data. + * 3. Judge whether region is suggested with system language. + * 4. Sort the regions use locale displayName and suggestion infomation. + */ +std::vector SystemLocaleManager::GetCountryInfoArray(const std::vector &countries, + const SortOptions &options) +{ + std::unordered_set blockedRegions = LocaleConfig::GetBlockedRegions(); + std::unordered_set blockedLanguageRegions = LocaleConfig::GetLanguageBlockedRegions(); + std::vector localeItemList; + for (auto it = countries.begin(); it != countries.end(); ++it) { + if (blockedRegions.find(*it) != blockedRegions.end() || blockedLanguageRegions.find(*it) != + blockedLanguageRegions.end()) { + continue; + } + std::string regionDisplayName = LocaleConfig::GetDisplayRegion(*it, options.localeTag, true); + regionDisplayName = tabooUtils->ReplaceCountryName(*it, options.localeTag, regionDisplayName); + bool isSuggestedRegion = LocaleConfig::IsSuggested(LocaleConfig::GetSystemLanguage(), *it); + SuggestionType suggestionType = SuggestionType::SUGGESTION_TYPE_NONE; + if (isSuggestedRegion) { + suggestionType = SuggestionType::SUGGESTION_TYPE_RELATED; + } + LocaleItem item { *it, suggestionType, regionDisplayName, "" }; + localeItemList.push_back(item); + } + SortLocaleItemList(localeItemList, options); + return localeItemList; +} + +void SystemLocaleManager::SortLocaleItemList(std::vector &localeItemList, const SortOptions &options) +{ + std::vector collatorLocaleTags { options.localeTag }; + std::map collatorOptions {}; + Collator *collator = new Collator(collatorLocaleTags, collatorOptions); + auto compareFunc = [collator](LocaleItem item1, LocaleItem item2) { + if (item1.suggestionType < item2.suggestionType) { + return false; + } else if (item1.suggestionType > item2.suggestionType) { + return true; + } + CompareResult result = CompareResult::INVALID; + if (item1.localName.length() != 0) { + result = collator->Compare(item1.localName, item2.localName); + if (result == CompareResult::SMALLER) { + return true; + } + if (result == CompareResult::INVALID) { + HiLog::Error(LABEL, "SystemLocaleManager: invalid compare result for local name."); + } + return false; + } + result = collator->Compare(item1.displayName, item2.displayName); + if (result == CompareResult::SMALLER) { + return true; + } + if (result == CompareResult::INVALID) { + HiLog::Error(LABEL, "SystemLocaleManager: invalid compare result for display name."); + } + return false; + }; + if (options.isSuggestedFirst) { + std::sort(localeItemList.begin(), localeItemList.end(), compareFunc); + } else { + std::sort(localeItemList.begin(), localeItemList.end(), compareFunc); + } + delete collator; +} +} // I18n +} // Global +} // OHOS \ No newline at end of file diff --git a/frameworks/intl/src/taboo.cpp b/frameworks/intl/src/taboo.cpp new file mode 100644 index 00000000..5e5ffd3a --- /dev/null +++ b/frameworks/intl/src/taboo.cpp @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include "hilog/log.h" +#include "libxml/globals.h" +#include "libxml/tree.h" +#include "libxml/xmlstring.h" +#include "locale_compare.h" +#include "taboo.h" + +namespace OHOS { +namespace Global { +namespace I18n { +static constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, 0xD001E00, "Taboo" }; +using namespace OHOS::HiviewDFX; +const char* Taboo::ROOT_TAG = "taboo"; +const char* Taboo::ITEM_TAG = "item"; +const char* Taboo::NAME_TAG = "name"; +const char* Taboo::VALUE_TAG = "value"; + +Taboo::Taboo() +{ +} + +Taboo::Taboo(const std::string& path) : tabooDataPath(path) +{ + using std::filesystem::directory_iterator; + + std::string tabooConfigFilePath = tabooDataPath + tabooConfigFileName; + struct stat s; + isTabooDataExist = stat(tabooConfigFilePath.c_str(), &s) == 0; + if (isTabooDataExist) { + // parse taboo-config.xml to obtain supported regions and languages for name replacement. + ParseTabooData(tabooConfigFilePath, DataFileType::CONFIG_FILE); + ReadResourceList(); + } +} + +Taboo::~Taboo() +{ +} + +std::string Taboo::ReplaceCountryName(const std::string& region, const std::string& displayLanguage, + const std::string& name) +{ + if (!isTabooDataExist) { + HiLog::Info(LABEL, "Taboo::ReplaceCountryName Taboo data not exist."); + return name; + } + if (supportedRegions.find(region) == supportedRegions.end()) { + HiLog::Info(LABEL, "Taboo::ReplaceCountryName taboo data don't support region %{public}s", region.c_str()); + return name; + } + std::string key = regionKey + region; + std::vector fallbackRegionKeys = QueryKeyFallBack(key); + std::string fallbackLanguage; + std::string fileName; + std::tie(fallbackLanguage, fileName) = LanguageFallBack(displayLanguage); + if (fallbackLanguage.length() == 0) { + HiLog::Info(LABEL, "Taboo::ReplaceCountryName language %{public}s fallback failed", displayLanguage.c_str()); + return name; + } + if (localeTabooData.find(fallbackLanguage) == localeTabooData.end()) { + localeTabooData[fallbackLanguage] = {}; + std::string localeTabooDataFilePath = tabooDataPath + fileName + filePathSplitor + tabooLocaleDataFileName; + ParseTabooData(localeTabooDataFilePath, DataFileType::DATA_FILE, fallbackLanguage); + } + auto tabooData = localeTabooData[fallbackLanguage]; + for (auto it = fallbackRegionKeys.begin(); it != fallbackRegionKeys.end(); ++it) { + if (tabooData.find(*it) != tabooData.end()) { + return tabooData[*it]; + } + } + HiLog::Info(LABEL, "Taboo::ReplaceCountryName not find taboo data correspond to region %{public}s", + region.c_str()); + return name; +} + +std::string Taboo::ReplaceLanguageName(const std::string& language, const std::string& displayLanguage, + const std::string& name) +{ + if (!isTabooDataExist) { + HiLog::Info(LABEL, "Taboo::ReplaceLanguageName Taboo data not exist."); + return name; + } + if (supportedLanguages.find(language) == supportedLanguages.end()) { + HiLog::Error(LABEL, "Taboo::ReplaceLanguageName taboo data don't support language %{public}s", + language.c_str()); + return name; + } + std::string key = languageKey + language; + std::vector fallbackLanguageKeys = QueryKeyFallBack(key); + std::string fallbackLanguage; + std::string fileName; + std::tie(fallbackLanguage, fileName) = LanguageFallBack(displayLanguage); + if (fallbackLanguage.size() == 0) { + HiLog::Error(LABEL, "Taboo::ReplaceLanguageName language %{public}s fallback failed", displayLanguage.c_str()); + return name; + } + if (localeTabooData.find(fallbackLanguage) == localeTabooData.end()) { + localeTabooData[fallbackLanguage] = {}; + std::string localeTabooDataFilePath = tabooDataPath + fileName + filePathSplitor + tabooLocaleDataFileName; + ParseTabooData(localeTabooDataFilePath, DataFileType::DATA_FILE, fallbackLanguage); + } + auto tabooData = localeTabooData[fallbackLanguage]; + for (auto it = fallbackLanguageKeys.begin(); it != fallbackLanguageKeys.end(); ++it) { + if (tabooData.find(*it) != tabooData.end()) { + return tabooData[*it]; + } + } + HiLog::Error(LABEL, "Taboo::ReplaceLanguageName not find taboo data correspond to language %{public}s", + language.c_str()); + return name; +} + +void Taboo::ParseTabooData(const std::string& path, DataFileType fileType, const std::string& locale) +{ + xmlKeepBlanksDefault(0); + xmlDocPtr doc = xmlParseFile(path.c_str()); + if (doc == nullptr) { + HiLog::Error(LABEL, "Taboo parse taboo data file failed: %{public}s", path.c_str()); + return; + } + xmlNodePtr cur = xmlDocGetRootElement(doc); + if (cur == nullptr || xmlStrcmp(cur->name, reinterpret_cast(ROOT_TAG)) != 0) { + xmlFreeDoc(doc); + HiLog::Error(LABEL, "Taboo get root tag from taboo data file failed: %{public}s", path.c_str()); + return; + } + cur = cur->xmlChildrenNode; + const xmlChar* nameTag = reinterpret_cast(NAME_TAG); + const xmlChar* valueTag = reinterpret_cast(VALUE_TAG); + while (cur != nullptr && xmlStrcmp(cur->name, reinterpret_cast(ITEM_TAG)) == 0) { + xmlChar* name = xmlGetProp(cur, nameTag); + xmlChar* value = xmlGetProp(cur, valueTag); + if (name == nullptr || value == nullptr) { + HiLog::Error(LABEL, "Taboo get name and value property failed: %{public}s", path.c_str()); + cur = cur->next; + continue; + } + std::string nameStr = reinterpret_cast(name); + std::string valueStr = reinterpret_cast(value); + switch (fileType) { + case DataFileType::CONFIG_FILE: + ProcessTabooConfigData(nameStr, valueStr); + case DataFileType::DATA_FILE: + ProcessTabooLocaleData(locale, nameStr, valueStr); + } + xmlFree(name); + xmlFree(value); + cur = cur->next; + } +} + +void Taboo::ProcessTabooConfigData(const std::string& name, const std::string& value) +{ + if (name.compare(supportedRegionsTag) == 0) { + SplitValue(value, supportedRegions); + } else if (name.compare(supportedLanguagesTag) == 0) { + SplitValue(value, supportedLanguages); + } +} + +void Taboo::ProcessTabooLocaleData(const std::string& locale, const std::string& name, const std::string& value) +{ + if (localeTabooData.find(locale) != localeTabooData.end()) { + localeTabooData[locale][name] = value; + } else { + std::map data; + data[name] = value; + localeTabooData[locale] = data; + } +} + +void Taboo::SplitValue(const std::string& value, std::set& collation) +{ + size_t startPos = 0; + while (startPos < value.length()) { + size_t endPos = value.find(tabooDataSplitor, startPos); + if (endPos == std::string::npos) { + collation.insert(value.substr(startPos)); + endPos = value.length(); + } else { + collation.insert(value.substr(startPos, endPos - startPos)); + } + startPos = endPos + 1; + } +} + +std::vector Taboo::QueryKeyFallBack(const std::string& key) +{ + std::vector fallback; + fallback.push_back(key + "_r_all"); + return fallback; +} + +std::tuple Taboo::LanguageFallBack(const std::string& language) +{ + std::string bestMatch; + std::string fileName; + int32_t bestScore = -1; + + for (auto it = resources.begin(); it != resources.end(); ++it) { + std::string resLanguage = it->first; + int32_t score = LocaleCompare::Compare(language, resLanguage); + if (score > bestScore) { + bestMatch = resLanguage; + fileName = it->second; + bestScore = score; + } + } + if (bestScore < 0) { + return std::make_tuple("", ""); + } + return std::make_tuple(bestMatch, fileName); +} + +void Taboo::ReadResourceList() +{ + using std::filesystem::directory_iterator; + struct stat s; + for (const auto &dirEntry : directory_iterator{tabooDataPath}) { + std::string path = dirEntry.path(); + if (stat(path.c_str(), &s) != 0) { + HiLog::Error(LABEL, "get path status failed"); + continue; + } + if (s.st_mode & S_IFDIR) { + std::string fileName = path.substr(tabooDataPath.length()); + std::string language = GetLanguageFromFileName(fileName); + resources[language] = fileName; + } + } +} + +std::string Taboo::GetLanguageFromFileName(const std::string& fileName) +{ + if (fileName.length() == 3) { + return "en"; + } + std::string language = fileName.substr(4); + if (language[0] == 'b' && language[1] == '+') { + language = language.substr(2); + } + size_t pos = language.find("+"); + if (pos != std::string::npos) { + language = language.replace(pos, 1, "-"); + } + pos = language.find("-r"); + if (pos != std::string::npos) { + language = language.replace(pos, 2, "-"); + } + return language; +} +} // namespace I18n +} // namespace Global +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/intl/src/taboo_utils.cpp b/frameworks/intl/src/taboo_utils.cpp new file mode 100644 index 00000000..54981e38 --- /dev/null +++ b/frameworks/intl/src/taboo_utils.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "taboo_utils.h" + +namespace OHOS { +namespace Global { +namespace I18n { +TabooUtils::TabooUtils() +{ + systemTaboo = std::make_shared(systemTabooDataPath); +} + +TabooUtils::~TabooUtils() +{ +} + +std::string TabooUtils::ReplaceCountryName(const std::string& region, const std::string& displayLanguage, + const std::string& name) +{ + std::shared_ptr taboo = GetLatestTaboo(); + return taboo->ReplaceCountryName(region, displayLanguage, name); +} + +std::string TabooUtils::ReplaceLanguageName(const std::string& language, const std::string& displayLanguage, + const std::string& name) +{ + std::shared_ptr taboo = GetLatestTaboo(); + return taboo->ReplaceLanguageName(language, displayLanguage, name); +} + +std::shared_ptr TabooUtils::GetLatestTaboo() +{ + return systemTaboo; +} +} // namespace I18n +} // namespace Global +} // namespace OHOS \ No newline at end of file diff --git a/interfaces/js/kits/include/i18n_addon.h b/interfaces/js/kits/include/i18n_addon.h index b334ebe7..f148ec63 100644 --- a/interfaces/js/kits/include/i18n_addon.h +++ b/interfaces/js/kits/include/i18n_addon.h @@ -29,6 +29,7 @@ #include "number_format.h" #include "phone_number_format.h" #include "preferred_language.h" +#include "system_locale_manager.h" namespace OHOS { namespace Global { @@ -104,6 +105,7 @@ public: static napi_value InitUtil(napi_env env, napi_value exports); static napi_value System(napi_env env, napi_value exports); static napi_value InitI18nNormalizer(napi_env env, napi_value exports); + static napi_value InitSystemLocaleManager(napi_env env, napi_value exports); private: static napi_value PhoneNumberFormatConstructor(napi_env env, napi_callback_info info); @@ -195,6 +197,18 @@ private: static napi_value I18nNormalizerConstructor(napi_env env, napi_callback_info info); static napi_value Normalize(napi_env env, napi_callback_info info); + static napi_value SystemLocaleManagerConstructor(napi_env env, napi_callback_info info); + bool InitSystemLocaleManagerContext(napi_env env, napi_callback_info info); + static napi_value GetLanguageInfoArray(napi_env env, napi_callback_info info); + static napi_value getCountryInfoArray(napi_env env, napi_callback_info info); + static void GetStringArrayFromJsParam(napi_env env, napi_value &jsArray, std::vector &strArray); + static void GetSortOptionsFromJsParam(napi_env env, napi_value &jsOptions, SortOptions &options); + static void GetBoolOptionValue(napi_env env, napi_value &options, const std::string &optionName, bool &boolVal); + static napi_value CreateLocaleItemArray(napi_env env, const std::vector &localeItemList); + static napi_value CreateLocaleItem(napi_env env, const LocaleItem &localeItem); + static napi_value CreateString(napi_env env, const std::string &str); + static napi_value CreateSuggestionType(napi_env env, SuggestionType suggestionType); + static const int32_t NORMALIZER_MODE_NFC = 1; static const int32_t NORMALIZER_MODE_NFD = 2; static const int32_t NORMALIZER_MODE_NFKC = 3; @@ -212,6 +226,7 @@ private: std::unique_ptr indexUtil_ = nullptr; std::unique_ptr timezone_ = nullptr; std::unique_ptr normalizer_ = nullptr; + std::unique_ptr systemLocaleManager_ = nullptr; }; } // namespace I18n } // namespace Global diff --git a/interfaces/js/kits/src/i18n_addon.cpp b/interfaces/js/kits/src/i18n_addon.cpp index 55758545..6b406361 100644 --- a/interfaces/js/kits/src/i18n_addon.cpp +++ b/interfaces/js/kits/src/i18n_addon.cpp @@ -267,7 +267,8 @@ napi_value I18nAddon::Init(napi_env env, napi_value exports) DECLARE_NAPI_PROPERTY("Normalizer", CreateI18nNormalizerObject(env, initStatus)), DECLARE_NAPI_PROPERTY("NormalizerMode", CreateI18NNormalizerModeEnum(env, initStatus)) }; - initStatus = napi_define_properties(env, exports, sizeof(properties) / sizeof(napi_property_descriptor), properties); + initStatus = napi_define_properties(env, exports, sizeof(properties) / sizeof(napi_property_descriptor), + properties); if (initStatus != napi_ok) { HiLog::Error(LABEL, "Failed to set properties at init"); return nullptr; @@ -3921,6 +3922,265 @@ napi_value I18nAddon::Normalize(napi_env env, napi_callback_info info) return result; } +napi_value I18nAddon::InitSystemLocaleManager(napi_env env, napi_value exports) +{ + napi_status status = napi_ok; + napi_property_descriptor properties[] = { + DECLARE_NAPI_FUNCTION("getLanguageInfoArray", GetLanguageInfoArray), + DECLARE_NAPI_FUNCTION("getCountryInfoArray", getCountryInfoArray) + }; + + napi_value constructor = nullptr; + status = napi_define_class(env, "SystemLocaleManager", NAPI_AUTO_LENGTH, SystemLocaleManagerConstructor, nullptr, + sizeof(properties) / sizeof(napi_property_descriptor), properties, &constructor); + if (status != napi_ok) { + HiLog::Error(LABEL, "Define class failed when InitSystemLocaleManager"); + return nullptr; + } + + status = napi_set_named_property(env, exports, "SystemLocaleManager", constructor); + if (status != napi_ok) { + HiLog::Error(LABEL, "Set property failed when InitSystemLocaleManager"); + return nullptr; + } + return exports; +} + +napi_value I18nAddon::SystemLocaleManagerConstructor(napi_env env, napi_callback_info info) +{ + size_t argc = 0; + napi_value argv[0]; + napi_value thisVar = nullptr; + void *data = nullptr; + napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data); + if (status != napi_ok) { + return nullptr; + } + std::unique_ptr obj = nullptr; + obj = std::make_unique(); + status = + napi_wrap(env, thisVar, reinterpret_cast(obj.get()), I18nAddon::Destructor, nullptr, nullptr); + if (status != napi_ok) { + HiLog::Error(LABEL, "Wrap I18nAddon failed"); + return nullptr; + } + if (!obj->InitSystemLocaleManagerContext(env, info)) { + HiLog::Error(LABEL, "Init SystemLocaleManager failed"); + return nullptr; + } + obj.release(); + return thisVar; +} + +bool I18nAddon::InitSystemLocaleManagerContext(napi_env env, napi_callback_info info) +{ + napi_value global = nullptr; + napi_status status = napi_get_global(env, &global); + if (status != napi_ok) { + HiLog::Error(LABEL, "Get global failed"); + return false; + } + env_ = env; + systemLocaleManager_ = std::make_unique(); + + return systemLocaleManager_ != nullptr; +} + +napi_value I18nAddon::GetLanguageInfoArray(napi_env env, napi_callback_info info) +{ + size_t argc = 2; + napi_value argv[2] = { 0 }; + napi_value thisVar = nullptr; + void *data = nullptr; + napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data); + if (status != napi_ok) { + HiLog::Error(LABEL, "can not obtain getLanguageInfoArray function param."); + ErrorUtil::NapiThrow(env, I18N_NOT_FOUND, true); + } + std::vector languageList; + GetStringArrayFromJsParam(env, argv[0], languageList); + SortOptions options; + GetSortOptionsFromJsParam(env, argv[1], options); + + I18nAddon *obj = nullptr; + status = napi_unwrap(env, thisVar, reinterpret_cast(&obj)); + if (status != napi_ok || !obj || !obj->systemLocaleManager_) { + HiLog::Error(LABEL, "Get SystemLocaleManager object failed"); + return nullptr; + } + std::vector localeItemList = obj->systemLocaleManager_->GetLanguageInfoArray(languageList, options); + napi_value result = CreateLocaleItemArray(env, localeItemList); + return result; +} + +napi_value I18nAddon::getCountryInfoArray(napi_env env, napi_callback_info info) +{ + size_t argc = 2; + napi_value argv[2] = { 0 }; + napi_value thisVar = nullptr; + void *data = nullptr; + napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data); + if (status != napi_ok) { + HiLog::Error(LABEL, "can not obtain getCountryInfoArray function param."); + ErrorUtil::NapiThrow(env, I18N_NOT_FOUND, true); + } + std::vector countryList; + GetStringArrayFromJsParam(env, argv[0], countryList); + SortOptions options; + GetSortOptionsFromJsParam(env, argv[1], options); + + I18nAddon *obj = nullptr; + status = napi_unwrap(env, thisVar, reinterpret_cast(&obj)); + if (status != napi_ok || !obj || !obj->systemLocaleManager_) { + HiLog::Error(LABEL, "Get SystemLocaleManager object failed"); + return nullptr; + } + std::vector localeItemList = obj->systemLocaleManager_->GetCountryInfoArray(countryList, options); + napi_value result = CreateLocaleItemArray(env, localeItemList); + return result; +} + +void I18nAddon::GetStringArrayFromJsParam(napi_env env, napi_value &jsArray, std::vector &strArray) +{ + if (jsArray == nullptr) { + HiLog::Error(LABEL, "js string array param not found."); + ErrorUtil::NapiThrow(env, I18N_NOT_FOUND, true); + } + bool isArray = false; + napi_status status = napi_is_array(env, jsArray, &isArray); + if (status != napi_ok || !isArray) { + HiLog::Error(LABEL, "js string array is not an Array."); + ErrorUtil::NapiThrow(env, I18N_NOT_VALID, true); + } + uint32_t arrayLength = 0; + napi_get_array_length(env, jsArray, &arrayLength); + napi_value element = nullptr; + int32_t code = 0; + for (uint32_t i = 0; i < arrayLength; ++i) { + napi_get_element(env, jsArray, i, &element); + std::string str = GetString(env, element, code); + if (code != 0) { + HiLog::Error(LABEL, "can't get string from js array param."); + ErrorUtil::NapiThrow(env, I18N_NOT_VALID, true); + } + strArray.push_back(str); + } +} + +void I18nAddon::GetSortOptionsFromJsParam(napi_env env, napi_value &jsOptions, SortOptions &options) +{ + if (jsOptions == nullptr) { + HiLog::Error(LABEL, "SortOptions js param not found."); + ErrorUtil::NapiThrow(env, I18N_NOT_FOUND, true); + } + std::string localeTag; + GetOptionValue(env, jsOptions, "locale", localeTag); + options.localeTag = localeTag; + bool isUseLocalName; + GetBoolOptionValue(env, jsOptions, "isUseLocalName", isUseLocalName); + options.isUseLocalName = isUseLocalName; + bool isSuggestedFirst; + GetBoolOptionValue(env, jsOptions, "isSuggestedFirst", isSuggestedFirst); + options.isSuggestedFirst = isSuggestedFirst; +} + +void I18nAddon::GetBoolOptionValue(napi_env env, napi_value &options, const std::string &optionName, bool &boolVal) +{ + napi_valuetype type = napi_undefined; + napi_status status = napi_typeof(env, options, &type); + if (status != napi_ok && type != napi_object) { + HiLog::Error(LABEL, "option is not an object"); + ErrorUtil::NapiThrow(env, I18N_NOT_VALID, true); + } + bool hasProperty = false; + status = napi_has_named_property(env, options, optionName.c_str(), &hasProperty); + if (status != napi_ok || !hasProperty) { + HiLog::Error(LABEL, "option don't have property %{public}s", optionName.c_str()); + ErrorUtil::NapiThrow(env, I18N_NOT_VALID, true); + } + napi_value optionValue = nullptr; + status = napi_get_named_property(env, options, optionName.c_str(), &optionValue); + if (status != napi_ok) { + HiLog::Error(LABEL, "get option %{public}s failed", optionName.c_str()); + ErrorUtil::NapiThrow(env, I18N_NOT_VALID, true); + } + napi_get_value_bool(env, optionValue, &boolVal); +} + +napi_value I18nAddon::CreateLocaleItemArray(napi_env env, const std::vector &localeItemList) +{ + napi_value result = nullptr; + napi_status status = napi_create_array_with_length(env, localeItemList.size(), &result); + if (status != napi_ok) { + HiLog::Error(LABEL, "create LocaleItem array failed."); + return nullptr; + } + for (size_t i = 0; i < localeItemList.size(); ++i) { + napi_value item = CreateLocaleItem(env, localeItemList[i]); + status = napi_set_element(env, result, i, item); + if (status != napi_ok) { + HiLog::Error(LABEL, "Failed to set LocaleItem element."); + return nullptr; + } + } + return result; +} + +napi_value I18nAddon::CreateLocaleItem(napi_env env, const LocaleItem &localeItem) +{ + napi_value result; + napi_status status = napi_create_object(env, &result); + if (status != napi_ok) { + HiLog::Error(LABEL, "Create Locale Item object failed."); + return nullptr; + } + status = napi_set_named_property(env, result, "id", CreateString(env, localeItem.id)); + if (status != napi_ok) { + HiLog::Error(LABEL, "Failed to set element id."); + return nullptr; + } + status = napi_set_named_property(env, result, "displayName", CreateString(env, localeItem.displayName)); + if (status != napi_ok) { + HiLog::Error(LABEL, "Failed to set element displayName."); + return nullptr; + } + if (localeItem.localName.length() != 0) { + status = napi_set_named_property(env, result, "localName", CreateString(env, localeItem.localName)); + if (status != napi_ok) { + HiLog::Error(LABEL, "Failed to set element localName."); + return nullptr; + } + } + status = napi_set_named_property(env, result, "suggestionType", CreateSuggestionType(env, localeItem.suggestionType)); + if (status != napi_ok) { + HiLog::Error(LABEL, "Failed to set element suggestionType."); + return nullptr; + } + return result; +} + +napi_value I18nAddon::CreateString(napi_env env, const std::string &str) +{ + napi_value result; + napi_status status = napi_create_string_utf8(env, str.c_str(), NAPI_AUTO_LENGTH, &result); + if (status != napi_ok) { + HiLog::Error(LABEL, "create string js variable failed."); + return nullptr; + } + return result; +} + +napi_value I18nAddon::CreateSuggestionType(napi_env env, SuggestionType suggestionType) +{ + napi_value result; + napi_status status = napi_create_int32(env, static_cast(suggestionType), &result); + if (status != napi_ok) { + HiLog::Error(LABEL, "create SuggestionType failed."); + return nullptr; + } + return result; +} + napi_value Init(napi_env env, napi_value exports) { napi_value val = I18nAddon::Init(env, exports); @@ -3933,6 +4193,7 @@ napi_value Init(napi_env env, napi_value exports) val = I18nAddon::InitCharacter(env, val); val = I18nAddon::InitUtil(env, val); val = I18nAddon::InitI18nNormalizer(env, val); + val = I18nAddon::InitSystemLocaleManager(env, val); return val; }