В WinUI 3 реализована поддержка многоязычности, но на данный момент наиболее стабильный подход предполагает перезапуск приложения после смены языка — только так можно добиться корректного отображения перевода. Теоретически существует способ обновить интерфейс без перезапуска, но он сопряжён с множеством сложностей. Я сам столкнулся с этой задачей, долго подбирал рабочий алгоритм: при попытке динамической смены языка либо текст не обновлялся, либо возникали критические ошибки. После множества экспериментов я выработал логичную схему интернационализации для WinUI 3, которой поделюсь ниже.
Сначала добавим логику определения языка в кодbehind файла App.xaml.cs. Если пользователь не выбрал собственный язык, программа берёт язык операционной системы по умолчанию; если системный язык не поддерживается приложением, используется английский.
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
const string LangSettingKey = "AppLanguageCode";
// Полный список поддерживаемых языков (краткие коды локализаций)
List<string> supportLangs = new() { "zh-CN", "en-US", "th-TH" };
string finalLang = "en-US"; // Язык по умолчанию
try
{
var localSetting = ApplicationData.Current.LocalSettings;
// ① Если сохранённый язык есть в локальных параметрах — берём его
if (localSetting.Values.TryGetValue(LangSettingKey, out var savedVal))
{
finalLang = NormalizeLang(savedVal?.ToString() ?? "");
}
else
{
// ② При отсутствии сохранённых настроек берём первый предпочтительный язык системы
string sysLang = ApplicationLanguages.Languages.FirstOrDefault() ?? "";
sysLang = NormalizeLang(sysLang);
// ③ Если системный язык есть в списке поддерживаемых — используем его, иначе английский
finalLang = supportLangs.Contains(sysLang) ? sysLang : "en-US";
}
}
catch
{
// При запуске без упаковки ApplicationData недоступен, игнорируем исключение
}
ApplicationLanguages.PrimaryLanguageOverride = finalLang;
_window = new MainWindow();
_window.Activate();
}
/// <summary>
/// Нормализация кода языка: преобразует системное значение zh-Hans-CN в zh-CN
/// </summary>
private static string NormalizeLang(string lang)
{
if (string.IsNullOrEmpty(lang)) return lang;
return lang switch
{
"zh-Hans-CN" => "zh-CN",
"zh-Hant-TW" => "zh-TW", // При необходимости поддержки традиционного китайского
_ => lang
};
}Code language: plaintext (plaintext)
Данный код можно доработать под свои нужды. К примеру, список поддерживаемых языков лучше вынести в отдельный статический класс, чтобы не дублировать набор кодов в разных частях программы. Пример общего статического хранилища языков ниже:
public class Languages
{
public static List<string> supportLangs = new() { "zh-CN", "en-US", "th-TH" };
}Code language: plaintext (plaintext)
Основная задача вышеописанного метода — инициализировать язык при запуске приложения: сначала проверяем сохранённые пользовательские настройки, при их отсутствии берём язык ОС, если он не поддерживается — включаем английский интерфейс.
Страница для ручного переключения языка
Требуется отдельный интерфейс для выбора языка пользователем. Я создал страницу Page, через которую открывается меню смены локализации.
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="ThaiTong.Pages.LanguageSettingPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ThaiTong.Pages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<StackPanel Padding="20" Spacing="24">
<TextBlock x:Uid="Txt_LangTip" FontSize="22"/>
<ComboBox x:Name="cbLangSelect" Width="320" Height="40" CornerRadius="6">
<ComboBoxItem Tag="zh-CN" Content="Упрощённый китайский"/>
<ComboBoxItem Tag="en-US" Content="English"/>
<ComboBoxItem Tag="th-TH" Content="ภาษาไทย"/>
</ComboBox>
<StackPanel Orientation="Horizontal" Spacing="12">
<Button x:Name="btnSave" x:Uid="Btn_Save" Content="Save" Click="BtnSave_Click" Width="120" Height="38" CornerRadius="5"/>
</StackPanel>
<TextBlock Foreground="Gray" Text="Примечание: после сохранения требуется перезапустить приложение для применения языка."/>
</StackPanel>
</Page>
Code language: plaintext (plaintext)
Кодbehind страницы настроек языка:
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Microsoft.Windows.ApplicationModel.Resources;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using ThaiTong.Common;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Globalization;
using Windows.Storage;
// Дополнительная информация по WinUI, структуре проектов и шаблонам: http://aka.ms/winui-project-info.
namespace ThaiTong.Pages
{
/// <summary>
/// Пустая страница, которую можно открыть отдельно или перейти на неё через Frame.
/// </summary>
public sealed partial class LanguageSettingPage : Page
{
private const string LangSettingKey = "AppLanguageCode";
private string _currentLang;
private readonly ResourceLoader _resLoader;
public LanguageSettingPage()
{
InitializeComponent();
_resLoader = new ResourceLoader();
var setting = ApplicationData.Current.LocalSettings;
if (setting.Values.TryGetValue(LangSettingKey, out var saveLang))
{
_currentLang = saveLang.ToString();
}
else
{
string sysLang = ApplicationLanguages.Languages.FirstOrDefault() ?? "";
_currentLang = Languages.supportLangs.Contains(sysLang) ? sysLang : "en-US";
}
// Выбор текущего языка в выпадающем списке
foreach (var item in cbLangSelect.Items)
{
if (item is ComboBoxItem cbi && cbi.Tag.ToString() == _currentLang)
{
cbLangSelect.SelectedItem = item;
break;
}
}
}
// Сохранение выбранного языка
private async void BtnSave_Click(object sender, RoutedEventArgs e)
{
if (cbLangSelect.SelectedItem is not ComboBoxItem selectItem) return;
string newLang = selectItem.Tag.ToString();
if (_currentLang == newLang) return;
ApplicationData.Current.LocalSettings.Values[LangSettingKey] = newLang;
_currentLang = newLang;
ApplicationLanguages.PrimaryLanguageOverride = newLang;
// Получение многоязычного текста из файлов ресурсов resw
ContentDialog tipDialog = new ContentDialog()
{
XamlRoot = this.XamlRoot,
Title = _resLoader.GetString("Dlg_LangChange_Title"),
Content = _resLoader.GetString("Dlg_LangChange_Content"),
CloseButtonText = _resLoader.GetString("Dlg_LangChange_BtnOk")
};
await tipDialog.ShowAsync();
}
}
}
Code language: plaintext (plaintext)
После сохранения нового языка появляется уведомление с просьбой перезапустить программу для вступления изменений в силу.
В WinUI 3 существует два способа реализации интернационализации: отдельно для разметки XAML и для бэкенд-кода C#.
Для элементов разметки XAML используется атрибут x:Uid, пример ниже:
<TextBlock x:Uid="Txt_LangTip" FontSize="22"/>Code language: plaintext (plaintext)
Далее нужно создать папку с ресурсами для переводов в корне проекта с именем Strings. Внутри неё создаются каталоги для каждого поддерживаемого языка (например en-US для американского английского). В каждой языковой папке лежит файл Resources.resw, где хранятся пары «ключ — перевод».
<data name="Btn_Save.Content" xml:space="preserve">
<value>Сохранить</value>
</data>Code language: plaintext (plaintext)
Значение атрибута name выступает уникальным ключом. Суффикс .Content указывает, какой атрибут элемента XAML нужно заменить переводом. Для вышеупомянутого Txt_LangTip разметка в файле resw выглядит так:
<data name="Txt_LangTip.Text" xml:space="preserve"><value>Выберите язык приложения</value></data>Code language: plaintext (plaintext)
Суффикс .Text необходим потому, что мы подменяем значение свойства Text у элемента TextBlock, поэтому обязательно указываем привязку к атрибуту контрола.
Для получения переводов в бэкенд-коде достаточно инициализировать экземпляр класса ResourceLoader:
private readonly ResourceLoader _resLoader;
public LanguageSettingPage()
{
InitializeComponent();
_resLoader = new ResourceLoader();Code language: plaintext (plaintext)
После инициализации ресурсный загрузчик позволяет получить переведённую строку по ключу через метод GetString:
Title = _resLoader.GetString("Dlg_LangChange_Title");Code language: plaintext (plaintext)
