1 /** 2 * Localization seam. 3 * 4 * Deft does not bundle a message-catalog parser — that would drag a dependency 5 * (and likely Phobos) into the library and force one catalog format on everyone. 6 * Instead it exposes a single pluggable hook: the application installs a 7 * `Translator` delegate with `setTranslator`, and every translatable string the 8 * framework emits is looked up through `tr`. The delegate is free to be backed by 9 * gettext, XLIFF, a plain associative array, or anything else — that choice (and 10 * any Phobos it needs) lives in the application, not here. 11 * 12 * Without a translator installed, `tr` returns its argument unchanged, so an 13 * un-localized app behaves exactly as before. The framework's own handful of 14 * strings (the standard dialog buttons) additionally fall back to the operating 15 * system's localized text, so they are translated even with no catalog at all. 16 * 17 * Install the translator once, before creating UI; reads are not synchronized. 18 */ 19 module deft.i18n; 20 21 /** 22 * Looks up a translation for `key`, returning the translated string. A delegate 23 * may throw to signal "no translation"; callers fall back to `key`. 24 */ 25 alias Translator = string delegate(string key); 26 27 private __gshared Translator g_translator; 28 29 /// Install (or clear, with `null`) the application's translation delegate. 30 void setTranslator(Translator translator) 31 { 32 g_translator = translator; 33 } 34 35 /// The currently installed translation delegate, or `null`. 36 Translator translator() 37 { 38 return g_translator; 39 } 40 41 /** 42 * Translate `key` through the installed `Translator`. 43 * 44 * Returns the translated string, or `key` itself when no translator is installed 45 * or the translator returns null/empty or throws. `nothrow`: safe to call from 46 * anywhere in the UI, including while building controls. 47 */ 48 string tr(string key) nothrow 49 { 50 auto t = g_translator; 51 if (t !is null) 52 { 53 try 54 { 55 auto translated = t(key); 56 if (translated.length != 0) 57 return translated; 58 } 59 catch (Exception) 60 { 61 // Fall through to returning the key unchanged. 62 } 63 } 64 return key; 65 } 66 67 unittest 68 { 69 // Default: no translator installed, tr is the identity. 70 setTranslator(null); 71 assert(tr("Hello") == "Hello"); 72 73 // An installed translator is consulted. 74 setTranslator((string k) => k == "Hello" ? "Bonjour" : k); 75 assert(tr("Hello") == "Bonjour"); 76 assert(tr("Unmapped") == "Unmapped"); // unknown key falls back to itself 77 78 // A translator that returns empty falls back to the key. 79 setTranslator((string k) => ""); 80 assert(tr("Keep") == "Keep"); 81 82 // A throwing translator must not propagate; it falls back to the key. 83 setTranslator(delegate string(string k) { throw new Exception("boom"); }); 84 assert(tr("Safe") == "Safe"); 85 86 setTranslator(null); // don't leak state into other tests 87 }