Category Archives: i18n

i18n in React

i18n (internationalization) is the process of making an application work in different languages and cultures. This includes things like, translations of string resources through to to handling date formats as per the user’s language/culture settings, along with things like decimal separators and more.

There are several libraries available for helping with i18n coding but we’re going to focus on the react-i18next in this post, for no other reason that it’s by other teams in the company I’m working for at the moment.

Getting Started

The react-i18next website is a really good place to get started and frankly I’m very likely to cover much the same code here, so their website should be your first port of call.

If you’re want to instead follow thought the process here then, let’s create a simple React app with TypeScript (as is my way) using

npx create-react-app i18n-app --template typescript

Now add the packages react-i18next and i18next i.e.

npm install react-i18next i18next --save

In this Getting Started I’m going to also include the following libraries which will handle loading the translation files and more

npm install i18next-http-backend i18next-browser-languagedetector --save

Adding localized strings

We’re going to first show an example of embeding the string into a .ts file, however it’s much more likely we’ll want them in a separate file (or multiple files). Let’s get this started by creating a file named i18n1.ts.

In the file we’ll just write one string and here it is

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources: {
      en: {
        translation: {
          welcome: "code: Hello string en World"
        },
      },
      fr: {
        translation: {
          welcome: "code: Bonjour string fr World"
        }
      }
    },
    debug: true,
    interpolation: {
      escapeValue: false, 
    },
  });

export default i18n;

As you can see we’re using the LanguageDetector to automatically set our resources. On my browser the language is set to en-GB so my expectation is to see the en strings as I’ve not listed en-GB specific strings.

IMPORTANT: Before we can use this, go to index.tsx and import this file import “./i18n1”; so the bundler includes it.

Display/using our localized strings

Now we’ll need to actually use our translation strings. react-i18next comes with hooks, HOC’s and standard JS type functions to interact with our translated strings. Let’s clear out most of the App.tsx and make it look like this

import React from "react";
import "./App.css";
import { useTranslation } from "react-i18next";

const lngs: any = {
  en: { nativeName: "English"},
  fr: { nativeName: "French"},
}

function App() {
  const { t, i18n } = useTranslation();

  return (
    <div className="App">
      <div className="App-header">
        {t("welcome")}
        <div>
          {Object.keys(lngs).map(lng => {
            return <button key={lng} style={{margin: "3px"}}
              onClick={() => i18n.changeLanguage(lng)} disabled={i18n.resolvedLanguage === lng}>{lngs[lng].nativeName}</button>
          })}
        </div>
      </div>
    </div>
  );
}

export default App;

This code will simply display the English/French buttons to allow us to change the language as we wish. Notice, however, that if we add a new language, such as de: { nativeName: “German”} and no strings exist for that language, you’ll end up seeing the “key” for the string. We can solve this later.

At this point if all is working, you can start the application up and switch between the languages. You’ll see that the strings will be prefixed with code: just to make it clear where the strings are coming from, i.e. our code file.

Moving to resource type files (part 1)

As I mentioned, we probably don’t want to embed our string in code. It’s preferable to move them into their own .JSON files.

Create a folder within the src folder named locales (the names of the folders and files doesn’t really matter but it’s good to be consistent) and within that we’ll have one folder name en-GB and another named fr. So the English strings are specific to GB but the French covers all French languages locales.

Now in en-GB create the file translations.json which will look like this

{
  "welcome": "src: Hello en-GB World"
}

For the French translations, add translations.json to the fr folder and it should look like this

{
 "welcome": "src: Bonjour fr World"
}

Note the src: prefix is again, just there to allow us to see where our resources are coming from in this demo.

We now need to change our i18n.ts file to look like this

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";

import enGB from "../src/locales/en-GB/translation.json";
import fr from "../src/locales/fr/translation.json";

const resources = {
  en: {
    translation: enGB
  },
  fr: {
    translation: fr
  }
};
i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources,
    debug: true,
    interpolation: {
      escapeValue: false, 
    },
  });

export default i18n;

So in this code we’reimporting the JSON and then assigning to the resources const. This is very similar to the other way we bought the resources into the i18n.ts file, just we’re importing via JSON files.

