We use a simple dictionary format stored plain text files. Each language file contains multiple lines, one for each vocabulary. The lines start with a key, separated from its translation by a colon.
Language file “dictionary_en.txt”
HelloWorld: Hello world!
NowInThreeLanguages: Now in three languages!
Language file “dictionary_de.txt”
HelloWorld: Hallo Welt!
NowInThreeLanguages: Jetzt in drei Sprachen!
Language file “dictionary_fr.txt”
HelloWorld: Bonjour le monde!
NowInThreeLanguages: Maintenant en trois langues!The core functionality for this internationalization approach is implemented in a static Internationalizationclass. It contains a Dictionary with string keys and string values. In contrast to the previous approach, this dictionary will be loaded at runtime, thus we need to implement a LoadDictionary method, which will be called from the platform-specific code. The method reads a given file stream line by line and splits key and value at the unique separator ":". The Trim method removes remaining whitespace from the beginning and the end of both strings. Note that, if any key or value contains a colon, you might need to use a different separator. The Translate method is a simple dictionary lookup with a fallback return value in case the key is invalid. Using percent signs indicates a missing translation clearly visible on the UI. The thiskeyword in the argument list defines an extension method so that we can call it via dot notation.
public static class Internationalization
{
static Dictionary<string, string> dictionary;
public static void LoadDictionary(Stream stream)
{
dictionary = new Dictionary<string, string>();
using (var streamReader = new StreamReader(stream)) {
while (!streamReader.EndOfStream) {
var columns = streamReader.ReadLine().Split(':');
dictionary.Add(columns[0].Trim(), columns[1].Trim());
}
streamReader.Close();
}
}
public static string Translate(this string key)
{
return dictionary.ContainsKey(key) ? dictionary[key] : "%" + key + "%";
}
}As mentioned above, the LoadDictionary method is called from the iOS- and Android-specific code. On Android we can easily get the required file stream from Android assets. Therefore, we add the dictionary files to the “Asset” folder of the Android project, make sure to set build action to “AndroidAsset” and refer to them using the following lines of code during OnCreate:
var language = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
var stream = Assets.Open("dictionary_" + language + ".txt");
Internationalization.LoadDictionary(stream);On iOS we create an “Assets” folder by ourselves, add the dictionary files with build action “Content” and refer to them creating a new FileStream withing FinishedLaunching:
var language = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
var file = @"Assets/dictionary_" + language + ".txt";
var path = Path.Combine(Directory.GetCurrentDirectory(), file);
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
Internationalization.LoadDictionary(stream);For easier file management you can also add the dictionary files to the shared project – since they are actually shared content – and link to them from both native projects.
Finally, on the MainPage we place the very same content as in the previous example, but using the new string extension method. The result is identical to the previous screenshots.
MainPage = new ContentPage {
Content = new Label {
Text = "HelloWorld".Translate() + "\n" + "NowInThreeLanguages".Translate(),
HorizontalTextAlignment = TextAlignment.Center,
VerticalOptions = LayoutOptions.CenterAndExpand,
},
};
DiscussionIn contrast to the previous approach, this solution has some major differences:
- It loads the dictionary at runtime. If done synchronously, this might slow down the app start a bit. But, alternatively, you could keep a dictionary in persistent memory and download an update from time to time. In contrast to the compiled dictionary, this gives you more flexibility to modify UI strings or to add additional languages after app release.
- The translation is implemented as an extension method for strings. This way you can write something like "HelloWorld".Translate().
- The dictionary is stored as a C# Dictionary<string, string>. Therefore – and because the dictionary is loaded only at runtime – you won’t be able to use features like syntax completion or refactoring for the vocabulary. But it allows you to use more complex keys like "Now in three languages!".
Remark on using keys instead of a reference languageBoth proposed solutions define a dictionary based on a set of constructed, formal keys (with a representation close to English) and corresponding translations in a natural language like English, German or French. One could argue, we should use a reference language like English as keys. Although this might work in some cases, there are problems with terms that are equal in English, but require different translations in other languages. We would need to add a prefix or suffix indicating the required translation, e.g. “_noun” or “_verb”. But then we already left the domain of a natural language and started to create a formal language anyway, which is why we suggest to use “international” keys and to treat English like any other language that requires translation.