1 module hunt.framework.i18n.I18n;
2 
3 import std.path;
4 import std.file;
5 import std.algorithm;
6 import std.array;
7 import std.stdio;
8 import std.string;
9 import std.json;
10 
11 import hunt.logging;
12 
13 alias StrStrStr = string[string][string];
14 
15 enum I18N_DEFAULT_LOCALE = "en-us";
16 
17 /**
18  * 
19  */
20 class I18n {
21     private {
22         StrStrStr _res;
23         // __gshared I18n _instance;
24         string _default;
25     }
26 
27     this() {
28         _default = I18N_DEFAULT_LOCALE;
29     }
30 
31     // static I18n instance() {
32     //     if (_instance is null) {
33     //         _instance = new I18n();
34     //     }
35     //     return _instance;
36     // }
37 
38     bool loadLangResources(string path, lazy string ext = "ini") {
39         _isResLoaded = false;
40         if(!path.exists()) {
41             warningf("The resource does not exist: %s", path);
42             return false;
43         }
44         auto resfiles = std.file.dirEntries(path, "*.{" ~ ext ~ "}", SpanMode.depth).filter!(a => a.isFile)
45             .map!(a => std.path.absolutePath(a.name))
46             .array;
47         if (resfiles.length == 0) {
48             logDebug("The lang resource file is empty");
49             return false;
50         }
51 
52         foreach (r; resfiles) {
53             parseResFile(r);
54         }
55         _isResLoaded = true;
56         return true;
57     }
58 
59     bool clear()
60     {
61         this._res = null;
62         return false;
63     }
64 
65     bool clear(string local)
66     {
67         this._res[local] = null;
68         return false;
69     }
70 
71     I18n add(string local, string key, string value)
72     {
73         this._res[local][key] = value;
74         return this;
75     }
76 
77     I18n merge(string local, string[string] data)
78     {
79         foreach(string key, string value; data) {
80             this._res[local][key] = value;
81         }
82         return this;
83     }
84 
85     @property bool isResLoaded() {
86         return _isResLoaded;
87     }
88 
89     private bool _isResLoaded = false;
90 
91     @property StrStrStr resources() {
92         return this._res;
93     }
94 
95     @property defaultLocale(string loc) {
96         this._default = loc;
97     }
98 
99     @property string defaultLocale() {
100         return this._default;
101     }
102 
103     private bool parseResFile(string fileName) {
104         auto f = File(fileName, "r");
105         scope (exit) {
106             f.close();
107         }
108 
109         if (!f.isOpen())
110             return false;
111 
112         string _res_file_name = baseName(fileName, extension(fileName));
113         string _loc = baseName(dirName(fileName));
114 
115         version(HUNT_DEBUG) trace("Reading lang resource file: ", fileName);
116 
117         int line = 1;
118         while (!f.eof()) {
119             scope (exit)
120                 line += 1;
121             string str = f.readln();
122             str = strip(str);
123             if (str.length == 0)
124                 continue;
125             if (str[0] == '#' || str[0] == ';')
126                 continue;
127             auto len = str.length - 1;
128 
129             auto site = str.indexOf("=");
130             if (site == -1) {
131                 import std.format;
132 
133                 throw new Exception(format("the format is erro in file %s, in line %d : string: %s",
134                         fileName, line, str));
135             }
136             string key = str[0 .. site].strip;
137             if (key.length == 0) {
138                 import std.format;
139 
140                 throw new Exception(format("the Key is empty in file %s, in line %d",
141                         fileName, line));
142             }
143             string value = str[site + 1 .. $].strip;
144 
145             this._res[_loc][key] = value; // _res_file_name ~ "." ~
146         }
147         return true;
148     }
149 
150     // TODO: Tasks pending completion -@zhangxueping at 2020-06-05T18:15:42+08:00
151     // 
152     // Add trans()
153 
154 }
155 
156 // private string _local /* = I18N_DEFAULT_LOCALE */ ;
157 
158 // @property string getLocale() {
159 //     if (_local)
160 //         return _local;
161 //     return I18n.instance().defaultLocale;
162 // }
163 
164 // @property setLocale(string _l) {
165 //     _local = toLower(_l);
166 // }
167 
168 // string trans(A...)(string key, lazy A args) {
169 //     import std.format;
170 //     Appender!string buffer;
171 //     string text = _trans(key);
172 //     version(HUNT_DEBUG) tracef("format string: %s, key: %s, args.length: ", text, key, args.length);
173 //     formattedWrite(buffer, text, args);
174 
175 //     return buffer.data;
176 // }
177 
178 // deprecated("Using transWithLocale instead.")
179 // alias transfWithLocale = transWithLocale;
180 
181 // string transWithLocale(A...)(string locale, string key, lazy A args) {
182 //     import std.format;
183 //     Appender!string buffer;
184 //     string text = _transWithLocale(locale, key);
185 //     formattedWrite(buffer, text, args);
186 
187 //     return buffer.data;
188 // }
189 
190 // string transWithLocale(string locale, string key, JSONValue args) {
191 //     import hunt.framework.util.Formatter;
192 //     string text = _transWithLocale(locale, key);
193 //     return StrFormat(text, args);
194 // }
195 
196 // ///key is [filename.key]
197 // private string _trans(string key) {
198 //     string defaultValue = key;
199 //     I18n i18n = I18n.instance();
200 //     if (!i18n.isResLoaded) {
201 //         logWarning("The lang resources haven't loaded yet!");
202 //         return key;
203 //     }
204 
205 //     auto p = getLocale in i18n.resources;
206 //     if (p !is null) {
207 //         return p.get(key, defaultValue);
208 //     }
209 //     logWarning("unsupported local: ", getLocale, ", use default now: ", i18n.defaultLocale);
210 
211 //     p = i18n.defaultLocale in i18n.resources;
212 
213 //     if (p !is null) {
214 //         return p.get(key, defaultValue);
215 //     }
216 
217 //     logWarning("unsupported locale: ", i18n.defaultLocale);
218 
219 //     return defaultValue;
220 // }
221 
222 // ///key is [filename.key]
223 // private string _transWithLocale(string locale, string key) {
224 //     string defaultValue = key;
225 //     I18n i18n = I18n.instance();
226 //     if (!i18n.isResLoaded) {
227 //         logWarning("The lang resources has't loaded yet!");
228 //         return key;
229 //     }
230 
231 //     auto p = locale in i18n.resources;
232 //     if (p !is null) {
233 //         return p.get(key, defaultValue);
234 //     }
235 //     logWarning("unsupported locale: ", locale, ", use default now: ", i18n.defaultLocale);
236 
237 //     p = i18n.defaultLocale in i18n.resources;
238 
239 //     if (p !is null) {
240 //         return p.get(key, defaultValue);
241 //     }
242 
243 //     logDebug("unsupported locale: ", i18n.defaultLocale);
244 
245 //     return defaultValue;
246 // }
247 
248 // unittest{
249 
250 //     I18n i18n = I18n.instance();
251 //     i18n.loadLangResources("./resources/translations");
252 //     i18n.defaultLocale = "en-us";
253 //     writeln(i18n.resources);
254 
255 //     ///
256 //     setLocale("en-br");
257 //     assert( trans("message.hello-world") == "Hello, world");
258 
259 //     ///
260 //     setLocale("zh-cn");
261 //     assert( trans("email.subject") == "收件人");
262 
263 //     setLocale("en-us");
264 //     assert( trans("email.subject") == "email.subject");
265 
266 //     assert(trans("message.title") == "%s Demo");
267 //     assert(transf("message.title", "Hunt") == "Hunt Demo");
268 
269 //     assert(transfWithLocale("zh-cn", "message.title", "Hunt") == "Hunt 示例");
270 // }
271