Привет! Меня зовут Егор, я работаю в компании DD Planet. В статье я хочу поделиться знаниями о том, как быстро и легко локализовать .NET-приложение с помощью собственного решения для локализации — Slang.Net.
Введение
Когда я только начинал свой путь как .NET-разработчик, локализация приложений казалась вполне понятной задачей. Стандартные механизмы — resx-файлы и связанные с ними сервисы — справлялись со своей работой, и тогда я даже не задумывался о том, что может быть иначе. Однако все изменилось, когда я переключился на разработку Flutter-приложений.
В этом новом для меня мире я открыл slang — инструмент для работы с локализацией, который существенно упростил рутину, связанную с поддержкой нескольких языков. Когда я вернулся к .NET-разработке, мой взгляд на стандартные механизмы локализации изменился. Простота и удобство, которые я нашел в slang, заставили меня осознать, насколько сложными и громоздкими кажутся resx-файлы по сравнению с этим инструментом.
Дальнейшие размышления привели меня к идее создать порт slang для .NET, адаптированный под особенности платформы.
Что такое Slang.Net?
Slang.Net — порт библиотеки slang из сообщества Dart/Flutter для .NET, предоставляющей типобезопасный и удобный способ работы с переводами в приложении. Порт дополнен новыми возможностями, включая форматирование строк, которое отсутствовало в оригинальной версии библиотеки на момент портирования, но было добавлено в одном из последних обновлений.
Основные возможности
Типобезопасность: Генерирует строго типизированный код для работы с переводами;
Поддержка форматирования: Встроенная поддержка форматирования строк и параметров;
Плюрализация: Автоматическая обработка множественных форм слов в зависимости от их количества;
Интеграция с GPT: Использование GPT-модели для автоматического перевода строк.
Гибкость: Поддержка списков, словарей и вложенных структур переводов;
Удобство: Простая интеграция и использование в проекте. Поддержка иерархического представления для строк локалей. Например, можно заложить иерархию по фичам и контроллерам — так, как вам удобнее.
Быстрый старт
Шаг 1: Установка пакета
Установите Slang.Net
dotnet add package Slang.Net
Шаг 2: Добавление JSON-файлов с переводами
Добавьте json файл для локали по умолчанию. Можно положить в любую папку проекта, для примера, положим его в папку i18n
:
• strings_ru.i18n.json
Названия файлов должны иметь следующий вид:<название>_<культура>.i18n.json
Культура представлена в привычной форме кода языка, с указанием или без указания кода страны (“ru”, “en-US”). Для файла локализации по умолчанию культуру можно не указывать.
Пример содержимого strings_ru.i18n.json
:
{
"greeting": "Привет, {name}!"
}
Шаг 3: Конфигурация Slang.Net
Создайте файл slang.json
в корне проекта:
{
"base_culture": "ru"
}
Шаг 4: Включение файлов в проект
Добавьте следующие строки в ваш .csproj
файл:
<ItemGroup>
<AdditionalFiles Include="i18n\*.i18n.json" />
<AdditionalFiles Include="slang.json" />
</ItemGroup>
Файлы с типом сборки AdditionalFiles
используются анализаторами и генераторами кода и не встраиваются в итоговую сборку проекта.
Шаг 5: Создание partial класса для переводов
Создайте класс с любым названием для доступа к строкам локализации, например, Strings
.
В InputFileName
указываем префикс из названия json файлов, на основе которых будет генерация для этого класса.
using Slang;
[Translations(InputFileName = "strings")]
public partial class Strings{ }
Можно добавить геттер для быстрого доступа к рутовому узлу словаря локализации
[Translations(InputFileName = "strings")]
public partial class Strings
{
public static Strings Loc => Instance.Root;
}
Шаг 6: Использование в вашем коде
После генерации кода вы можете использовать переводы следующим образом:
// Установка текущей культуры
Strings.SetCulture(new CultureInfo("ru-RU"));
// Использование перевода
Console.WriteLine(Translations.Instance.Root.Greeting("Иван")); // Выведет: Привет, Иван!
Расширенные возможности
Интерполяция строк
Slang.Net поддерживает интерполяцию и типизацию параметров:
{
"welcome": "Добро пожаловать, {name:string}!"
}
В коде:
Console.WriteLine(Translations.Instance.Root.Welcome("Мария"));
По умолчанию тип параметра object
. Так же есть более сложный синтаксис установки типов параметров:
{
"greet2": "Hello {name}, you are {age} years old",
"@greet2": {
"placeholders": {
"name": {
"type": "string"
},
"age": {
"type": "int"
}
}
}
}
Установка отдельного словаря с префиксом @
позволяет использовать еще несколько фич библиотеки, о которых пойдет речь ниже.
Поддержка склонений с числительными (Pluralization)
Обработка множественных форм слов в зависимости от количества:
{
"apple": {
"one": "У меня {n} яблоко.",
"few": "У меня {n} яблока.",
"many": "У меня {n} яблок.",
"other": "У меня {n} яблока."
}
}
В коде:
Console.WriteLine(Translations.Instance.Root.Apple(3)); // Выведет: У меня 3 яблока.
Доступны следующие ключи для работы данной фичи: zero
, one
, two
, few
, many
, other
.
По умолчанию имя параметра n
, его можно поменять на свой с помощью param
:
{
"someKey": {
"apple(param=appleCount)": {
"one": "I have one apple.",
"other": "I have multiple apples."
}
}
}
Имя параметра по умолчанию можно переопределить, используя параметр PluralParameter
в атрибуте TranslationsAttribute
:
[Translations(
InputFileName = "strings",
PluralParameter = "count")]
internal partial class Strings;
Форматирование строк
Поддержка форматирования дат и чисел. Можно использовать следующие типы с поддержкой форматирования в ToString(string format)
- int
, long
, double
, decimal
, float
, DateTime
, DateOnly
, TimeOnly
, TimeSpan
. Для остальных типов используется метод string.Format(format, cultureInfo)
.
{
"dateExample": "Date {date}",
"@dateExample": {
"placeholders": {
"date": {
"type": "DateTime",
"format": "dd MMMM HH:mm"
}
}
}
}
В коде:
String s = Strings.Instance.Root.DateExample(DateTime.Now); // Date 17 October 22:25
Комментарии в переводах
Вы можете добавлять комментарии в JSON-файлы, которые будут включены в сгенерированный код:
{
"mainScreen": {
"button": "Submit",
// ignored as translation but rendered as a comment
"@button": "The submit button shown at the bottom",
// or use
"button2": "Submit",
"@button2": {
"description": "The submit button shown at the bottom"
}
}
}
Тогда сгенерированный код будет включать в себя переопределенный xml комментарий. По умолчанию комментарий включает в себя содержимое строки локализации.
/// The submit button shown at the bottom
///
/// In ru, this message translates to:
/// **"Submit"**
public virtual string Button => "Submit";
Ссылки на другие переводы
Slang.Net поддерживает ссылки на другие строки перевода с помощью @:
:
{
"greeting": "Здравствуйте",
"welcome": "@:greeting, {name}!"
}
В коде:
Console.WriteLine(Translations.Instance.Root.Welcome("Павел")); // Выведет: Здравствуйте, Павел!
Путь на локаль должен быть абсолютным относительно рутового объекта дерева с локалями.
Использование списков и словарей
Slang.Net поддерживает списки и словари:
{
"menu": {
"items": [
"Главная",
"О нас",
"Контакты"
]
}
}
В коде:
foreach (var item in Translations.Instance.Root.Menu.Items)
{
Console.WriteLine(item);
}
Перевод с помощью GPT
Одной из уникальных возможностей Slang.Net является интеграция с моделями GPT для автоматического перевода строк. Эта функция особенно полезна, когда вам нужно быстро локализовать приложение на несколько языков. Для перевода достаточно указать код культуры для перевода, и инструмент сам создаст файл локализации для новой локали или обновит существующий. В настоящее время поддерживается только OpenAI API.
Установка slang-gpt-cli
Для начала установите CLI утилиту с помощью команды:
dotnet tool install -g Slang.CLI
Настройка конфигурации
Создайте или отредактируйте файл slang.json и добавьте следующую конфигурацию:
{
"base_culture": "ru",
"gpt": {
"model": "gpt-4o-mini",
"description": "Showcase for Slang.Net"
}
}
Тип модели и описание проекта являются обязательными параметрами для старта перевода.
Запуск перевода
Выполните команду для перевода:
slang gpt --project=<csproj> --target=<culture> --api-key=<api-key>
Где:
<csproj>
— путь к вашему проекту .NET. Если параметр--project
не задан, будет использоваться первый найденныйcsproj
файл проекта в текущей директории.<api-key>
— ваш API-ключ OpenAI.<culture>
— двухбуквенный код культуры, перевод на которую вы хотите получить. Если--target
не задан, будут использоваться культуры уже существующих файлов локализации.
Чтобы выполнить полный перевод базовой локали с перезаписью текущих локалей (если файлы целевых локалей уже существуют), укажите аргумент --full
.
Для исключения отдельных ключей из перевода используйте модификатор ignoreGpt
:
{
"key1": "This will be translated",
"key2(ignoreGpt)": {
"key3": "This will be ignored"
}
}
Список поддерживаемых моделей которые можно указать в slang.json
следующий:
Модель |
Контекст (токены) |
Стоимость за 1k входящих токенов |
Стоимость за 1k исходящих токенов |
gpt-3.5-turbo |
4096 |
$0.0005 |
$0.0015 |
gpt-3.5-turbo-16k |
16384 |
$0.003 |
$0.004 |
gpt-4 |
8192 |
$0.03 |
$0.06 |
gpt-4-turbo |
64000 |
$0.01 |
$0.03 |
gpt-4o |
128000 |
$0.005 |
$0.015 |
gpt-4o-mini |
128000 |
$0.00015 |
$0.0006 |
Примечание: 1k токенов ≈ 750 слов на английском языке.
Преимущества использования GPT:
Скорость: Автоматический перевод большого количества строк за короткое время
Наличие контекста: GPT-модели учитывают контекст, что повышает качество перевода
Экономия ресурсов: Сокращение затрат на ручной перевод
Интеграция с различными платформами
Slang.Net не зависит от конкретной платформы и может использоваться в:
Консольных приложениях;
WPF-приложениях;
ASP.NET Core приложениях (включая фронтенд: Blazor, MVC);
Xamarin и MAUI приложениях.
Локализация Web Api
Для установки поддерживаемых культур можно воспользоваться свойством SupportedCultures
и BaseCulture
:
using Slang.WebApi.i18n;
var builder = WebApplication.CreateBuilder(args);
// Configure supported cultures
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
string[] supportedCultures = Strings.SupportedCultures.Select(c => c.ToString()).ToArray();
options.SetDefaultCulture(Strings.BaseCulture.ToString())
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
});
Осталось подключить middleware
, который переключает культуру в зависимости от значения в хедере Accept-Language
.
app.UseRequestLocalization();
Локализация AvaloniaUI
Рекомендуется устанавливать строки в XAML через Bindings для переключения локализации в рантайме (без необходимости перезапускать приложение).
<MenuItem Header="{Binding Root.Screen.Locale1, Source={x:Static localization:Strings.Instance}}" />
Instance
поддерживает интерфейс INotifyPropertyChanged
для уведомления UI о изменении свойства Root
.
Для переключения языка можно воспользоваться методом SetCulture
.
Для использования локали с параметрами через xaml нужно использовать MultiValueConverter
, передавая в параметры Instance
и параметры локали.
Например, локаль с параметрами задана следующим образом:
"Pages": {
"Explorer": {
"SelectedItems": {
"one": "Выбран: {n}",
"other": "Выбрано: {n}"
},
}
},
Тогда ее применение в xaml будет выглядеть следующим образом:
<TextBlock >
<TextBlock.Text>
<MultiBinding Converter="{converters:SelectedCountConverter}">
<Binding Path="Root" Source="{x:Static localization:Strings.Instance}"/>
<Binding Path="SelectedCount"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
ГдеSelectedCount
- свойство ViewModel.
Конвертер будет выглядеть следующим образом:
public class SelectedCountConverter : IMultiValueConverter
{
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
{
if (values.Count == 2)
{
if (values[0] is Strings loc)
{
if (values[1] is int count)
return loc.Pages.Explorer.SelectedItems(count);
}
}
return null;
}
}
В последних версиях C# (с 11 версии) данный код можно упростить до:
public class SelectedCountConverter : IMultiValueConverter
{
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
{
return values is [Strings loc, int count] ? loc.Pages.Explorer.SelectedItems(count) : null;
}
}
Таким образом, при смене языка через метод Strings.SetCulture
, текст автоматически поменяется в интерфейсе на текст выбранной культуры. Именно поэтому не рекомендуется использовать тексты локализации внутри ViewModel
, только если это не текстовки каких-либо диалоговых окон, которые обычно не отображаются в момент смены языка.
Заключение
Slang.Net — инструмент для локализации .NET-приложений, который:
Упрощает работу с переводами;
Делает код чище и понятнее;
Позволяет автоматически переводить строки на разные языки благодаря интеграции с GPT;
Экономит время и ресурсы.
Если вы ищете решение для поддержки нескольких языков в вашем приложении, Slang.Net — отличный выбор. В статье я кратко описал возможности библиотеки. Дополнительные параметры и флаги вы найдете в файле документации репозитория, где также можно посмотреть примеры их использования.
Буду рад вашим комментариям и предложениям по доработке функционала, API методов, а также новым issue, в случае появления багов или пожеланий. Библиотека молодая, но как порт оригинальной версии на Dart, она уже включает в себя опыт исправления типовых багов, выявленных ранее.
Полезные ссылки