Moving to resource type files (part 2)

There’s another way to import the translated strings and that is to simply include the files in the public folder of our React application. So in the public folder add the same folders and files, i.e. locales folder with en-GB and fr folders with the same translation.json files as the last example. I’ve changed the src: prefix on the strings to public: again just so I can prove, for this demo, where the strings originate from.

In other words, here’s the en-GB translations.json file

{
  "welcome": "public: Hello en-GB World"
}

and the fr translations.json file

{
  "welcome": "public: Bonjour fr World"
}

Now back in our i18n.ts file change it to look like this

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import LanguageDetector from "i18next-browser-languagedetector";

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: {
      "en" : ["en-GB"]
    },
    debug: true,
    interpolation: {
      escapeValue: false, 
    },
  });

export default i18n;

Fallback

My browser is setup as English (United Kingdom) which is en-GB and all works well. But what happens if we add a detect a language where we have no translation strings for? Well let’s try it by adding a German option to the lngs const in our App.tsx, so it looks like this

const lngs: any = {
  en: { nativeName: "English"},
  fr: { nativeName: "French"},
  de: { nativeName: "German"},
}

Now, what happens ? Well we’ve see the key for the string, which is probably not what we want in production, better to fall back to a known language. So we need to change the i18n.ts file and add a fallbackLng, I’ll include the whole i18n object so it’s obvious

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources,
    lng: "en-GB",
    fallbackLng: "en",
    debug: true,
    interpolation: {
      escapeValue: false, 
    },
  });

Now if our application encounters a locale it’s not setup for, it’ll default to the fallback language (in this case) English. We can also achieve this using

fallbackLng: {
  "default": ["en"]
},

