1 // Copyright 2013 Gushcha Anton
2 module djtext;
3 import std.experimental.logger;
4 
5 /**
6 * Special locale that doesn't have own locale file.
7 */
8 enum BASE_LOCALE = "en_US";
9 
10 /**
11 * Language file extension
12 */
13 enum LOCALE_EXTESION = ".json";
14 
15 private string _defaultLocale = BASE_LOCALE;
16 private string[string][string] localeMap;
17 private string[][string] fuzzyText;
18 
19 /**
20 *   Returns translated string $(B s) for specified $(B locale). If locale is empty default
21 *   locale will be taken. If locale name is equal to base locale $(B s) string is returned
22 *   without modification.
23 *
24 *   Localization strings are taken from special files previosly loaded into memory.
25 *
26 *   If string $(B s) isn't persists in locale strings it will be put into fuzzy text map.
27 *   Fuzzy strings is saved in separate file for each locale to be translated later.
28 *
29 *   See_Also: BASE_LOCALE, defaultLocale properties.
30 *
31 *   Example:
32 *   --------
33 *   assert(getdtext("Hello, world!", "ru_RU") == "Привет, мир!");
34 *   assert(getdtext("Hello, world!", "es_ES") == "Hola, mundo!");
35 *   assert(getdtext("") == "");
36 *   --------
37 */
38 string getdtext(string s, string locale = "") {
39    import std.algorithm : find;
40 
41    if (locale == "") {
42       locale = defaultLocale;
43    }
44    if (locale == BASE_LOCALE) {
45       return s;
46    }
47 
48    if (locale in localeMap) {
49       auto map = localeMap[locale];
50       if (s in map) {
51          return map[s];
52       }
53    }
54    if (locale !in fuzzyText) {
55       fuzzyText[locale] = [];
56    }
57    if (fuzzyText[locale].find(s) == []) {
58       fuzzyText[locale] ~= s;
59    }
60    return s;
61 }
62 
63 /// Short name for getdtext
64 //alias getdtext _;
65 alias _ = getdtext;
66 
67 /**
68 *   Setups current locale name. If empty string is passed to
69 *   $(B getdtext) then default locale will be taken.
70 *
71 *   Example:
72 *   --------
73 *   defaultLocale = "ru_RU";
74 *   defaultLocale = BASE_LOCALE;
75 *   --------
76 */
77 void defaultLocale(string locale) {
78    _defaultLocale = locale;
79 }
80 
81 /**
82 *   Returns current locale name. If empty string is passed to
83 *   $(B getdtext) then default locale will be taken.
84 */
85 string defaultLocale() {
86    return _defaultLocale;
87 }
88 
89 /**
90 *   Manuall loads localization file with $(B name). May be usefull to
91 *   load localization during program execution.
92 *
93 *   Example:
94 *   --------
95 *   loadLocaleFile("ru_RU");
96 *   loadLocaleFile("es_ES");
97 *   --------
98 */
99 void loadLocaleFile(string name) {
100    import std.path : baseName;
101    import std.file : readText;
102    import std..string : endsWith;
103    import std.json;
104 
105    //import vibe.data.json: Json, parseJsonString;
106 
107    if (!name.endsWith(LOCALE_EXTESION)) {
108       name ~= LOCALE_EXTESION;
109    }
110 
111    auto localeName = baseName(name, LOCALE_EXTESION);
112    if (localeName !in localeMap) {
113       localeMap[localeName] = ["" : ""];
114    }
115    auto map = localeMap[localeName];
116 
117    string jsonString = readText(name);
118    JSONValue json = parseJSON(jsonString);
119 
120    foreach (string k, v; json) {
121       map[k] = v.str;
122    }
123 }
124 
125 private string getFuzzyLocaleFileName(string locale) {
126    return locale ~ ".fuzzy";
127 }
128 
129 void saveFuzzyText() {
130    import std.stdio : File;
131    foreach (locale, strs; fuzzyText) {
132       try {
133          auto file = new File(getFuzzyLocaleFileName(locale), "wr");
134          scope (exit) file.close;
135          file.writeln('{');
136 
137          foreach (i, s; strs) {
138             string row = `    "` ~ s ~ `" : "` ~ s ~ `"`;
139             if (i++ == strs.length - 1) {
140                file.writeln(row);
141             } else {
142                file.writeln(row ~ ",");
143             }
144          }
145          file.write('}');
146       } catch (Exception e) {
147          errorf("Failed to save fuzzy text for locale %s", locale);
148       }
149    }
150 }
151 
152 unittest {
153    loadAllLocales("./locale");
154    defaultLocale = "es";
155 
156    _("Hello, world!");
157    _("Hello, json!");
158    _("Hello, json!", "ru");
159    _("Hello, json!", "it");
160    _("Hello, dj!", "it");
161    saveFuzzyText();
162 }
163 
164 
165 void loadAllLocales(string dir) {
166    import std.algorithm : filter;
167    import std.file : dirEntries, SpanMode;
168 
169    auto locFiles = filter!`endsWith(a.name,".json")`(dirEntries(dir, SpanMode.shallow));
170    foreach (entry; locFiles) {
171       try {
172          loadLocaleFile(entry.name);
173       } catch (Exception e) {
174          import std.stdio;
175 
176          writeln("Failed to load localization file ", entry.name, ". Reason: ", e.msg);
177       }
178    }
179 }
180 
181 unittest {
182    loadAllLocales("./locale");
183    defaultLocale = "ru";
184    assert(_("Hello, world!")  ==  "Привет, мир!");
185    assert(_("Hello, world!", "es")  ==  "Hola, mundo!") ;
186    assert(getdtext("") == "");
187    assert(getdtext("cul") == "cul");
188 }