Mercurial > projects > mde
comparison mde/lookup/Translation.d @ 107:20f7d813bb0f
Translation: now has a file for each locale, instead of a file for each section. Items updated; all strings translated.
New unittest for Translation; I realised the old one wasn't run anymore.
Changed unittest data and conf dirs.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Sun, 30 Nov 2008 17:17:56 +0000 |
parents | 42e241e7be3e |
children | 6acd96f8685f |
comparison
equal
deleted
inserted
replaced
106:7f7b40fed72b | 107:20f7d813bb0f |
---|---|
54 * | 54 * |
55 * Encoding used is UTF-8. | 55 * Encoding used is UTF-8. |
56 */ | 56 */ |
57 class Translation : IDataSection | 57 class Translation : IDataSection |
58 { | 58 { |
59 final char[] name; /// The module/package/... which the instance is for | 59 /** Load the translation for the requested module/package/... |
60 final char[] L10n; /// The localization loaded (e.g. en-GB) | 60 * |
61 * Options (mde.options) must have been loaded before this is run. | |
62 * | |
63 * Params: | |
64 * name The module/package/... to load strings for. | |
65 * | |
66 * Throws: | |
67 * If no localization exists for this name and the current locale (or any locale to be chain- | |
68 * loaded), or an error occurs while loading the database, a L10nLoadException will be thrown. | |
69 */ | |
70 static { | |
71 /** Get Translation instance for _section section. | |
72 * | |
73 * On the first call, loads all Translation sections. | |
74 * | |
75 * These are loaded from data/L10n/locale.mtt where locale is the current locale, as well | |
76 * as any files named for locales specified in the header tag <char[][]|depends=[...]>. | |
77 * | |
78 * On errors, a message is logged and the function continues, likely resulting in some | |
79 * symbol names being untranslated. */ | |
80 Translation get (char[] section) { | |
81 if (sections is null) { | |
82 char[][] files = [miscOpts.L10n()]; // initial locale plus dependencies | |
83 size_t read = 0; // index in files of next file to read | |
84 while (read < files.length) { | |
85 try { | |
86 IReader reader = dataDir.makeMTReader ("L10n/"~files[read++], PRIORITY.HIGH_LOW, null, true); | |
87 assert (reader.dataset.header); | |
88 auto dp = "depends" in reader.dataset.header._charAA; | |
89 if (dp) { // append, making sure no duplicates are inserted | |
90 f1: foreach (dep; *dp) { | |
91 foreach (f; files) | |
92 if (f == dep) | |
93 continue f1; | |
94 files ~= dep; | |
95 } | |
96 } | |
97 reader.dataSecCreator = delegate IDataSection(ID sec) { | |
98 auto p = sec in sections; | |
99 if (p) return *p; | |
100 return new Translation (sec); | |
101 }; | |
102 reader.read; | |
103 } catch (MTException e) { | |
104 logger.error ("Mergetag exception occurred:"); | |
105 logger.error (e.msg); | |
106 } | |
107 } | |
108 } | |
109 auto p = section in sections; | |
110 if (p is null) { | |
111 logger.warn ("Section "~section ~ " not found in files; strings will not be translated."); | |
112 return new Translation (section); | |
113 } | |
114 return *p; | |
115 } | |
116 Translation[char[]] sections; | |
117 } | |
118 | |
119 final char[] section; // ONLY used to log an error message | |
61 | 120 |
62 alias entry opCall; /// Convenience alias | 121 alias entry opCall; /// Convenience alias |
63 | 122 |
64 /** Get the translation for the given identifier. | 123 /** Get the translation for the given identifier. |
65 * If no entry exists, the identifier will be returned. | 124 * If no entry exists, the identifier will be returned. |
92 } else { | 151 } else { |
93 Entry ret; | 152 Entry ret; |
94 ret.name = id; | 153 ret.name = id; |
95 return ret; | 154 return ret; |
96 } | 155 } |
97 } | |
98 | |
99 /** Load the translation for the requested module/package/... | |
100 * | |
101 * Options (mde.options) must have been loaded before this is run. | |
102 * | |
103 * Params: | |
104 * name The module/package/... to load strings for. | |
105 * | |
106 * Throws: | |
107 * If no localization exists for this name and the current locale (or any locale to be chain- | |
108 * loaded), or an error occurs while loading the database, a L10nLoadException will be thrown. | |
109 */ | |
110 static Translation load (char[] name) | |
111 { | |
112 bool[ID] loadedSecs; // set of all locales/sections loaded; used to prevent circular loading | |
113 ID[] secsToLoad // locales/sections to load (dependancies may be added) | |
114 = [cast(ID) miscOpts.L10n]; // start by loading the current locale | |
115 | |
116 Translation transl = new Translation (name, miscOpts.L10n()); | |
117 | |
118 IReader reader; | |
119 try { | |
120 reader = dataDir.makeMTReader (name, PRIORITY.HIGH_LOW); | |
121 /* Note: we don't want to load every translation section depended on to its own class | |
122 * instance, since we want to merge them. So make every mergetag section use the same | |
123 * instance. */ | |
124 reader.dataSecCreator = delegate IDataSection(ID) { | |
125 return transl; | |
126 }; | |
127 | |
128 while (secsToLoad.length) { // while we have sections left to load | |
129 ID sec = secsToLoad[0]; // take current section | |
130 secsToLoad = secsToLoad[1..$]; // and remove from list | |
131 | |
132 if (sec in loadedSecs) continue; // skip if it's already been loaded | |
133 loadedSecs[sec] = true; | |
134 | |
135 reader.read ([sec]); // Do the actual loading | |
136 | |
137 // Add dependancies to front of list: | |
138 secsToLoad = transl.depends ~ secsToLoad; | |
139 } | |
140 // When loop finishes, we're done loading. | |
141 } catch (MTException e) { | |
142 // If an error occurs, log a message but continue (strings will just be untranslated) | |
143 logger.error ("Mergetag exception occurred:"); | |
144 logger.error (e.msg); | |
145 } | |
146 | |
147 delete transl.depends; // Free memory | |
148 transl.depends = []; | |
149 | |
150 return transl; | |
151 } | 156 } |
152 | 157 |
153 static this() { | 158 static this() { |
154 logger = Log.getLogger ("mde.lookup.Translation"); | 159 logger = Log.getLogger ("mde.lookup.Translation"); |
155 } | 160 } |
170 // If the tag already exists, don't replace it | 175 // If the tag already exists, don't replace it |
171 if (cast(char[]) id in entries) return; | 176 if (cast(char[]) id in entries) return; |
172 | 177 |
173 Entry entry = deserialize!(Entry) (dt); | 178 Entry entry = deserialize!(Entry) (dt); |
174 if (entry.name is null) { // This tag is invalid; ignore it | 179 if (entry.name is null) { // This tag is invalid; ignore it |
175 logger.error ("For name "~name~", L10n "~L10n~": tag with ID "~cast(char[])id~" has no data"); | 180 logger.error ("In L10n/*.mtt section "~section~": tag with ID "~cast(char[])id~" has no data"); |
176 return; | 181 return; |
177 } | 182 } |
178 entries[cast(char[]) id] = entry; | 183 entries[cast(char[]) id] = entry; |
179 } else if (tp == "char[][]") { | 184 } else if (tp == "char[][]") { |
180 if (id == cast(ID)"depends") depends = cast(ID[]) parseTo!(char[][]) (dt); | 185 if (id == cast(ID)"depends") depends = cast(ID[]) parseTo!(char[][]) (dt); |
192 char[] name; // The translated string | 197 char[] name; // The translated string |
193 char[] desc; // An optional description | 198 char[] desc; // An optional description |
194 } | 199 } |
195 | 200 |
196 private: | 201 private: |
197 /* Sets name and L10n. | 202 /* Sets name and ensures only Translation's static functions can create instances. */ |
198 * | 203 this (char[] n) { |
199 * Also ensures only load() can create instances. */ | 204 section = n; |
200 this (char[] n, char[] l) { | 205 sections[n] = this; |
201 name = n; | |
202 L10n = l; | |
203 } | 206 } |
204 | 207 |
205 //BEGIN Data | 208 //BEGIN Data |
206 static Logger logger; | 209 static Logger logger; |
207 | 210 |
209 | 212 |
210 ID[] depends; // dependancy sections (only used while loading) | 213 ID[] depends; // dependancy sections (only used while loading) |
211 //END Data | 214 //END Data |
212 | 215 |
213 debug (mdeUnitTest) unittest { | 216 debug (mdeUnitTest) unittest { |
214 /* Relies on file: conf/L10n/i18nUnitTest.mtt | 217 // Relies on files in unittest/data/L10n: locale-x.mtt for x in 1..4 |
215 * Contents: | |
216 ********* | |
217 {MT01} | |
218 {test-1} | |
219 <entry|Str1=["Test 1"]> | |
220 <char[][]|depends=["test-2"]> | |
221 {test-2} | |
222 <entry|Str1=["Test 2"]> | |
223 <entry|Str2=["Test 3","Description",bogus,"entries",56]> | |
224 *********/ | |
225 | 218 |
226 // Hack a specific locale... | 219 // Hack a specific locale. Also to allow unittest to run without init. |
227 // Also to allow unittest to run without init. | 220 StringContent realL10n = miscOpts.L10n; |
228 TextContent realL10n = miscOpts.L10n; | 221 miscOpts.L10n = new StringContent ("L10n", "locale-1"); |
229 miscOpts.L10n = new TextContent ("L10n", "test-1"); | |
230 | 222 |
231 Translation transl = load ("unittest/Translation"); | 223 // Struct tests |
232 | 224 Translation t = get ("section-2"); |
233 // Simple get-string, check dependancy's entry doesn't override | 225 Entry e = t.getStruct ("entry-1"); |
234 assert (transl.entry ("Str1") == "Test 1"); | 226 assert (e.name == "Test 1"); |
235 | 227 assert (e.desc == "Description"); |
236 // Entry included from dependancy with description | 228 e = t.getStruct ("entry-2"); |
237 char[] desc; | 229 assert (e.name == "Test 2"); |
238 assert (transl.entry ("Str2", desc) == "Test 3"); | 230 assert (e.desc is null); |
239 assert (desc == "Description"); | 231 |
240 | 232 // Dependency tests. Priority should be 1,2,3,4 (entries should come from first file in list that they appear in). |
241 // No entry: fallback to identifier string | 233 t = get ("section-1"); |
242 assert (transl.entry ("Str3") == "Str3"); | 234 char[] d = "1"; |
243 | 235 assert (t.entry ("file-1", d) == "locale-1"); |
244 // No checks for version info since it's not functionality of this module. | 236 assert (d is null); |
245 // Only check extra entries are allowed but ignored. | 237 assert (t.entry ("file-2", d) == "locale-2"); |
246 | 238 assert (d == "desc2"); |
239 assert (t.entry ("file-3") == "locale-3"); | |
240 assert (t.entry ("file-4") == "locale-4"); | |
241 | |
247 // Restore | 242 // Restore |
248 delete miscOpts.L10n; | 243 delete miscOpts.L10n; |
249 miscOpts.L10n = realL10n; | 244 miscOpts.L10n = realL10n; |
245 sections = null; | |
250 | 246 |
251 logger.info ("Unittest complete."); | 247 logger.info ("Unittest complete."); |
252 } | 248 } |
253 } | 249 } |