Using this syntax we can also map different languages to specific fallback languages, so for example we might map Swiss locale to map to use French or Italian. This is achieved by passing an array of fallback languages (as taken from the <a href="https://www.i18next.com/principles/fallback" rel="noopener" target="_blank">Fallback documentation</a>

[code]
fallbackLng: { 
  "de-CH": ["fr", "it"], // French and Italian are also spoken in Switzerland
},

Finally with regards fallback languages, we can write code to determine the translation to use, again the Fallback documentation has a good example of this, so I’d suggest checking that link out.

Code

Code for this post is available on github which includes i18n files 1-3 for each of these options for creating translations. just change the index.tsx to import the one you wish to use.

Multilingual support for a ASP.NET web API application

We sometimes wish to make our web API return error messages or other types of string data in different languages. The process for this is similar to MAUI and WinForms, we just do the following

  • Create a folder names Resources in our web APIT project
  • Add a RESX file, which we’ll name AppResources.resx. This will be the default language, so in my case this will include en-GB strings
  • Ensure the file has a Build Action of Embedded resource and Custom Tool of ResXFileCodeGenerator
  • Add a name (which is the key to your resource string) and then add the value. This is the string (i.e. the translated string) for the given key
  • Let’s add another RESX file, but this type name it AppResources.{language identifier}.resx, for example AppResources.de-DE.resx which will contain the German translation of the key/name’s
  • Again ensure the Build Action and Custom Tool are correctly set

The ResXFileCodeGenerator will generate properties in the AppResources class for us to access the resource strings. For example

AppResources.ExceptionMessage

If we need to test our translations without changing our OS language, we simply use code such as the following in the Program.cs of the web API

AppResources.Culture = new CultureInfo("de-DE");

Adventures in UWP – Globalization & Localization

In a previous post I created a “default” UWP blank application, which did nothing. But we did look at some of the key parts of the template code, manifest etc.

Usually I would go the “Hello World” route and just display a TextBlock or similar to display the text “Hello World”, so let’s do that here and then look to make this post a little more useful by taking a look at globalization & localization.

Obviously if we were to actually deploy an application to the Window’s store it would be good to have it available in different languages etc.

I’m not intending to spend too long on this as it’s quite a large subject in and of itself, but let’s look at the basics.

In MainPage.xaml, add a TextBlock between the Grid (here’s the code including the Grid)

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
   <TextBlock Text="Hello World" 
      HorizontalAlignment="Center" 
      VerticalAlignment="Center"/>
</Grid>

In the above code we’ve effectively hard coded “Hello World” into our application.

Globalization & Localization

Internationalization (also known as i18n) is the combination of Globalization & Localization.

Globalization tends to mean the implementation of code within your application which can change automatically based upon the locale, so for example formatting of numbers or dates.

Localization tends to mean the changes to an application for a different locale, such as translation etc.

Note: I covered some i18n coding for WPF previously in my post A quick look at WPF Localization Extensions.

Let’s localize our app.

Okay, we don’t have a lot of text to translate (or images or the likes), so this is very much an overview of the process we’d undertake on our application. Let’s change our “Hello World” into a format that can be localized.

To do this we use the x:Uid markup element on our TextBlock, for example

<TextBlock x:Uid="helloWorld" 
   HorizontalAlignment="Center" 
   VerticalAlignment="Center"/>

Note: In another post I intend to cover accessibility, such as the narrator. In some instances we might wish to add AutomationProperties.Name text to a control but we’ll also want that to be localized and so whilst a x:Uid=”hellWorld” might be used for a control, in the .resw file we’d have the name/key helloWorld.AutomationProperties.Name and put our localized text in here.

We don’t actually need to have the Text property, but you might find having something displayed at design time useful and so you can just as easily leave the Text=”Hello World” markup and it’ll be overwritten by our localization.

The x:Uid differs from the x:Name markup as it’s aimed specifically for localization.

Now we need to create our resources, in our project we simply create a folder (at the same level as our Assets) with the name of the locale we want to store resources for, so for example we can create en for general English language, or en-GB, en-CA, en-US for example, for British, Canadian and US variants of English.

See Supported Languages for a list of language codes.

With our folder in place we simple add a new item of type Resources file (.resw) and this gives us a simply grid entry screen for key/value pairs along with a comment for passing onto our translators (for example).

Within the grid, the key (actually it’s the header Name) is our x:Uid but in the format uid.PropertyName, where the property name for our TextBlock is Text. Therefore remember if the UI element changes from a TextBlock to a Button (for example) this PropertyName would need to also change from Text to Content.

We can (and should) also store string representations of colours, such as “Red” etc. as values for those elements where the colour may need to change for the different locale.

Testing our localization

So let’s assume we’ve created several folders and localized our application strings etc. How do we test the different localization without having to change our Windows setup.

There’s actually a Default language option in the app’s manifest, but changing this doesn’t appear to have the desire affect.

A simple way to change our locale is within the App constructor, simply place the following

ApplicationLanguages.PrimaryLanguageOverride = "fr-FR";

We could also have an option within the application for switching languages, in which case we’ve need to reload the UI, but I’m not going to cover that within this post.

Using the Multilingual App Toolkit

An alternate route to translation is using the MAT (Multiligual App Toolkit) which can be used in WPF or UWP.

The Multilingual app toolkit 4.0 for VS 2015 or Multilingual app toolkit for VS 2017. Give us another way to handle translations/localization.

MAT seems a little tricky to get working, but it’s not too bad once you know what’s required (ofcourse that statement can be used for most things).

  1. Select Tools | Multilingual App Toolkit and Enable Selection
  2. Depending upon your manifests Default Language (mine’s en-GB) you’ll need a folder of the same name, i.e. en-GB with a Resources.resw file in it before you can add translations via MAT. As the default language, the key’s and default strings will be taken from this file. So add our helloWorld.Text name and “Hello” as the value to this resource file.
  3. You should build the project here just to make sure all is working
  4. Now you can right mouse click on the project and select Multilingual App Toolkit and now Add translation languages will be enabled, from here you can select multiple languages.
  5. Let’s select fr-FR for some French translation. This will create another folder fr-FR, as you’d expect, along with a Resources.resw for this translation. Do not edit any new translation files, only the default language file
  6. Along with the Resource.rews a MultiligualResources folder will appears with xlf files matching the appname.language format, i.e. HelloWorld.fr-FR.xlf. It is the XLF file that we translate our text into.

Whilst we can edit our XLF in Visual Studio as it’s just an XML file, we can also double click on it from File Explorer to load via the Multilingual Editor. This editor could, ofcource, be used by a translator to go through each resource and supply the translation. Once saved MAT will again sync. but this time it will automatically supply the strings and identifiers to the fr-FR Resource.resw (in my case).

Each time we finished adding new strings to our default locale (in my case the en-GB Resource.resw) then rebuild to get MAT to resync. Then translate when you’re ready.

In most cases we’d wait until all (or most) of our application is in ready before sending the XLF to translators ofcourse, but for us to test things, just translate and rebuild to resync.

Pseudo language with MAT

Whilst the MAT includes many locales that you’ll recognise, it also includes a Pseudo language which is generated from your default language strings but with alterations to the string, for example taking “OK” and creating a longer string with semi-random characters as well as the O and K, so it’s still readable in English (my default language).

To get translations in the pseudo language, open the *.qps-ploc.xlf file and for each string you wish to translate click the Translate button or for all strings, click the Translate button’s drop down and select Translate All. This will then create translations for testing your layouts etc.

Using resource strings in the manifest, for the Display Name

In some cases we might wish to localize the Display name, i.e. Title of the main Window.

From the manifest we can reference our resource string using

ms-resource:myTitle

where myTitle is a key/name within the Resources.

We can (sort of) handle this in code by accessing using

ApplicationView.GetForCurrentView().Title = "My Title";

but actually this doesn’t replace the Display Name from the manifest but instead prepends the text.

Finally, using our resource in code

Whilst we obviously want to do most of our UI work in XAML, we still need to store strings etc. for use in code, for example when displaying a message box of the likes.

To use the resources in code we can declare the strings within the .resw. Interestingly, not sure if this is just me or how things work, but for strings that will be used in code, the name/key of the string does not include the .PropertyName format. Hence “hello.Text” fails to work when used in code, whereas “hello” as a key does work.

Here’s the code that we can use to get the string from the default resources

var rl = ResourceLoader.GetForCurrentView();
var r = rl.GetString("hello");
// r will now be our translated string

References

Globalization and localization
Put UI strings into resources

A quick look at WPF Localization Extensions

Continuing my look into localizing WPF application…

I’ve covered techniques used by theme’s to change a WPF application’s resources for the current culture. I’ve looked at using locbaml for extracting resources and localizing them. Now I’m looking at WPF Localization Extensions.

The WPF localization extension take the route of good old resx files and markup extensions (along with other classes) to implement localization of WPF applications.

Getting Started

  • Create a WPF application
  • Using NuGet, install the WPFLocalizeExtension
  • Open the Properties section and copy Resources.resx and rename the copy Resources.fr-FR.resx (or whatever culture you wish to support)

As with my other examples of localizing WPF applications, I’m not going to put too much effort into developing a UI as it’s the concepts I’m more interested in at this time.

First off let’s add the following XAML to MainWindow.xaml within the Window namspaces etc.

xmlns:lex="http://wpflocalizeextension.codeplex.com"
lex:LocalizeDictionary.DesignCulture="en"
lex:LocalizeDictionary.OutputMissingKeys="True"
lex:ResxLocalizationProvider.DefaultAssembly="Localize2"
lex:ResxLocalizationProvider.DefaultDictionary="Resources"        

The DefaultDictionary needs to have the name of the resource file, whether it’s Resources (exluding the .resx) or if you’ve created one names Strings or whatever, just exclude the extension.

The DefaultAssembly is the name of the assembly to be used as the default for resources, i.e. in this case it’s the name of my project’s assembly.

Next up, within the Grid, we’re going to have this

<TextBlock Text="{lex:Loc Header}" />

Header is a key – obviously on a production ready application with more than one string to translate, we’d probably implement a better naming convention.

A tell tale sign things are wrong is you’ll see the text displayed as Key: Header, this likely points to one of the namespace values being incorrect, such as the DefaultDictionary name.

That’s it for the UI.

In the Resources.resx, add a string named Header and give it a Value, for example mine should default to English and hence will have Hello as the value. In the Resources.fr-FR.resx, add a Header name and give it the value Bonjour.

That’s the extent of our strings for this application. If you run the application you should see the default Resources string, “Hello”. So now let’s look at testing the fr-FR culture.

In App.xaml.cs create a default constructor and place this code within it

LocalizeDictionary.Instance.SetCurrentThreadCulture = true;
LocalizeDictionary.Instance.Culture = new CultureInfo("fr-FR");

Run the application again and the displayed string will be take from the fr-FR resource file.

To allow us to easily switch, at runtime, between cultures, we can use the LocalizeDictionary. Here’s a combo box selector to do this (taken from the sample source on the WPF Localization Extensions github page).

<ComboBox ItemsSource="{Binding Source={x:Static lex:LocalizeDictionary.Instance}, Path=MergedAvailableCultures}"
   SelectedItem="{Binding Source={x:Static lex:LocalizeDictionary.Instance}, Path=Culture}" DisplayMemberPath="NativeName" />

We also need to be able get strings from the selected resource in our code, here’s a simple static class from StackOverflow which allows us to get a string (or other type) from the currently selected resources

public static class LocalizationProvider
{
    public static T GetLocalizedValue<T>(string key)
    {
        return LocExtension.GetLocalizedValue<T>
(Assembly.GetCallingAssembly().GetName().Name + ":Resources:" + key);
    }
}

The string “Resources” should obviously be changed to the name of your resource files (for example if you’re using just strings in a “Strings” resource etc).

This is certainly simpler to set-up than locbaml, the obvious drawback with this approach is that the strings at design time are not very useful. But if, like me, you tend to code WPF UI primarily in XAML then this probably won’t concern you.

Localizing a WPF application using locbaml

This post is going to mirror the Microsoft post How to: Localize an Application but I’ll try to add something of value to it.

Getting Started

Let’s create a WPF Application, mine’s called Localize1. In the MainWindow add one or more controls – I’m going basic at this point with the following XAML within the Window element of MainWindow.xaml

<Grid>
   <TextBlock>Hello</TextBlock>
</Grid>

According to the Microsoft “How To”, we now place the following line in the csproj

<UICulture>en-US</UICulture>

So locate the end of the <PropertyGroup> elements and put the following

<PropertyGroup>
    <UICulture>en-GB</UICulture>
</PropertyGroup>

See AssemblyInfo.cs comment on using the UICulture element also

Obviously put the culture specific to your default locale, hence mine’s en-GB. Save the altered csproj and ofcourse reload in Visual Studio if you have the project loaded.

The inclusion of the UICulture will result (when the application is built) in a folder en-GB (in my case) with a single satellite assembly created, named Localize1.resources.dll.

Next we’re going to use msbuild to generate Uid’s for our controls. So from the command line in the project folder of your application run

msbuild /t:updateuid Localize1.csproj

Obviously replace the project name with your own. This should generate Uid’s for controls within your XAML files. They’re not very descriptive, i.e. Grid_1, TextBlock_1 etc. but we’ll stick with following the “How To” for now. Ofcourse you can implement your own Uid’s and either use msbuild /t:updateuid to generate any missing Uid’s or ignore them – and have Uid’s for those controls you wish to localize only.

We can also verify that Uid’s exist for our controls by running

msbuild /t:checkuid Localize1.csproj

At this point we’ve generated Uid’s for our controls and msbuild generated as part of the compilation a resource DLL for the culture we assigned to the project file.

We now need to look at generating alternate language resource.

How to create an alternate language resource

We need to download the LocBaml tool or it’s source. I had problems locating this but luckily found source on github here.

So if you don’t have LocBaml already, download and build the source and drop the locbaml.exe into your bin\debug folder. Now run the following command

locbaml.exe /parse en-GB/Localize1.resources.dll /out:Localize1.csv

You could ofcourse copy the locbaml.exe to the en-GB folder in my example if you prefer. What we’re after is for locbaml to generate our Localize1.csv file, which will be then add translated text to.

Here’s what my csv file looks like

Localize1.g.en-GB.resources:mainwindow.baml,Window_1:Localize1.MainWindow.$Content,None,True,True,,#Grid_1;
Localize1.g.en-GB.resources:mainwindow.baml,Window_1:System.Windows.Window.Title,Title,True,True,,MainWindow
Localize1.g.en-GB.resources:mainwindow.baml,Window_1:System.Windows.FrameworkElement.Height,None,False,True,,350
Localize1.g.en-GB.resources:mainwindow.baml,Window_1:System.Windows.FrameworkElement.Width,None,False,True,,525
Localize1.g.en-GB.resources:mainwindow.baml,TextBlock_1:System.Windows.Controls.TextBlock.$Content,Text,True,True,,Hello

If you view this csv in Excel you’ll see 7 columns. These are in the following order (decriptions copied from the How To document)

  • BAML Name. The name of the BAML resource with respect to the source language satellite assembly.
  • Resource Key. The localized resource identifier.
  • Category. The value type.
  • Readability. Whether the value can be read by a localizer.
  • Modifiability. Whether the value can be modified by a localizer.
  • Comments. Additional description of the value to help determine how a value is localized.
  • Value. The text value to translate to the desired culture.

For our translators (if we’re using an external translator to localize our applications) we might wish to supply comments regarding expectations or context for the item to be localized.

So, go ahead and translate the string Hello to an alternate culture, I’m going to change it to Bonjour. Once completed, save the csv as Localize1_fr-FR.csv (or at least in my case translating to French).

Now we want to get locbaml to generate our new satellite assembly for the French language resources, so again from the Debug folder (where you should have the generated csv from the original set of resources as well as the new fr-FR file) create a folder named fr-FR (or whatever your new culture is).

Run the locbaml command

locbaml.exe /generate en-GB/Localize1.resources.dll /trans:Localize1_fr-FR.csv /out:fr-FR /cul:fr-FR

This will generate a new .resource.dll based upon the Localize1.resource.dll but using our translated text (as specified in the file Localize1_fr-FR.csv). The new DLL will be written to the fr-FR folder.

Testing our translations

So now let’s see if everything worked by testing our translations in our application.

The easiest way to do this is to edit the App.xaml.cs and if it doesn’t have a constructor, then add one which should look like this

public App()
{
   CultureInfo ci = new CultureInfo("fr-FR");
   Thread.CurrentThread.CurrentCulture = ci;
   Thread.CurrentThread.CurrentUICulture = ci;
}

you’ll obviously requires the following using clauses as well

using System.Globalization;
using System.Threading;

We’re basically forcing our application to use fr-FR by default when it starts. If all went well, you should see the TextBlock with the text Bonjour.

Now change the Culture to one which you have not generated a set of resources for, i.e. in my case I support en-GB and fr-FR, so switching to en-US and running the application will have an undesirable affect, i.e. an IOException occurs, with additional information “Cannot locate resource ‘mainwindow.xaml'”. This is not very helpful, but basically means we do not have a “fallback” or “neutral language” resource.

Setting a fallback/neutral language resource

We, obviously don’t want to have to create resource files for every possible culture. What we need is to have a fallback or neutral language resource which is used when a culture is not supported via a translation DLL. To achieve this, open AssemblyInfo.cs and locate the commented out line which includes NeutralResourcesLanguage or just add the following either way

[assembly: NeutralResourcesLanguage("en-GB", UltimateResourceFallbackLocation.Satellite)]

obviously replace the eb-GB with your preferred default language. Run the application again and no IOException should occur and the default resources en-GB are used.

What about strings in code?

Well as the name suggests, locbaml is really localizing our BAML and when our WPF application starts up, in essence it loads our XAML with the one’s stored in the resource DLL.

So the string that we’ve embedded in the MainWindow.xaml, is not separated from the XAML (i.e. it’s embedded within the TextBlock itself). So we need to move our strings into a shared ResourceDictionary file and reference them from the UI XAML. For example in our App.xaml let’s add

<ResourceDictionary>
   <system:String x:Uid="Header_1" x:Key="Header">TBC</system:String>
</ResourceDictionary>

Now, change our MainWindow.xaml to

<TextBlock Text="{StaticResource Header}" />

This allows us to use FindResource to get at the string resource using the standard WPF FindResource method, as per

var headerString = Application.Current.FindResource("Header");

This appears to be the only “easy” way I’ve found of accessing resources and requires the resource key, not the Uid. This is obviously not great (if it is the only mechanism) as it then requires that we maintain both Uid and Key on each string, control etc. However if we ensure strings are stored as string resources then this probably isn’t too much of a headache.

References

https://msdn.microsoft.com/en-us/library/ms745650.aspx
https://msdn.microsoft.com/en-us/library/ms788718(v=vs.110).aspx

Localizing a WPF application using dynamic resources

There are several options for localizating a WPF application. We can use resx files, locbaml to create resource DLL’s for us, or why not just use the same technique used in theme’s, i.e. DynamicResource and ResourceDictionary’s.

In this post I’m going to look at the DynamicResource and ResourceDictionary approach to localization. Although this technique can obviously be used with images etc., we’ll concentrate on dealing with strings, which usually are the main area of localization.

Let’s start with some code

Create a simple WPF application which will use the standard DynamicResource to set text on controls. We will create a “default” set of string resources to allow us to develop our initial application with and we will create two satellite assemblies which will contain the same string resources for en-GB and en-US resources.

  • Create a WPF Application
  • Create a class library named Resources_en-GB and another class library named Resources_en-US
  • Add the references PresnetationCore, PresentationFramework, WindowsBase and System.Xaml to these class libraries
  • Change the class library output folders for Debug and Release to match those for the WPF application so the DLL’s will be built to the same folder as the application

Now in the WPF application, add a ResourceDictionary, mine’s named Strings.xaml and this will act as our default/design-time dictionary, here’s mine

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:system="clr-namespace:System;assembly=mscorlib">

    <system:String x:Key="Header">TBC</system:String>
    
</ResourceDictionary>

and my MainWindow.xaml looks like this

<Window x:Class="WpfLocalizable.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfLocalizable"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <TextBlock Text="{DynamicResource Header}" FontSize="24"/>
        <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
            <Button Content="US" Width="100" Margin="10" />
            <Button Content="GB" Width="100" Margin="10" />
        </StackPanel>
    </StackPanel>
</Window>

I didn’t say this was going to be exciting, did I?

Now if you run the application as it currently stands, you’ll see the string TBC – our design-time string.

Next, copy the Strings.xaml file from the application to both the Resources_en-GB and Resources_en-US and change the string text to represent your GB and US strings for header – I used the word Colour in GB and Color in US – just to demonstrate the common language differences.

Now if you build and run the application, you’ll see the default header text still, so we now need to make the application set the resources at start-up and allow us to easily switch them. So change the buttons in MainWindow.xaml to these

<Button Content="US" Width="100" Margin="10" Click="US_OnClick"/>
<Button Content="GB" Width="100" Margin="10" Click="GB_OnClick"/>

We’re going to simply use code behind for changing the resource in this demo. So in MainWindow.xaml.cs add the following code

private void LoadStringResource(string locale)
{
   var resources = new ResourceDictionary();

   resources.Source = new Uri("pack://application:,,,/Resources_" + locale + ";component/Strings.xaml", UriKind.Absolute);

   var current = Application.Current.Resources.MergedDictionaries.FirstOrDefault(
                    m => m.Source.OriginalString.EndsWith("Strings.xaml"));


   if (current != null)
   {
      Application.Current.Resources.MergedDictionaries.Remove(current);
   }

   Application.Current.Resources.MergedDictionaries.Add(resources);
}

private void US_OnClick(object sender, RoutedEventArgs e)
{
   LoadStringResource("en-US");
}

private void GB_OnClick(object sender, RoutedEventArgs e)
{
   LoadStringResource("en-GB");
}

and finally in the constructor let’s default to en-GB, so simply add this line after the InitializeComponent

LoadStringResource("en-GB");

Now run the application, be default you should see en-GB strings and then press the US button to see the en-US version etc.

Finishing touches

In some situations we might want to switch the languages strings used via an option (very useful when debugging but also in you’re natural language is not the same as the default on your machine). In most cases, we’re likely to want to switch the language at start-up to match the machines’s culture/language.

Using ResourceDictionary might look a little more complex than CSV files, but should be easy for your translators to use and being, ultimately, XML – we could ofcourse write a simple application to allow the translators to view strings etc. in a tabular format.

We can deploy as many or as few localized resources as we need on a machine.

References

Checkout this a useful document WPF Globalization and Localization Overview from Microsoft.