1 // Written in the D programming language. 2 3 /** 4 DLANGUI library. 5 6 This module contains internationalization support implementation. 7 8 Translation files contain of simple key=value pair lines. 9 10 STRING_RESOURCE_ID=Translation text. 11 12 Supports fallback to another translation file (e.g. default language). 13 14 15 16 Synopsis: 17 18 ---- 19 import dlangui.core.i18n; 20 21 // use global i18n object to get translation for string ID 22 dstring translated = i18n.get("STR_FILE_OPEN"); 23 24 // UIString type can hold either string resource id or dstring raw value. 25 UIString text; 26 27 // assign resource id as string 28 text = "ID_FILE_EXIT"; 29 // or assign raw value as dstring 30 text = "some text"d; 31 32 // i18n.get() will automatically be invoked when getting UIString value (e.g. using alias this). 33 dstring translated = text; 34 35 ---- 36 37 Copyright: Vadim Lopatin, 2014 38 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 39 Authors: $(WEB coolreader.org, Vadim Lopatin) 40 */ 41 module dlangui.core.i18n; 42 43 import dlangui.core.types; 44 import dlangui.core.logger; 45 import std.utf; 46 47 /// container for UI string - either raw value or string resource ID 48 struct UIString { 49 /// if not null, use it, otherwise lookup by id 50 private dstring _value; 51 /// id to find value in translator 52 private string _id; 53 54 /// create string with i18n resource id 55 this(string id) { 56 _id = id; 57 } 58 /// create string with raw value 59 this(dstring value) { 60 _value = value; 61 } 62 63 64 65 @property string id() const { return _id; } 66 @property void id(string ID) { 67 _id = ID; 68 _value = null; 69 } 70 /// get value (either raw or translated by id) 71 @property dstring value() const { 72 if (_value !is null) 73 return _value; 74 if (_id is null) 75 return null; 76 // translate ID to dstring 77 return i18n.get(_id); 78 } 79 /// set raw value 80 @property void value(dstring newValue) { 81 _value = newValue; 82 } 83 /// assign raw value 84 ref UIString opAssign(dstring rawValue) { 85 _value = rawValue; 86 _id = null; 87 return this; 88 } 89 /// assign ID 90 ref UIString opAssign(string ID) { 91 _id = ID; 92 _value = null; 93 return this; 94 } 95 /// default conversion to dstring 96 alias value this; 97 } 98 99 public __gshared UIStringTranslator i18n = new UIStringTranslator(); 100 //static shared this() { 101 // i18n = new UIStringTranslator(); 102 //} 103 104 class UIStringTranslator { 105 private UIStringList _main; 106 private UIStringList _fallback; 107 private string _resourceDir; 108 /// get i18n resource directory 109 @property string resourceDir() { return _resourceDir; } 110 /// set i18n resource directory 111 @property void resourceDir(string dir) { _resourceDir = dir; } 112 /// looks for i18n directory inside one of passed dirs, and uses first found as directory to read i18n files from 113 string findTranslationsDir(string[] dirs ...) { 114 import std.file; 115 foreach(dir; dirs) { 116 string path = appendPath(dir, "i18n/"); 117 if (exists(path) && isDir(path)) { 118 _resourceDir = path; 119 return _resourceDir; 120 } 121 } 122 return null; 123 } 124 125 /// convert resource path - зкуpend resource dir if necessary 126 string convertResourcePath(string filename) { 127 if (filename is null) 128 return null; 129 bool hasPathDelimiters = false; 130 foreach(char ch; filename) 131 if (ch == '/' || ch == '\\') 132 hasPathDelimiters = true; 133 if (!hasPathDelimiters && _resourceDir !is null) 134 return _resourceDir ~ filename; 135 return filename; 136 } 137 138 this() { 139 _main = new UIStringList(); 140 _fallback = new UIStringList(); 141 } 142 /// load translation file(s) 143 bool load(string mainFilename, string fallbackFilename = null) { 144 _main.clear(); 145 _fallback.clear(); 146 bool res = _main.load(convertResourcePath(mainFilename)); 147 if (fallbackFilename !is null) { 148 res = _fallback.load(convertResourcePath(fallbackFilename)) || res; 149 } 150 return res; 151 } 152 /// translate string ID to string (returns "UNTRANSLATED: id" for missing values) 153 dstring get(string id) { 154 if (id is null) 155 return null; 156 dstring s = _main.get(id); 157 if (s !is null) 158 return s; 159 s = _fallback.get(id); 160 if (s !is null) 161 return s; 162 return "UNTRANSLATED: "d ~ toUTF32(id); 163 } 164 } 165 166 /// UI string translator 167 class UIStringList { 168 private dstring[string] _map; 169 /// remove all items 170 void clear() { 171 _map.clear(); 172 } 173 /// set item value 174 void set(string id, dstring value) { 175 _map[id] = value; 176 } 177 /// get item value, null if translation is not found for id 178 dstring get(string id) const { 179 if (id in _map) 180 return _map[id]; 181 return null; 182 } 183 /// load strings from stream 184 bool load(std.stream.InputStream stream) { 185 clear(); 186 dlangui.core.linestream.LineStream lines = dlangui.core.linestream.LineStream.create(stream, ""); 187 int count = 0; 188 for (;;) { 189 dchar[] s = lines.readLine(); 190 if (s is null) 191 break; 192 int eqpos = -1; 193 int firstNonspace = -1; 194 int lastNonspace = -1; 195 for (int i = 0; i < s.length; i++) 196 if (s[i] == '=') { 197 eqpos = i; 198 break; 199 } else if (s[i] != ' ' && s[i] != '\t') { 200 if (firstNonspace == -1) 201 firstNonspace = i; 202 lastNonspace = i; 203 } 204 if (eqpos > 0 && firstNonspace != -1) { 205 string id = toUTF8(s[firstNonspace .. lastNonspace + 1]); 206 dstring value = s[eqpos + 1 .. $].dup; 207 set(id, value); 208 count++; 209 } 210 } 211 return count > 0; 212 } 213 214 /// load strings from file (utf8, id=value lines) 215 bool load(string filename) { 216 import std.stream; 217 import std.file; 218 try { 219 Log.d("Loading string resources from file ", filename); 220 if (!exists(filename) || !isFile(filename)) { 221 Log.e("File does not exist: ", filename); 222 return false; 223 } 224 std.stream.File f = new std.stream.File(filename); 225 scope(exit) { f.close(); } 226 return load(f); 227 } catch (StreamFileException e) { 228 Log.e("Cannot read string resources from file ", filename); 229 } 230 return false; 231 } 232 }