![]() |
.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.
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>
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.
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);
}
Some platforms need to know the languages that the application supports.
Info.plist files in the Platforms\iOS and Platforms\MacCatalyst folders. Otherwise the operating system does not use the localized resources.
<key>CFBundleLocalizations</key> <array> <string>en</string> <string>fi</string> </array>
Platforms\Windows\Package.appxmanifest generates the languages from the resources:
<Resources> <Resource Language="x-generate" /> </Resources>
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.
When the strings are in the .resx files, create a Soluling project for the project file.
.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..resx files, Soluling shows the Import existing localized files page where you can select the languages to import..resx files, for example, Resources\Strings\AppResources.fi.resx, next to the original .resx file.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.
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:
.resx files directly to Soluling Cloud. Because the .NET build creates the satellite assemblies, this is all a .NET MAUI application needs..csproj file in Soluling Desktop and upload it to Soluling Cloud.Resources\Strings\AppResources.resx), or drag and drop the file onto the page. Click Create.If your application has several .resx files, add them to the project later from the project's Manage page.
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.
Resources\Strings\AppResources.fi.resx.dotnet build.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.
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.
.ntp).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.
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! |
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.