mirror of
https://gitee.com/openharmony/global_i18n
synced 2024-11-27 17:21:23 +00:00
!403 增加SystemLocaleManager接口
Merge pull request !403 from sunyaozu/master
This commit is contained in:
commit
6605abffaa
@ -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"
|
||||
|
46
frameworks/intl/include/locale_compare.h
Normal file
46
frameworks/intl/include/locale_compare.h
Normal file
@ -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 <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
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<std::string> scriptLocales;
|
||||
static std::map<std::string, std::string> hantParent;
|
||||
static std::map<std::string, std::string> latnParent;
|
||||
static std::map<std::string, std::string> extendedHantParent;
|
||||
static std::map<std::string, std::string> extendedLatnParent;
|
||||
};
|
||||
} // namespace I18n
|
||||
} // namespace Global
|
||||
} // namespace OHOS
|
||||
#endif
|
@ -50,6 +50,9 @@ public:
|
||||
static bool CheckPermission();
|
||||
static bool SetUsingLocalDigit(bool flag);
|
||||
static bool GetUsingLocalDigit();
|
||||
static std::unordered_set<std::string> GetBlockedLanguages();
|
||||
static std::unordered_set<std::string> GetBlockedRegions();
|
||||
static std::unordered_set<std::string> 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<std::string> &forbiddenRegions);
|
||||
static std::unordered_set<std::string> supportedLocales;
|
||||
static std::unordered_set<std::string> supportedRegions;
|
||||
static std::unordered_set<std::string> blockedLanguages;
|
||||
static std::unordered_set<std::string> blockedRegions;
|
||||
static std::unordered_map<std::string, std::unordered_set<std::string>> blockedLanguageRegions;
|
||||
static std::unordered_set<std::string> whiteLanguages;
|
||||
static std::map<std::string, std::string> supportedDialectLocales;
|
||||
static std::unordered_map<std::string, std::string> dialectMap;
|
||||
|
96
frameworks/intl/include/system_locale_manager.h
Normal file
96
frameworks/intl/include/system_locale_manager.h
Normal file
@ -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 <string>
|
||||
#include <vector>
|
||||
#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<LocaleItem> sort result.
|
||||
*/
|
||||
std::vector<LocaleItem> GetLanguageInfoArray(const std::vector<std::string> &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<LocaleItem> sort result.
|
||||
*/
|
||||
std::vector<LocaleItem> GetCountryInfoArray(const std::vector<std::string> &countries, const SortOptions &options);
|
||||
|
||||
private:
|
||||
void SortLocaleItemList(std::vector<LocaleItem> &localeItemList, const SortOptions &options);
|
||||
std::unique_ptr<TabooUtils> tabooUtils;
|
||||
static const char* SIM_COUNTRY_CODE_KEY;
|
||||
static constexpr int CONFIG_LEN = 128;
|
||||
};
|
||||
} // namespace I18n
|
||||
} // namespace Global
|
||||
} // namespace OHOS
|
||||
#endif
|
77
frameworks/intl/include/taboo.h
Normal file
77
frameworks/intl/include/taboo.h
Normal file
@ -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 <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<std::string>& collation);
|
||||
std::vector<std::string> QueryKeyFallBack(const std::string& key);
|
||||
std::tuple<std::string, std::string> LanguageFallBack(const std::string& language);
|
||||
void ReadResourceList();
|
||||
std::string GetLanguageFromFileName(const std::string& fileName);
|
||||
|
||||
std::set<std::string> supportedRegions; // Indicates which regions support name replacement using taboo data.
|
||||
std::set<std::string> supportedLanguages; // Indicates which languages support name replacement using taboo data.
|
||||
// cache the name replacement taboo data of different locale.
|
||||
std::map<std::string, std::map<std::string, std::string>> localeTabooData;
|
||||
std::map<std::string, std::string> 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
|
41
frameworks/intl/include/taboo_utils.h
Normal file
41
frameworks/intl/include/taboo_utils.h
Normal file
@ -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<Taboo> GetLatestTaboo();
|
||||
std::shared_ptr<Taboo> systemTaboo;
|
||||
std::string systemTabooDataPath = "/system/etc/taboo/";
|
||||
};
|
||||
} // namespace I18n
|
||||
} // namespace Global
|
||||
} // namespace OHOS
|
||||
#endif
|
254
frameworks/intl/src/locale_compare.cpp
Normal file
254
frameworks/intl/src/locale_compare.cpp
Normal file
@ -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<std::string> LocaleCompare::scriptLocales {
|
||||
"zh", "en", "es", "pt"
|
||||
};
|
||||
std::map<std::string, std::string> LocaleCompare::hantParent {
|
||||
{ "zh-MO", "zh-Hant-HK" }
|
||||
};
|
||||
std::map<std::string, std::string> 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<std::string, std::string> LocaleCompare::extendedHantParent {};
|
||||
std::map<std::string, std::string> 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
|
@ -73,6 +73,9 @@ const char *LocaleConfig::rootTag = "languages";
|
||||
const char *LocaleConfig::secondRootTag = "lang";
|
||||
unordered_set<string> LocaleConfig::supportedLocales;
|
||||
unordered_set<string> LocaleConfig::supportedRegions;
|
||||
unordered_set<string> LocaleConfig::blockedLanguages;
|
||||
unordered_set<string> LocaleConfig::blockedRegions;
|
||||
unordered_map<string, unordered_set<string>> LocaleConfig::blockedLanguageRegions;
|
||||
unordered_set<string> LocaleConfig::whiteLanguages;
|
||||
unordered_map<string, string> 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::AppMgrClient>();
|
||||
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<string> &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<string> &src, const unordered_set<string> &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<string> 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<string> 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<std::string> LocaleConfig::GetBlockedLanguages()
|
||||
{
|
||||
return blockedLanguages;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> LocaleConfig::GetBlockedRegions()
|
||||
{
|
||||
return blockedRegions;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> LocaleConfig::GetLanguageBlockedRegions()
|
||||
{
|
||||
std::string systemLanguage = LocaleConfig::GetSystemLanguage();
|
||||
if (blockedLanguageRegions.find(systemLanguage) != blockedLanguageRegions.end()) {
|
||||
return blockedLanguageRegions[systemLanguage];
|
||||
}
|
||||
std::unordered_set<std::string> emptyResult;
|
||||
return emptyResult;
|
||||
}
|
||||
} // namespace I18n
|
||||
} // namespace Global
|
||||
} // namespace OHOS
|
||||
|
153
frameworks/intl/src/system_locale_manager.cpp
Normal file
153
frameworks/intl/src/system_locale_manager.cpp
Normal file
@ -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<TabooUtils>();
|
||||
}
|
||||
|
||||
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<LocaleItem> SystemLocaleManager::GetLanguageInfoArray(const std::vector<std::string> &languages,
|
||||
const SortOptions &options)
|
||||
{
|
||||
std::unordered_set<std::string> blockedLanguages = LocaleConfig::GetBlockedLanguages();
|
||||
std::vector<LocaleItem> 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<LocaleItem> SystemLocaleManager::GetCountryInfoArray(const std::vector<std::string> &countries,
|
||||
const SortOptions &options)
|
||||
{
|
||||
std::unordered_set<std::string> blockedRegions = LocaleConfig::GetBlockedRegions();
|
||||
std::unordered_set<std::string> blockedLanguageRegions = LocaleConfig::GetLanguageBlockedRegions();
|
||||
std::vector<LocaleItem> 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<LocaleItem> &localeItemList, const SortOptions &options)
|
||||
{
|
||||
std::vector<std::string> collatorLocaleTags { options.localeTag };
|
||||
std::map<std::string, std::string> 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
|
272
frameworks/intl/src/taboo.cpp
Normal file
272
frameworks/intl/src/taboo.cpp
Normal file
@ -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 <filesystem>
|
||||
#include <sys/stat.h>
|
||||
#include <string.h>
|
||||
#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<std::string> 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<std::string> 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<const xmlChar*>(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<const xmlChar*>(NAME_TAG);
|
||||
const xmlChar* valueTag = reinterpret_cast<const xmlChar*>(VALUE_TAG);
|
||||
while (cur != nullptr && xmlStrcmp(cur->name, reinterpret_cast<const xmlChar*>(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<const char*>(name);
|
||||
std::string valueStr = reinterpret_cast<const char*>(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<std::string, std::string> data;
|
||||
data[name] = value;
|
||||
localeTabooData[locale] = data;
|
||||
}
|
||||
}
|
||||
|
||||
void Taboo::SplitValue(const std::string& value, std::set<std::string>& 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<std::string> Taboo::QueryKeyFallBack(const std::string& key)
|
||||
{
|
||||
std::vector<std::string> fallback;
|
||||
fallback.push_back(key + "_r_all");
|
||||
return fallback;
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string> 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
|
50
frameworks/intl/src/taboo_utils.cpp
Normal file
50
frameworks/intl/src/taboo_utils.cpp
Normal file
@ -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<Taboo>(systemTabooDataPath);
|
||||
}
|
||||
|
||||
TabooUtils::~TabooUtils()
|
||||
{
|
||||
}
|
||||
|
||||
std::string TabooUtils::ReplaceCountryName(const std::string& region, const std::string& displayLanguage,
|
||||
const std::string& name)
|
||||
{
|
||||
std::shared_ptr<Taboo> 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> taboo = GetLatestTaboo();
|
||||
return taboo->ReplaceLanguageName(language, displayLanguage, name);
|
||||
}
|
||||
|
||||
std::shared_ptr<Taboo> TabooUtils::GetLatestTaboo()
|
||||
{
|
||||
return systemTaboo;
|
||||
}
|
||||
} // namespace I18n
|
||||
} // namespace Global
|
||||
} // namespace OHOS
|
@ -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<std::string> &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<LocaleItem> &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> indexUtil_ = nullptr;
|
||||
std::unique_ptr<I18nTimeZone> timezone_ = nullptr;
|
||||
std::unique_ptr<I18nNormalizer> normalizer_ = nullptr;
|
||||
std::unique_ptr<SystemLocaleManager> systemLocaleManager_ = nullptr;
|
||||
};
|
||||
} // namespace I18n
|
||||
} // namespace Global
|
||||
|
@ -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<I18nAddon> obj = nullptr;
|
||||
obj = std::make_unique<I18nAddon>();
|
||||
status =
|
||||
napi_wrap(env, thisVar, reinterpret_cast<void *>(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<SystemLocaleManager>();
|
||||
|
||||
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<std::string> languageList;
|
||||
GetStringArrayFromJsParam(env, argv[0], languageList);
|
||||
SortOptions options;
|
||||
GetSortOptionsFromJsParam(env, argv[1], options);
|
||||
|
||||
I18nAddon *obj = nullptr;
|
||||
status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
|
||||
if (status != napi_ok || !obj || !obj->systemLocaleManager_) {
|
||||
HiLog::Error(LABEL, "Get SystemLocaleManager object failed");
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<LocaleItem> 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<std::string> countryList;
|
||||
GetStringArrayFromJsParam(env, argv[0], countryList);
|
||||
SortOptions options;
|
||||
GetSortOptionsFromJsParam(env, argv[1], options);
|
||||
|
||||
I18nAddon *obj = nullptr;
|
||||
status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
|
||||
if (status != napi_ok || !obj || !obj->systemLocaleManager_) {
|
||||
HiLog::Error(LABEL, "Get SystemLocaleManager object failed");
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<LocaleItem> 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<std::string> &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<LocaleItem> &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<int32_t>(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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user