A specialized string is a type with a text representation. For example, user-visible text is a string, but not every string is user-visible text. When adding internationalization (often shortened to i18n) to an application, using a string subtype can help ensure that all messages are available to translators. Moreover, we'll use D's templates and compile-time string imports to efficiently load the translation file.
Execute the following steps to create a subtyped string for i18n:
alias this
to the string getting property.pragma(msg)
to print out the whole list of translatable strings.TranslatedString
type rather than string.The following is the code to create a subtyped string for i18n:
struct TranslatedString { private string value; @property string getValue() { return value; } alias getValue this; } private TranslatedString localize(string key, bool requireResult = true) { // we'll use an associative array for the translation // it would also be possible to load from a file, at // compile time enum string[string] translations = [ "Hello!" : "¡Hola!" ]; if(auto value = key in translations) return TranslatedString(*value); if(requireResult) throw new Exception(key ~ " is not translated"); return TranslatedString(null); } template T(string key, string file = __FILE__, size_t line = __LINE__) { version(gettext) { // prints messages at compile time with all strings // in the program, if requested import std.conv; pragma(msg, "#: " ~ file ~ ":" ~ to!string(line)); pragma(msg, "msgid "" ~ key ~ """); pragma(msg, "msgstr ""~localize(key, false)~"""); pragma(msg, ""); } enum T = localize(key); } void main() { auto p = T!"Hello!"; // translated string literal import std.stdio; writeln(p); }
Here, we utilize the type system to help us catch untranslated strings that are to be printed to the user and the compile-time features to help us translate them.
The TranslatedString
type, if used throughout the library, would provide a degree of type safety to printing. Using alias this
subtyping, it will easily convert back to string for integration with other functions without allowing implicit conversions from string to TranslatedString
, which would defeat the purpose.
The T
template provides concise syntax to access the localization function (without runtime cost; this pattern is how we can do user-defined literals in D, a topic we'll cover in greater depth in a later chapter) as well as a place to track all translatable strings in the application.
Using the version statement, we provide an optional code path that prints the locations, strings, and current translations of all the instantiations of this template. The version statement takes a single identifier. Unless that identifier is specialized on the compiler's command line or is predefined for the given target platform (for example, Windows or OS X), the code inside is not compiled. Here, we used version(gettext)
, so the code inside is only compiled if dmd
is passed the –version=gettext
argument.
If this code is compiled, it will run the pragma(msg)
instructions. This will print out the given string at compile time. It is meant to give a compilation message to the user. The argument need not be a literal because it will be evaluated using compile-time function evaluation; ordinary D code will be run and the result printed.
The arguments to T
, (string key, string file = __FILE__, size_t line = __LINE__)
, include another bit of D magic: the default arguments to file
and line
. Just like we saw with runtime function arguments back in Chapter 1, Core Tasks, when we looked at creating a custom exception type, the special tokens __FILE__
, __LINE__
, and a few others are automatically inserted at the call site. It works the same way with compile-time arguments. Using these, we can print out the list of strings (including the location of their appearance) for the translator to examine, without writing an external tool to parse the code to find the strings.
Once we have the strings, we put them into an associative array literal inside the localize
function, which is wrapped in the TranslatedString
type, and use that to get the translation to print to the user.
3.12.166.131