Почему вашему C# приложению нужна интернационализация?
Представьте: вы написали крутой сервис для управления заметками. Всё работает, интерфейс лаконичный, пользователи довольны. Но только русскоязычные. Стоит вам выйти на рынок США или Германии — и проект встанет. Не потому, что код плохой, а потому что кнопка «Сохранить» не превращается в «Save», а сообщение «Ошибка ввода» пугает иностранца.
Это и есть проблема локализации. В мире .NET её решение называется интернационализация C# (или i18n — от слова «internationalization», где 18 — количество букв между i и n). Если вы используете C# и платформу .NET (Core, 5/6/7/8/9), у вас под капотом уже есть мощнейший инструментарий, чтобы сделать приложение мультиязычным. В этой статье мы разберём его на практике: от простых ресурсов до смены языка «на лету».
Основы: что такое культура и нейтральный язык
Прежде чем писать код, разберёмся с терминами. В .NET за локализацию отвечают два ключевых класса из пространства имён System.Globalization:
- CultureInfo — объект, описывающий язык и региональные стандарты (например,
ru-RU,en-US,de-DE). - CurrentUICulture — текущая культура пользовательского интерфейса. Именно на неё ориентируется система при поиске переводов.
- CurrentCulture — формат дат, чисел, валюты. Его тоже можно менять, но для переводов важен первый.
В C# приложениях мы обычно используем ресурсные файлы (.resx). Это XML-файлы, где хранятся строки, изображения и другие данные, привязанные к конкретной культуре. Например:
Messages.resx— строки по умолчанию (обычно английский).Messages.ru.resx— русский перевод.Messages.de.resx— немецкий перевод.
Когда приложение запускается, .NET автоматически выбирает файл, соответствующий текущей UICulture. Если точного совпадения нет — использует нейтральный (базовый) файл.
Создаём первый ресурсный файл в Visual Studio
Допустим, у нас есть консольное приложение или ASP.NET Core проект. Добавим папку Resources и внутри неё файл Strings.resx. Заполним его:
Имя | Значение
-------------|-------------------
WelcomeMsg | Welcome to our app!
GoodbyeMsg | Goodbye!
Теперь создадим Strings.ru.resx:
Имя | Значение
-------------|-------------------
WelcomeMsg | Добро пожаловать в наше приложение!
GoodbyeMsg | До свидания!
Обратите внимание: имена ключей должны совпадать. После сохранения Visual Studio автоматически сгенерирует класс Strings с доступом к строкам через статические свойства.
Программное переключение языка
Чтобы интернационализация C# работала, нужно в начале выполнения приложения (например, в Main или Startup) установить нужную культуру. Вот как это выглядит:
using System.Globalization;
using System.Threading;
// Устанавливаем русский язык интерфейса
CultureInfo culture = new CultureInfo("ru-RU");
Thread.CurrentThread.CurrentUICulture = culture;
Thread.CurrentThread.CurrentCulture = culture; // для форматов дат/чисел
// Теперь обращаемся к ресурсам
Console.WriteLine(Strings.WelcomeMsg); // Выведет: Добро пожаловать в наше приложение!
Если вы хотите дать пользователю возможность переключать язык «на лету» (без перезапуска), просто вызывайте эти строки повторно после смены культуры. В ASP.NET Core для этого используют middleware:
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new[] { new CultureInfo("en-US"), new CultureInfo("ru-RU") },
SupportedUICultures = new[] { new CultureInfo("en-US"), new CultureInfo("ru-RU") }
});
Этот middleware автоматически определяет язык браузера пользователя (заголовок Accept-Language) и устанавливает соответствующую культуру для каждого запроса.
Локализация строк с параметрами
Часто в сообщениях нужно подставлять динамические данные. Например: «Привет, Иван! У вас 3 новых сообщения.». В ресурсном файле мы пишем шаблон:
Имя | Значение
------------------|--------------------------------------------
GreetingWithName | Hello, {0}! You have {1} new messages.
А в коде используем String.Format:
string userName = "John";
int msgCount = 3;
string greeting = string.Format(Strings.GreetingWithName, userName, msgCount);
Console.WriteLine(greeting); // Hello, John! You have 3 new messages.
Для русского перевода в Strings.ru.resx пишем:
Имя | Значение
------------------|--------------------------------------------
GreetingWithName | Привет, {0}! У вас {1} новых сообщений.
Обратите внимание: порядок подстановок ({0}, {1}) не меняется, а сами фразы могут строиться по-разному. Это нормально — грамматика языков отличается.
Сложные случаи: плюрализация (множественное число)
В русском языке есть три формы: 1 сообщение, 2-4 сообщения, 5+ сообщений. В английском — две: 1 message, 2 messages. Простым String.Format тут не обойтись.
Решение: используйте библиотеку Humanizer (NuGet: Humanizer) или пишите свою логику с условными конструкциями. Пример с Humanizer:
using Humanizer;
int count = 5;
string pluralized = "message".ToQuantity(count); // "5 messages"
// Для русского нужно указать культуру:
string russianPlural = "сообщение".ToQuantity(count, showQuantityAs: ShowQuantityAs.Numeric,
format: "N0", culture: new CultureInfo("ru-RU"));
// Выведет: "5 сообщений"
Но лучше хранить готовые строки для каждого числового варианта в ресурсах и выбирать через switch. Это надёжнее и проще для переводчиков.
Интернационализация в ASP.NET Core (веб-приложения)
Для веба интернационализация C# становится ещё важнее — пользователи заходят со всего мира. Помимо ресурсных файлов, в ASP.NET Core есть встроенная система локализации представлений и Data Annotations.
Локализация моделей и валидации
Предположим, у вас есть модель:
public class UserModel
{
[Required(ErrorMessageResourceName = "FieldRequired", ErrorMessage