comparison mde/content/Translation.d @ 137:9f035cd139c6

BIG commit. Major change: old Options class is gone, all content values are loaded and saved automatically. All options updated to reflect this, some changed. Content restrutured a lot: New IContent module, Content module includes more functionality. New ContentLoader module to manage content loading/saving/translation. Translation module moved to content dir and cut down to reflect current usage. File format unchanged except renames: FontOptions -> Font, VideoOptions -> Screen. Font render mode and LCD filter options are now enums. GUI loading needs to create content (and set type for enums), but doesn't save/load value. Some setup of mainSchedule moved to mde.mainLoop. Content callbacks are called on content change now. ContentLists are set up implicitly from content symbols. Not as fast but much easier! Bug-fix in the new MTTagReader. Renamed MT *Reader maker functions to avoid confusion in paths.d. New mde.setup.logger module to allow logger setup before any other module's static this().
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 07 Feb 2009 12:46:03 +0000
parents mde/lookup/Translation.d@7ababdf97748
children 0520cc00c0cc
comparison
equal deleted inserted replaced
136:4084f07f2c7a 137:9f035cd139c6
1 /* LICENSE BLOCK
2 Part of mde: a Modular D game-oriented Engine
3 Copyright © 2007-2008 Diggory Hardy
4
5 This program is free software: you can redistribute it and/or modify it under the terms
6 of the GNU General Public License as published by the Free Software Foundation, either
7 version 2 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>. */
15 /** Translation − internationalization module for translating strings
16 *
17 * Loads a set of names and optionally descriptions intended to provide a user-
18 * friendly localised name to symbols defined in code or data files.
19 *
20 * Each locale may specify dependant locales which will be loaded and merged,
21 * so that, for instance, variants of a language need not define common strings
22 * in all variants. Dependencies are loaded in the order specified, including
23 * all dependencies of first locale before any dependencies of other locales.
24 * Circular dependencies are allowed.
25 *
26 * Entries may be extended to include a version number, intended to indicate
27 * translations which may need updating to reflect a changed meaning. A
28 * translation tool is needed to handle this really. */
29 module mde.lookup.Translation;
30
31 import mde.file.paths;
32 import mde.exception;
33
34 import mde.file.mergetag.MTTagReader;
35 import mde.file.deserialize;
36
37 import tango.util.log.Log : Log, Logger;
38
39 /** Loads all translation strings for current locale.
40 *
41 * See module description for details.
42 *
43 * Encoding used is UTF-8. */
44 struct Translation
45 {
46 /** Load the translation for the requested locale.
47 *
48 * Params:
49 * name = The locale to load strings for.
50 */
51 static {
52 /** Get Translation instance for locale l10n, loading if not already
53 * loaded. Only keeps one locale loaded.
54 *
55 * These are loaded from data/L10n/locale.mtt where locale is l10n and
56 * files for dependant locales given in the header tag
57 * <char[][]|depends=[...]>.
58 *
59 * On errors, a message is logged and the function continues, likely
60 * resulting in some symbol names being untranslated. */
61 Translation get (char[] l10n) {
62 if (loadedL10n != l10n) {
63 loadedL10n = l10n;
64 debug logger.trace ("Loading L10n: "~loadedL10n);
65 transl.load (l10n);
66 }
67 return transl;
68 }
69 private Translation transl;
70 private char[] loadedL10n;
71 private Logger logger;
72 static this () {
73 logger = Log.lookup ("mde.lookup.Translation");
74 }
75 }
76
77 private void load (char[] l10n) {
78 char[][] files = [loadedL10n]; // initial locale plus dependencies
79 size_t read = 0; // index in files of next file to read
80 while (read < files.length) {
81 logger.trace ("reading file {}", files[read]);
82 try {
83 MTTagReader reader = dataDir.makeMTTagReader ("L10n/"~files[read], PRIORITY.HIGH_LOW);
84 bool isSecTag;
85 while (reader.readTag (isSecTag)) {
86 if (isSecTag) break; // end of header
87 if (reader.tagID == "depends" && reader.tagType == "char[][]") {
88 // append, making sure no duplicates are inserted
89 char[][] deps = deserialize!(char[][]) (reader.tagData);
90 f1:
91 foreach (dep; deps) {
92 foreach (f; files)
93 if (f == dep)
94 continue f1;
95 files ~= dep;
96 }
97 }
98 }
99 char[] symPrefix;
100 do {
101 if (isSecTag) {
102 if (reader.section.length == 0) {
103 symPrefix = "";
104 continue;
105 }
106 symPrefix = reader.section ~ '.';
107 } else {
108 if (reader.tagType == "entry") {
109 char[] sym = symPrefix ~ reader.tagID;
110 // If the tag already exists, don't replace it
111 if (sym in entries) continue;
112
113 Entry entry = deserialize!(Entry) (reader.tagData);
114 if (entry.name is null) { // This tag is invalid; ignore it
115 logger.error ("In L10n/{}.mtt: tag {} has no name", files[read], sym);
116 continue;
117 }
118 entries[sym] = entry;
119 }
120 }
121 } while (reader.readTag (isSecTag))
122 ++read;
123 } catch (Exception e) {
124 logger.error ("Exception loading translation: {}", e.msg);
125 }
126 }
127 }
128
129 /+ Getters for entries... not wanted now.
130 alias entry opCall; /// Convenience alias
131
132 /** Get the translation for the given identifier.
133 * If no entry exists, the identifier will be returned.
134 *
135 * Optionally, the description can be returned. */
136 char[] entry (char[] id) {
137 Entry* p = id in entries;
138 if (p) {
139 return p.name;
140 } else {
141 return id;
142 }
143 }
144 /** ditto */
145 char[] entry (char[] id, out char[] description) {
146 Entry* p = id in entries;
147 if (p) {
148 description = p.desc;
149 return p.name;
150 } else {
151 return id;
152 }
153 }
154
155 /** Alternative usage: return a Translation.Entry struct. */
156 Entry getStruct (char[] id) {
157 Entry* p = id in entries;
158 if (p) {
159 return *p;
160 } else {
161 logger.warn ("Unable to find translation for: {}", id);
162 Entry ret;
163 ret.name = id;
164 return ret;
165 }
166 } +/
167
168 /** This struct is used to store each translation name and description pair.
169 *
170 * Entries may also have a version field, but this is only needed for
171 * writing/updating translations. */
172 struct Entry {
173 char[] name; // The translated string
174 char[] desc; // An optional description
175 }
176
177 Entry[char[]] entries; // all entries
178
179 debug (mdeUnitTest) unittest {
180 // Relies on files in unittest/data/L10n: locale-x.mtt for x in 1..4
181
182 // Struct tests
183 Translation t = get ("locale-1");
184 Entry e = t.getStruct ("section-2.entry-1");
185 assert (e.name == "Test 1");
186 assert (e.desc == "Description");
187 e = t.getStruct ("section-2.entry-2");
188 assert (e.name == "Test 2");
189 assert (e.desc is null);
190
191 // Dependency tests. Priority should be 1,2,3,4 (entries should come from first file in list that they appear in).
192 char[] d = "1";
193 assert (t.entry ("section-1.file-1", d) == "locale-1");
194 assert (d is null);
195 assert (t.entry ("section-1.file-2", d) == "locale-2");
196 assert (d == "desc2");
197 assert (t.entry ("section-1.file-3") == "locale-3");
198 assert (t.entry ("section-1.file-4") == "locale-4");
199
200 logger.info ("Unittest complete.");
201 }
202 }