1// Aseprite
2// Copyright (C) 2016-2018 David Capello
3//
4// This program is distributed under the terms of
5// the End-User License Agreement for Aseprite.
6
7#ifdef HAVE_CONFIG_H
8#include "config.h"
9#endif
10
11#include "app/i18n/strings.h"
12
13#include "app/app.h"
14#include "app/extensions.h"
15#include "app/pref/preferences.h"
16#include "app/resource_finder.h"
17#include "app/xml_document.h"
18#include "app/xml_exception.h"
19#include "base/fs.h"
20#include "cfg/cfg.h"
21
22namespace app {
23
24static Strings* singleton = nullptr;
25static const char* kDefLanguage = "en";
26
27// static
28void Strings::createInstance(Preferences& pref,
29 Extensions& exts)
30{
31 ASSERT(!singleton);
32 singleton = new Strings(pref, exts);
33}
34
35// static
36Strings* Strings::instance()
37{
38 return singleton;
39}
40
41Strings::Strings(Preferences& pref,
42 Extensions& exts)
43 : m_pref(pref)
44 , m_exts(exts)
45{
46 loadLanguage(currentLanguage());
47}
48
49std::set<std::string> Strings::availableLanguages() const
50{
51 std::set<std::string> result;
52
53 // Add languages in data/strings/
54 ResourceFinder rf;
55 rf.includeDataDir("strings");
56 while (rf.next()) {
57 if (!base::is_directory(rf.filename()))
58 continue;
59
60 for (const auto& fn : base::list_files(rf.filename())) {
61 const std::string langId = base::get_file_title(fn);
62 result.insert(langId);
63 }
64 }
65
66 // Add languages in extensions
67 for (const auto& ext : m_exts) {
68 if (ext->isEnabled() &&
69 ext->hasLanguages()) {
70 for (const auto& langId : ext->languages())
71 result.insert(langId.first);
72 }
73 }
74
75 ASSERT(result.find(kDefLanguage) != result.end());
76 return result;
77}
78
79std::string Strings::currentLanguage() const
80{
81 return m_pref.general.language();
82}
83
84void Strings::setCurrentLanguage(const std::string& langId)
85{
86 // Do nothing (same language)
87 if (currentLanguage() == langId)
88 return;
89
90 m_pref.general.language(langId);
91 loadLanguage(langId);
92
93 LanguageChange();
94}
95
96void Strings::loadLanguage(const std::string& langId)
97{
98 m_strings.clear();
99 loadStringsFromDataDir(kDefLanguage);
100 if (langId != kDefLanguage) {
101 loadStringsFromDataDir(langId);
102 loadStringsFromExtension(langId);
103 }
104}
105
106void Strings::loadStringsFromDataDir(const std::string& langId)
107{
108 // Load the English language file from the Aseprite data directory (so we have the most update list of strings)
109 LOG("I18N: Loading strings/%s.ini file\n", langId.c_str());
110 ResourceFinder rf;
111 rf.includeDataDir(("strings/" + langId + ".ini").c_str());
112 if (!rf.findFirst()) {
113 LOG("strings/%s.ini was not found", langId.c_str());
114 return;
115 }
116
117 loadStringsFromFile(rf.filename());
118}
119
120void Strings::loadStringsFromExtension(const std::string& langId)
121{
122 std::string fn = m_exts.languagePath(langId);
123 if (!fn.empty() && base::is_file(fn))
124 loadStringsFromFile(fn);
125}
126
127void Strings::loadStringsFromFile(const std::string& fn)
128{
129 cfg::CfgFile cfg;
130 cfg.load(fn);
131
132 std::vector<std::string> sections;
133 std::vector<std::string> keys;
134 cfg.getAllSections(sections);
135 for (const auto& section : sections) {
136 keys.clear();
137 cfg.getAllKeys(section.c_str(), keys);
138
139 std::string textId = section;
140 textId.push_back('.');
141 for (auto key : keys) {
142 textId.append(key);
143 m_strings[textId] = cfg.getValue(section.c_str(), key.c_str(), "");
144
145 //TRACE("I18N: Reading string %s -> %s\n", textId.c_str(), m_strings[textId].c_str());
146
147 textId.erase(section.size()+1);
148 }
149 }
150}
151
152const std::string& Strings::translate(const char* id) const
153{
154 auto it = m_strings.find(id);
155 if (it != m_strings.end())
156 return it->second;
157 else
158 return m_strings[id] = id;
159}
160
161} // namespace app
162