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
22namespace Updater {
23namespace Lang {
24constexpr int MIN_LVL = 0; // 0 : min resource level
25constexpr int MAX_LVL = 2; // 2 : max resource level
26constexpr 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
29std::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
36const std::unordered_map<std::string, Language> LanguageUI::LOCALES {
37    {"zh", Language::CHINESE},
38    {"en", Language::ENGLISH},
39    {"es", Language::SPANISH}
40};
41
42LanguageUI::LanguageUI() : strMap_ {}, res_ {}, langRes_ {}, language_ {Language::ENGLISH}
43{
44    res_.resize(MAX_LVL + 1);
45}
46
47void LanguageUI::SetDefaultLanguage(Language language)
48{
49    defaultLanguage_ = language;
50}
51
52LanguageUI &LanguageUI::GetInstance()
53{
54    static LanguageUI instance;
55    return instance;
56}
57
58bool 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
68bool 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
77bool 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
93bool 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
129bool 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
138const 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
150bool 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
173Language LanguageUI::ParseLanguage() const
174{
175    Language DEFAULT_LOCALE = defaultLanguage_;
176#ifndef UPDATER_UT
177    //read language type(en-Latn-US/zh-Hans) from misc
178    constexpr const char *CHINSES_LANGUAGE_PREFIX = "zh";
179    constexpr const char *ENGLISH_LANGUAGE_PREFIX = "en";
180    struct UpdaterPara para {};
181    if (!ReadUpdaterParaMisc(para)) {
182        LOG(ERROR) << "ReadUpdaterParaMisc failed";
183        return DEFAULT_LOCALE;
184    }
185    if (strcmp(para.language, "") == 0) {
186        LOG(INFO) << "Language in misc is empty";
187        return Language::CHINESE;
188    } else if (strncmp(para.language, CHINSES_LANGUAGE_PREFIX, strlen(CHINSES_LANGUAGE_PREFIX)) == 0) {
189        return Language::CHINESE;
190    } else if (strncmp(para.language, ENGLISH_LANGUAGE_PREFIX, strlen(ENGLISH_LANGUAGE_PREFIX)) == 0) {
191        return Language::ENGLISH;
192    }
193#endif
194    constexpr size_t localeLen = 2; // zh|es|en
195    std::string realPath {};
196    if (!Utils::PathToRealPath(langRes_.localeFile, realPath)) {
197        LOG(ERROR) << "get real path failed";
198        return DEFAULT_LOCALE;
199    }
200
201    std::ifstream ifs(realPath);
202    std::string content {std::istreambuf_iterator<char> {ifs}, {}};
203    const std::string &locale = content.substr(0, localeLen);
204    if (auto it = LOCALES.find(locale); it != LOCALES.end()) {
205        return it->second;
206    }
207    LOG(ERROR) << "locale " << locale << " not recognized";
208    return DEFAULT_LOCALE;
209}
210
211Language LanguageUI::GetCurLanguage() const
212{
213    return language_;
214}
215}
216} // namespace Updater
217