1 /*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include "language_ui.h"
16
17 #include "json_node.h"
18 #include "log/log.h"
19 #include "utils.h"
20 #include "misc_info/misc_info.h"
21
22 namespace Updater {
23 namespace Lang {
24 constexpr int MIN_LVL = 0; // 0 : min resource level
25 constexpr int MAX_LVL = 2; // 2 : max resource level
26 constexpr const char *DEFAULT_KEY = "DEFAULT_STRING";
27
28 // map value zh/en/spa is used in string.json to specify language type for each string key
29 std::unordered_map<Language, std::string> g_languageDataVars = {
30 {Language::CHINESE, "zh"},
31 {Language::ENGLISH, "en"},
32 {Language::SPANISH, "spa"},
33 };
34
35 // map key zh/es/en is used in locale file to specify locale env for updater
36 const std::unordered_map<std::string, Language> LanguageUI::LOCALES {
37 {"zh", Language::CHINESE},
38 {"en", Language::ENGLISH},
39 {"es", Language::SPANISH}
40 };
41
LanguageUI()42 LanguageUI::LanguageUI() : strMap_ {}, res_ {}, langRes_ {}, language_ {Language::ENGLISH}
43 {
44 res_.resize(MAX_LVL + 1);
45 }
46
SetDefaultLanguage(Language language)47 void LanguageUI::SetDefaultLanguage(Language language)
48 {
49 defaultLanguage_ = language;
50 }
51
GetInstance()52 LanguageUI &LanguageUI::GetInstance()
53 {
54 static LanguageUI instance;
55 return instance;
56 }
57
Init(Language language)58 bool LanguageUI::Init(Language language)
59 {
60 language_ = language;
61 if (!Parse()) {
62 LOG(ERROR) << "parse language resources failed";
63 return false;
64 }
65 return true;
66 }
67
SetRes(const Res & res)68 bool LanguageUI::SetRes(const Res &res)
69 {
70 if (!CheckLevel(res.level)) {
71 return false;
72 }
73 res_[res.level] = res.path;
74 return true;
75 }
76
Parse()77 bool LanguageUI::Parse()
78 {
79 strMap_.clear();
80 for (auto &file : res_) {
81 if (file.empty()) {
82 LOG(WARNING) << "file name empty";
83 continue;
84 }
85 if (!ParseJson(file)) {
86 LOG(ERROR) << "parse file " << file << " error";
87 return false;
88 }
89 }
90 return true;
91 }
92
ParseJson(const std::string & file)93 bool LanguageUI::ParseJson(const std::string &file)
94 {
95 JsonNode root {std::filesystem::path { file }};
96 /*
97 * an example:
98 * {
99 * "LABEL_REBOOT_DEVICE": {
100 * "zh" : "",
101 * "en" : "",
102 * "spa" : ""
103 * }
104 * }
105 * , this is an object node
106 */
107 if (root.Type() != NodeType::OBJECT) {
108 LOG(ERROR) << file << " is invalid, nodetype is " << static_cast<int>(root.Type());
109 return false;
110 }
111 for (auto &node : root) {
112 const JsonNode &strNode = node.get();
113 std::string key = strNode.Key().value_or("");
114 if (key.empty()) {
115 LOG(ERROR) << "key is empty";
116 return false;
117 }
118 if (auto optionalStr = strNode[g_languageDataVars[language_]].As<std::string>();
119 optionalStr != std::nullopt) {
120 strMap_[key] = *optionalStr;
121 continue;
122 }
123 LOG(ERROR) << "dont have a " << g_languageDataVars[language_] << " string";
124 return false;
125 }
126 return true;
127 }
128
CheckLevel(int level)129 bool LanguageUI::CheckLevel(int level)
130 {
131 if (level < MIN_LVL || level > MAX_LVL) {
132 LOG(ERROR) << "level invalid : " << level;
133 return false;
134 }
135 return true;
136 }
137
Translate(const std::string & key) const138 const std::string &LanguageUI::Translate(const std::string &key) const
139 {
140 static std::string emptyStr;
141 if (auto it = strMap_.find(key); it != strMap_.end() && !it->second.empty()) {
142 return it->second;
143 }
144 if (auto it = strMap_.find(DEFAULT_KEY); it != strMap_.end()) {
145 return it->second;
146 }
147 return emptyStr;
148 }
149
LoadLangRes(const JsonNode & node)150 bool LanguageUI::LoadLangRes(const JsonNode &node)
151 {
152 langRes_ = {};
153 if (!Visit<SETVAL>(node[LANG_RES_KEY], langRes_)) {
154 LOG(ERROR) << "parse language res error";
155 return false;
156 }
157 // clear resources
158 std::vector<std::string>{3, ""}.swap(res_);
159 // load resources
160 for (auto &res : langRes_.res) {
161 if (!SetRes(res)) {
162 return false;
163 }
164 }
165 if (!Init(ParseLanguage())) {
166 LOG(ERROR) << "init failed";
167 return false;
168 }
169 LOG(INFO) << "load language resource success";
170 return true;
171 }
172
ParseLangIfZh(const std::string & globalLang) const173 Language LanguageUI::ParseLangIfZh(const std::string &globalLang) const
174 {
175 const std::string ZH_CN_REGION_UNDERLINE_PREFIX = "zh_CN";
176 const std::string ZH_CN_REGION_DASH_PREFIX = "zh-CN";
177 const std::string CN_REGION_UNDERLINE_SUFFIX = "_CN";
178 const std::string CN_REGION_DASH_SUFFIX = "-CN";
179
180 if (globalLang.empty()) {
181 LOG(ERROR) << "globalLang is empty";
182 return Language::CHINESE;
183 }
184 if (globalLang.find("-") == std::string::npos && globalLang.find("_") == std::string::npos) {
185 LOG(ERROR) << "globalLang doesn't contain '-' or '_'";
186 return Language::CHINESE;
187 }
188 if (globalLang.find(ZH_CN_REGION_UNDERLINE_PREFIX) == 0 || globalLang.find(ZH_CN_REGION_DASH_PREFIX) == 0) {
189 LOG(INFO) << "starts with zh_CN or zh-CN";
190 return Language::CHINESE;
191 }
192 if ((globalLang.size() >= CN_REGION_DASH_SUFFIX.size()) &&
193 (globalLang.rfind(CN_REGION_DASH_SUFFIX) == globalLang.size() - CN_REGION_DASH_SUFFIX.size() ||
194 globalLang.rfind(CN_REGION_UNDERLINE_SUFFIX) == globalLang.size() - CN_REGION_UNDERLINE_SUFFIX.size())) {
195 LOG(INFO) << "ends with _CN or -CN";
196 return Language::CHINESE;
197 }
198 return Language::ENGLISH;
199 }
200
ParseLanguage() const201 Language LanguageUI::ParseLanguage() const
202 {
203 Language DEFAULT_LOCALE = defaultLanguage_;
204 #ifndef UPDATER_UT
205 // read locale type(en-Latn-CN/zh-Hans-CN) from misc
206 constexpr const char *CHINESE_LANGUAGE_PREFIX = "zh";
207 constexpr const char *SPANISH_LANGUAGE_PREFIX = "es";
208 struct UpdaterPara para {};
209 if (!ReadUpdaterParaMisc(para)) {
210 LOG(ERROR) << "ReadUpdaterParaMisc failed";
211 return DEFAULT_LOCALE;
212 }
213 if (strcmp(para.language, "") == 0) {
214 LOG(INFO) << "Language in misc is empty";
215 return Language::CHINESE;
216 } else if (strncmp(para.language, CHINESE_LANGUAGE_PREFIX, strlen(CHINESE_LANGUAGE_PREFIX)) == 0) {
217 LOG(INFO) << "para language starts with zh";
218 return ParseLangIfZh(para.language);
219 } else if (strncmp(para.language, SPANISH_LANGUAGE_PREFIX, strlen(SPANISH_LANGUAGE_PREFIX)) == 0) {
220 LOG(INFO) << "parsed language is Spanish";
221 return Language::SPANISH;
222 } else {
223 LOG(INFO) << "parsed language is English";
224 return Language::ENGLISH;
225 }
226 #endif
227 constexpr size_t localeLen = 2; // zh|es|en
228 std::string realPath {};
229 if (!Utils::PathToRealPath(langRes_.localeFile, realPath)) {
230 LOG(ERROR) << "get real path failed";
231 return DEFAULT_LOCALE;
232 }
233
234 std::ifstream ifs(realPath);
235 std::string content {std::istreambuf_iterator<char> {ifs}, {}};
236 const std::string &locale = content.substr(0, localeLen);
237 if (auto it = LOCALES.find(locale); it != LOCALES.end()) {
238 return it->second;
239 }
240 LOG(ERROR) << "locale " << locale << " not recognized";
241 return DEFAULT_LOCALE;
242 }
243
GetCurLanguage() const244 Language LanguageUI::GetCurLanguage() const
245 {
246 return language_;
247 }
248 }
249 } // namespace Updater
250