Mercurial > projects > mde
view mde/lookup/Translation.d @ 128:41582439a42b
Added support for dynamic EnumContent loading and saving, with translation loading.
WMScreen.init removed; code moved to this() since class is now created by main() instead of a static this().
Fix for SwitchWidget not passing events. Still some resizing bugs evident in SwitchWidget :-(
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Wed, 14 Jan 2009 20:24:14 +0000 |
parents | 3328c6fb77ca |
children | 7ababdf97748 |
line wrap: on
line source
/* LICENSE BLOCK Part of mde: a Modular D game-oriented Engine Copyright © 2007-2008 Diggory Hardy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /** Translation − internationalization module for translating strings * * The idea behind this module is a class which, when asked to load symbols for a particular module/ * package/part of the program, will load internationalized names and optional descriptions for each * symbol needing translation. No support for non-left-to-right scripts is currently planned, and * this module is currently limited to translations, although support for different date formats, * etc. could potentially be added later. * * Code symbols are used as identifiers for each name and its optional description. The code symbol * will be used as a fallback in the case no entry exists, however it is not intended to provide the * string for the default language (a "translation" should be used for the default language). * * Each locale may specify dependant locales/sections which will be loaded and merged in to the * database, to cover for symbols with a missing entry. Sections are loaded in the order specified, * with each section's sub-dependancies loaded before continuing with the next top-level dependancy. * A list of loaded sections is used to prevent any locale/section from being loaded twice, and thus * allow circular dependancies. * * In order that translated strings get updated correctly to reflect changes, each entry carries a * version number. If, for any entry, a translation exists with a higher version number, that entry * is out of date. A tool should be made for checking for out of date entries to take advantage of * this feature. Of course, out of date entries are still valid for use. */ module mde.lookup.Translation; import mde.lookup.Options; import mde.setup.paths; import mde.exception; import mde.file.mergetag.DataSet; import mde.file.mergetag.Reader; import mde.file.mergetag.exception; import mde.file.deserialize; import tango.util.log.Log : Log, Logger; /** The translation class * * See module description for details. * * Encoding used is UTF-8. */ class Translation : IDataSection { /** Load the translation for the requested module/package/... * * Options (mde.options) must have been loaded before this is run. * * Params: * name The module/package/... to load strings for. * * Throws: * If no localization exists for this name and the current locale (or any locale to be chain- * loaded), or an error occurs while loading the database, a L10nLoadException will be thrown. */ static { /** Get Translation instance for _section section. * * On the first call, loads all Translation sections. * * These are loaded from data/L10n/locale.mtt where locale is the current locale, as well * as any files named for locales specified in the header tag <char[][]|depends=[...]>. * * On errors, a message is logged and the function continues, likely resulting in some * symbol names being untranslated. */ Translation get (char[] section) { if (sections is null || loadedL10n !is miscOpts.L10n()) { if (sections.length) { // Clear entries hash-map, but re-use classes foreach (s; sections) s.entries = null; } loadedL10n = miscOpts.L10n(); debug logger.trace ("Loading L10n: "~loadedL10n); char[][] files = [loadedL10n]; // initial locale plus dependencies size_t read = 0; // index in files of next file to read while (read < files.length) { try { IReader reader = dataDir.makeMTReader ("L10n/"~files[read++], PRIORITY.HIGH_LOW, null, true); assert (reader.dataset.header); auto dp = "depends" in reader.dataset.header._charAA; if (dp) { // append, making sure no duplicates are inserted f1: foreach (dep; *dp) { foreach (f; files) if (f == dep) continue f1; files ~= dep; } } reader.dataSecCreator = delegate IDataSection(ID sec) { auto p = sec in sections; if (p) return *p; return new Translation (sec); }; reader.read; } catch (MTException e) { logger.error ("Mergetag exception occurred:"); logger.error (e.msg); } } } auto p = section in sections; if (p is null) { logger.warn ("Section "~section ~ " not found in files; strings will not be translated."); return new Translation (section); } return *p; } private Translation[char[]] sections; private char[] loadedL10n; // reload if different } final char[] section; // ONLY used to log an error message alias entry opCall; /// Convenience alias /** Get the translation for the given identifier. * If no entry exists, the identifier will be returned. * * Optionally, the description can be returned. */ char[] entry (char[] id) { Entry* p = id in entries; if (p) { return p.name; } else { return id; } } /** ditto */ char[] entry (char[] id, out char[] description) { Entry* p = id in entries; if (p) { description = p.desc; return p.name; } else { return id; } } /** Alternative usage: return a Translation.Entry struct. */ Entry getStruct (char[] id) { Entry* p = id in entries; if (p) { return *p; } else { logger.warn ("Unable to find translation for {} in {}", id, section); Entry ret; ret.name = id; return ret; } } static this() { logger = Log.getLogger ("mde.lookup.Translation"); } /* Mergetag functionality. * * Merge tags in to entries, prefering existing values. */ void addTag (char[] tp, ID id, char[] dt) { if (tp == "entry") { // If the tag already exists, don't replace it if (cast(char[]) id in entries) return; Entry entry = deserialize!(Entry) (dt); if (entry.name is null) { // This tag is invalid; ignore it logger.error ("In L10n/*.mtt section "~section~": tag with ID "~cast(char[])id~" has no data"); return; } entries[cast(char[]) id] = entry; } } // This class is read-only and has no need of being saved. void writeAll (ItemDelg) {} /** This struct is used to store each translation entry. * * Note that although each entry also has a version field, this is not loaded for general use. */ struct Entry { char[] name; // The translated string char[] desc; // An optional description } private: /* Sets name and ensures only Translation's static functions can create instances. */ this (char[] n) { section = n; sections[n] = this; } //BEGIN Data static Logger logger; Entry[char[]] entries; // all entries //END Data debug (mdeUnitTest) unittest { // Relies on files in unittest/data/L10n: locale-x.mtt for x in 1..4 // Hack a specific locale. Also to allow unittest to run without init. StringContent realL10n = miscOpts.L10n; miscOpts.L10n = new StringContent ("L10n", "locale-1"); // Struct tests Translation t = get ("section-2"); Entry e = t.getStruct ("entry-1"); assert (e.name == "Test 1"); assert (e.desc == "Description"); e = t.getStruct ("entry-2"); assert (e.name == "Test 2"); assert (e.desc is null); // Dependency tests. Priority should be 1,2,3,4 (entries should come from first file in list that they appear in). t = get ("section-1"); char[] d = "1"; assert (t.entry ("file-1", d) == "locale-1"); assert (d is null); assert (t.entry ("file-2", d) == "locale-2"); assert (d == "desc2"); assert (t.entry ("file-3") == "locale-3"); assert (t.entry ("file-4") == "locale-4"); // Restore delete miscOpts.L10n; miscOpts.L10n = realL10n; sections = null; logger.info ("Unittest complete."); } }