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 }