Soluling home   Document home

.NET MAUI Localization and Internationalization

.NET MAUI Localization and Internationalization

.NET Multi-platform App UI (.NET MAUI) is a framework for building Android, iOS, macOS, and Windows applications from one code base. It is the successor of Xamarin.Forms, which Microsoft no longer supports. Soluling Desktop and Soluling Cloud support .NET MAUI.

.NET MAUI uses the standard .NET localization: you put the strings into .resx files, and the application reads them from resources at run time. Your XAML pages and your C# code use the strings through the class that Visual Studio generates for the .resx file. Soluling localizes the .resx files of your project and creates the localized .resx files. When you build the application, the .NET build compiles them into satellite assemblies for each platform.

Soluling does not localize the strings that are hard coded in your .xaml files. .NET MAUI compiles the XAML into the application, so there is no XAML resource that a localized version could replace. Before you localize the application, move every string from the XAML pages and the code into a .resx file. This is what this page describes.

The end of the document contains links to get the full source code of the samples. After reading this document, we recommend reading a tutorial about how to use Soluling.

Create a resource file

Add a resource file to your project. In Visual Studio, right-click the folder where you want the file, for example, Resources\Strings, and choose Add | New Item | Resources File. Name the file, for example, AppResources.resx.

Open the file in the resource editor and set Access Modifier to Public. This makes Visual Studio generate a public class (AppResources) with a static property for each string, so that your XAML pages can use the strings. The project file then contains the following item:

<ItemGroup>
  <EmbeddedResource Update="Resources\Strings\AppResources.resx">
    <Generator>PublicResXFileCodeGenerator</Generator>
    <LastGenOutput>AppResources.Designer.cs</LastGenOutput>
  </EmbeddedResource>
</ItemGroup>

Add a string for each text of your user interface, including the texts that a screen reader speaks. Give each string a descriptive name and write the text in the original language. Use the Comment column to tell the translators the context of the string. Soluling shows the comments to the translators. Here are the strings of the Simple sample.

Name Value Comment
Home Home Title of the main page
Hello Hello, World! Main title
Welcome Welcome to .NET Multi-platform App UI Subtitle
WelcomeDescription Welcome to dot net Multi platform App U I Text that a screen reader speaks for the subtitle
BotDescription Cute dot net bot waving hi to you! Text that a screen reader speaks for the image
ClickMe Click me Button caption
ClickMeHint Counts the number of times you click Screen reader hint of the button
ClickedSingular Clicked {0} time {0} is 1
ClickedPlural Clicked {0} times {0} is the number of clicks

You only create the resource file of the original language. Soluling creates the localized files, such as AppResources.fi.resx and AppResources.de.resx, for you.

Tell .NET the language of the strings in the original resource file. Add the NeutralLanguage property to the project file:

<PropertyGroup>
  <NeutralLanguage>en</NeutralLanguage>
</PropertyGroup>

Replace the strings in XAML with resource strings

A XAML page that has not been internationalized contains the strings as attribute values:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Simple.MainPage">
  <VerticalStackLayout Spacing="25" Padding="30,0" VerticalOptions="Center">
    <Label Text="Hello, World!" FontSize="32" HorizontalOptions="Center" />
    <Label Text="Welcome to .NET Multi-platform App UI" FontSize="18" HorizontalOptions="Center" />
    <Button x:Name="CounterBtn" Text="Click me" SemanticProperties.Hint="Counts the number of times you click" Clicked="OnCounterClicked" HorizontalOptions="Center" />
  </VerticalStackLayout>
</ContentPage>

