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 }