C# How to use a simple form of i18n instead of resource files (resx files)
Tired of resource files (resx files) ?
Like many of you, I’ve been developing C# applications for many years. There were times where I had to use .resx files for storing translation as key-values.?Using resx files has some benefits:
1- It’s editor is built-in into Visual Studio
2- Visual studio creates a static class to provide translation values
But there are also some problems:
1- Editing resx files is possible only in Visual Studio. Even if we use third-party apps like?ZetaResourceEditor?or?ResXResourceManager?we are limited to Windows operating system.
2- For those who care about less dlls in application output, resx files are a problem, since they cause the application output to have many satellite assemblies.
3- Dependency injection with resx approach is not trivial and needs some wrappers around the static class.
If you are curious about an alternative, keep reading.
JSON to the rescue!
As an alternative we can use JSON to store our translations. I’ve created a repository named?libc.translation?which does this job with only 6 files of source code. Let’s see how this piece of code would help us:
1- Reference the nuget package:
2- Now we need to write our translations in a JSON file (create our localization source). Our JSON file is an object with keys as cultures of translations. Below is an example:
3- There are two ways to use this file as an ILocalizationSource:
3–1-?Embed?this file in our assembly: Select the file in Solution Explorer and hit F4. In Properties panel choose “Embedded resource” as “Build Action”. Now the JSON file is embedded in our assembly :-) Remember using my approach there won’t be any satellite assemblies in output
3–2- Store this file somewhere on?disk.
4- In this article I’m going to go with embedded JSON file (3–1). If our project name is “libc.translation.tests” then we can create an instance of ILocalizationSource like this:
LocalizationSource class also has some other constructor overloads which allow us to provide a FileInfo, JObject or Stream for JSON data.
领英推荐
Most of the times we don’t use ILocalizationSource directly. Instead, we pass the source to Localizer constructor:
Have you noticed that there’s a“fallbackCulture” parameter? As the name suggests, fallback culture is used when a culture is missing in our translations. In above example JSON, let’s assume somewhere in our code we try to get translation value of “InvalidInput” key in “de” (German) language. “de” culture is not specified in JSON file, so Localizer instance uses the fallback culture and returns the value in “ar” (Arabic).
5- It is time to retrieve translation values using some keys and desired cultures:
5–1-?Get translation without passing a culture: Just pass the key (case-insensitive). Here the Localizer will use thread’s culture: (Remember if thread’s current culture is not present in JSON file, fallback culture will be used)
5–2-?Get translation in a specific culture: Pass the desired culture, then the key (case-insensitive) (Remember if the given culture is not present in JSON file, fallback culture will be used). Let’s see an example:
In the first block above, we needed the translation value for “InvalidInput” key in “en” (English) culture. In the second block, we tried to get this translation in “de” (German) culture, but since “de” is not present in our JSON file, Localizer will try to retrieve the translation in fallback culture (which is “ar” (Arabic))
5–3-?Get formatted translation (using string.Format) without passing a culture:
Here we passed the key (notice that first letter is in lower-case as opposed to “UnknownError” which U is upper-case) and the current thread’s culture (which is Arabic) is used. And also, similar to string.Format, Localizer replaces “{0}” with “!!!”.
You can take a look at?provided tests?to see more examples.
Conclusion:
In this article, we tried to use an alternative way to localize our applications in .Net Standard 2.0 (.Net Core & .Net Framework 4.6.1+) projects. We saw how to create an ILocalizationSource and pass that to an instance of ILocalizer.