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 }