Add an XML namespace for the namespace of the resource class, and replace each string with an x:Static markup extension that refers to the property of the string. The namespace of the class is the root namespace of the project followed by the folder of the .resx file, here Simple.Resources.Strings.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:strings="clr-namespace:Simple.Resources.Strings"
             x:Class="Simple.MainPage">
  <VerticalStackLayout Spacing="25" Padding="30,0" VerticalOptions="Center">
    <Label Text="{x:Static strings:AppResources.Hello}" FontSize="32" HorizontalOptions="Center" />
    <Label Text="{x:Static strings:AppResources.Welcome}" FontSize="18" HorizontalOptions="Center" />
    <Button x:Name="CounterBtn" Text="{x:Static strings:AppResources.ClickMe}" SemanticProperties.Hint="{x:Static strings:AppResources.ClickMeHint}" Clicked="OnCounterClicked" HorizontalOptions="Center" />
  </VerticalStackLayout>
</ContentPage>

Do the same for every property that the user can see or hear, for example, Text, Title, Placeholder, SemanticProperties.Description, and SemanticProperties.Hint. Do not move values that are not text, such as colors, sizes, or image file names, unless they depend on the language.

The x:Static markup extension reads the value when the page is created. The page shows the strings in the language that is active at that moment. See Select the language at run time.

Replace the strings in code with resource strings

Use the properties of the resource class also in your C# code. Do not concatenate strings. Use a format string with placeholders instead, so that the translator can change the order of the words.

using Simple.Resources.Strings;

private void OnCounterClicked(object sender, EventArgs e)
{
  count++;

  if (count == 1)
    CounterBtn.Text = String.Format(AppResources.ClickedSingular, count);
  else
    CounterBtn.Text = String.Format(AppResources.ClickedPlural, count);
}

Configure the platforms

Some platforms need to know the languages that the application supports.

Select the language at run time

By default, the application uses the language of the device. If the localized resources of that language exist, the application shows the localized strings. Otherwise it shows the original strings.

If you want to let the user select the language, set the UI culture before the pages are created, for example, in the constructor of the App class:

using System.Globalization;

public App()
{
  // Use Finnish. In a real application, read the language selected by the user from the settings.
  var culture = new CultureInfo("fi");
  CultureInfo.DefaultThreadCurrentUICulture = culture;
  CultureInfo.CurrentUICulture = culture;

  InitializeComponent();
}

protected override Window CreateWindow(IActivationState? activationState)
{
  return new Window(new AppShell());
}

Because x:Static reads the strings when a page is created, a page that already exists does not change when you change the language. Create the pages again, for example, by assigning a new AppShell to the page of the window (Windows[0].Page = new AppShell();), or ask the user to restart the application.

Desktop process Desktop process

When the strings are in the .resx files, create a Soluling project for the project file.

  1. Start Soluling. Drag your .csproj file (e.g., Simple.csproj) on Soluling or click the New from File or Files button and select the file. The Project Wizard appears.
  2. On the Resources page, keep .resx checked. Soluling does not check XAML in a .NET MAUI project, because localized XAML has no effect. Click Next.
  3. If there are existing localized .resx files, Soluling shows the Import existing localized files page where you can select the languages to import.
  4. On the Select Languages page, select the original language and add the languages that you want to support, for example, Finnish. Click Finish.
  5. Translate the project.
  6. Choose Home | Build All to build the localized files. Soluling writes the localized .resx files, for example, Resources\Strings\AppResources.fi.resx, next to the original .resx file.
  7. Build the application in Visual Studio or with dotnet build. Because the localized .resx files are now in your project folder, the .NET build includes them and creates a satellite assembly, for example, fi\Simple.resources.dll, for every target platform.

In a .NET MAUI project, Soluling does not compile the satellite assemblies itself, because the project has a separate output directory for each target platform. The Compile method on the Satellite assembly options sheet is None.

When you add or change strings, edit the original .resx file in Visual Studio, open the Soluling project, and choose Home | Scan. Soluling keeps the existing translations, and you only translate the strings that were added or changed. Then build again. All this can be done in your build process using Soluling's command-line tool, SoluMake.

Cloud process Cloud process

