How to Implement Internationalization and Localization of WPF Prism Modules?

How to Implement Internationalization and Localization of WPF Prism Modules?

The previous article briefly introduced the internationalization of the main project, implemented using resource dictionaries (XAML). In the past few days, I added several Prism modules and found that using resource dictionaries for internationalization and localization in submodules is not easy, and I couldn't find good reference articles. So I switched to using resource files instead.

Last updated 4/21/2020 1:45 PM
沙漠尽头的狼
8 min read
Category
WPF
Topic
WPF MVVM Framework Prism Series
Tags
.NET WPF Prism Internationalization Localization

The previous article briefly introduced the main project's internationalization using resource dictionaries (XAML). In the past few days, I added several Prism modules and found that implementing internationalization and localization in submodules with resource dictionaries was challenging, with no good reference articles found. So I switched to using resource files instead.

I. Overview

Requirements for submodule internationalization and localization:

  1. Each module needs its own separate language files.
  2. When switching languages dynamically in the main project, submodules should switch accordingly.
  3. Using Prism for the modular framework means the main project and submodules must have no direct reference (loose coupling), so switching language files in submodules cannot be done directly from the main project.

Based on the above requirements, I also defined language files (XAML) in each module. However, when switching languages in the main window, loading the module language files kept prompting that the corresponding resource dictionary files did not exist. Frustrating! In the end, I referred to the "Accelerider.Windows" internationalization approach and implemented localization and internationalization using resource files instead of XAML.

Here's the effect after modification:

Differences from the previous version:

  1. The title bar internationalization remains unchanged; only the text binding method was changed, achieving the same effect.
  2. Three submodules (Home, Client, Server) were added on the left side, dynamically loaded using Prism, and they follow the language switch of the main window.

Below is a brief introduction on how to create modules and how to implement internationalization in the main form and modules. It's a very simple overview; the specific implementation can be seen by pulling the code.

II. Adding Three Prism Modules

You can install the Prism template to quickly create module projects. Of course, manually creating a .NET Core project is also possible, but it requires a few extra steps (like installing the Prism.Wpf package (7.2.0.1422) via NuGet). I used the Prism template for quick creation.

2.1 Preparation Before Creating Modules

Download the Prism template shown in the search results above, restart VS, and it will automatically install. When creating a new project, you'll find the Prism Module template available:

Make sure to select the .NET Core 3 version, because I created the WPF project using .NET Core.

2.2 Creating Modules

Below are screenshots of the three already created module projects:

Currently, the file organization of the three modules is similar:

  • I18nResources: Resource folder containing 3 language resource files and a T4 template file (used to reference language keys). The T4 template file is identical across the three modules and the main project. You can check the source code on GitHub for details.
  • Views: Contains view files. Currently, only the TabItem view displayed in the main window's TabControl is used, i.e., MainTabItem.xaml, which inherits from TabItem.
  • xxxxModule.cs: Prism template definition file, used by Prism for module discovery.

Key points to note for the three modules:

  1. Edit the module project file to modify the output directory:
// Part of the code omitted. The line below is set to False to exclude .NET Core version info from output path.
<AppendTargetFrameworkToOutputPath>False</AppendTargetFrameworkToOutputPath>
// Part omitted. Modify Debug and Release output directories for unified module loading by the main project.
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
  <OutputPath>..\Build\Debug\Modules</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
  <OutputPath>..\Build\Release\Modules</OutputPath>
</PropertyGroup>
// Part omitted.
  1. In XXXModule, you need to add a reference to the resource file's ResourceManager to another library for later use when switching languages. For example, add the following code in the constructor of HomeModule. That's all that's needed for the module's internationalization and localization:
I18nManager.Instance.Add(TerminalMACS.Home.I18nResources.UiResource.ResourceManager);
  1. In XXXModule's RegisterTypes method, register the view "MainTabItem" with "RegionNames.MainTabRegion". The main form uses "RegionNames.MainTabRegion" to associate and load the module view.
_regionManager.RegisterViewWithRegion(RegionNames.MainTabRegion, typeof(MainTabItem));
  1. Bind the internationalized text in UI controls. The markup uses a namespace from an open-source library (link provided later). This library is directly included in the solution. i18NResources:Language is the class generated by the T4 template file, which references the translation keys. Example of binding text:
<TextBlock Grid.Row="2" Text="{markup:I18n {x:Static i18NResources:Language.MainTabItm_Header}}" />

III. Main Project

The directory structure of the main project is as follows:

3.1 Dynamically Loading Prism Modules

The key code for configuring the loading of the three modules is in the App.xaml.cs file. As shown above, the three module outputs are placed in the Modules directory. The main project directly loads this directory. Other loading methods include using configuration files, etc., which can be referenced from the official Prism examples (link at the end).

protected override IModuleCatalog CreateModuleCatalog()
{
    string modulePath = @".\Modules";
    if (!Directory.Exists(modulePath))
    {
        Directory.CreateDirectory(modulePath);
    }
    return new DirectoryModuleCatalog() { ModulePath = modulePath };
}

The main form displays the TabItem views registered by submodules using prism:RegionManager.RegionName, which is the region string registered in each submodule. It is associated with the corresponding TabItem view of the module. Code:

<TabControl Grid.ColumnSpan="2" SelectedIndex="0"
    Style="{StaticResource MainTabControlStyle}"
    ItemContainerStyle="{StaticResource MainTabItemStyle}"
    prism:RegionManager.RegionName="{x:Static ui:RegionNames.MainTabRegion}"/>

The main form displays submodule views using a TabControl:

Submodule TabItem view

For the main project to load submodules properly, the main project's project file also needs to modify its output directory:

// Part omitted. The line below is set to False to exclude .NET Core version info from output path.
<AppendTargetFrameworkToOutputPath>False</AppendTargetFrameworkToOutputPath>
// Part omitted. Modify Debug and Release output directories for unified module loading by the main project.
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
  <OutputPath>..\Build\Debug</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
  <OutputPath>..\Build\Release</OutputPath>
</PropertyGroup>
// Part omitted.

3.2 Changing Language File Format

Removed the original XAML language files and replaced them with resx resource files, similar to the resource file types of the three modules. Below are the main project's resource files:

Using resource files as language files

Using resource files is more convenient for editing than XAML files. Initially, I considered using resource files for internationalization but stubbornly tried XAML files.

Tinkering can be educational

3.3 Core Code for Language Switching

The key code for dynamic language switching changed to:

public static void SetLanguage(string language = "")
{
    if (string.IsNullOrWhiteSpace(language))
    {
        language = ConfigHelper.ReadKey(KEY_OF_LANGUAGE);
        if (string.IsNullOrWhiteSpace(language))
        {
            language = System.Globalization.CultureInfo.CurrentCulture.ToString();
        }
    }

    ConfigHelper.SetKey(KEY_OF_LANGUAGE, language);
    _lastLanguage = language;

    var culture = new System.Globalization.CultureInfo(language);
    I18nManager.Instance.CurrentUICulture = culture;
}

The core switch is the last line. I won't go into detail; the solution contains the library and source code:

I18nManager.Instance.CurrentUICulture = culture;

IV. Source Code

V. References

Keep Exploring

Related Reading

More Articles