With Soluling Cloud, you and your translators translate the strings in a browser using Translate. You need a Soluling account. See the available Cloud plans. There are two ways to start:

Resource files

Create a project

  1. Sign in at www.soluling.com and open Cloud > Dashboard for Projects, Users and Terminology.
  2. Click Add a new project, then Select files and pick the original resource file (e.g., Resources\Strings\AppResources.resx), or drag and drop the file onto the page. Click Create.
  3. On the next page, check the project's Name. It defaults to the file's name.
  4. Set the Original language to the language of your application, for example, English.
  5. Under Target languages, check the languages you want to translate to, for example, Finnish and German.
  6. Under Source type, select .NET resource file (.resx).
  7. Click Create. The project appears on the dashboard, and its status changes to Ready once the file has been processed.

If your application has several .resx files, add them to the project later from the project's Manage page.

Translate the project

Click the project's green Translate button on the dashboard. Translate opens the project, and you can type the translations into the grid. Machine translation suggestions, translation memory, and spell checking are available there. You do not have to finish every language before moving on to the next step.

Get the localized files

  1. In Translate's top bar, click the project's name. It opens the project's Manage page, the same page that the project's Manage button on the dashboard opens.
  2. If you have translated something since the localized files were last built, the page says Localized files need to be built. Click Build localized files and wait until it finishes.
  3. The Files section lists each file with a download button for each target language. Save each localized file next to the original, named with the language code, for example, Resources\Strings\AppResources.fi.resx.
  4. Build the application in Visual Studio or with dotnet build.

Update the project

When you add or change strings, upload the new file, translate what is new or changed, and download the localized files again. On the dashboard, click the project's Update file button (the upload arrow), select the new file or drop it onto the page, and click Update. Existing translations are kept, so you only translate the strings that were added or changed.

The dashboard is one of four ways to use Soluling Cloud. You can also use the SoluCLI command-line tool, Soluling Desktop, or SoluMake, which can run the update and build steps in your build pipeline. See the Cloud tutorial for all of them.

Project file

A project file refers to other files, so you create the project in Soluling Desktop, upload it to Soluling Cloud, and build the localized files in Soluling Desktop.

  1. Create the project in Soluling Desktop as described in Desktop process, and save it. This creates the Soluling project file (.ntp).
  2. On the Translate tab, in the Send and Receive group, click Configure Cloud.
  3. Log in with your Soluling account, and select the cloud account to use if you have several.
  4. Click OK to create the cloud project. When Soluling asks whether to upload the local project to the cloud, click Yes, and Yes again to wait until the cloud has processed it.
  5. Translate the project in Translate, or click the project's Manage button and invite a translator for each language under Languages and Invitations.
  6. In Soluling Desktop, on the Translate tab, click Import from Cloud. Soluling downloads the translations made in the cloud and imports them into your local project. Then choose Home | Build All, and build the application.

When you change the strings, in Soluling Desktop, on the Home tab, click Scan. On the Translate tab, click Upload to Cloud. The cloud project is updated to match your local one, and translations that exist only in the cloud so far are kept. Translate the new and changed strings, then Import from Cloud and build again.

Samples

GitHub and <data-dir>\Samples\MAUI contain the following .NET MAUI sample. MAUI.sln contains it.

Directory Description Notes
Simple The application of this document. The strings are in Resources\Strings\AppResources.resx, and the XAML pages and the code use them. Simple.ntp localizes the project file into Finnish, and AppResources.fi.resx is the localized resource file. The application targets .NET 10 on Android, iOS, Mac Catalyst, and Windows. Try this first!

Configuring .NET MAUI localization

You create a .NET MAUI localization project by adding a project file (e.g., D:\Sample\Simple.csproj) into your Soluling project. You can configure how to localize your .NET MAUI project file by selecting the item in the project tree, right-clicking, and choosing the Options menu. A source dialog appears that lets you edit the options. This source uses the following option sheets.

See .NET for more about .NET localization.