Как организуешь работу с числительными при локализации?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Подход к локализации числительных в C# Backend
Локализация числительных — сложная задача, требующая учёта грамматических, морфологических и культурных особенностей языка. В C# экосистеме существует несколько уровней решения этой проблемы.
Основные аспекты локализации числительных
- Форматирование чисел (разделители, валюта)
- Склонение числительных по падежам
- Правильное согласование с существительными
- Особенности написания (прописью/цифрами)
- Культурные различия (например, миллиард/биллион в разных странах)
Стандартные средства .NET для базового форматирования
Для простого форматирования чисел .NET предоставляет встроенные механизмы:
// Базовое форматирование с учётом культуры
decimal value = 1234567.89m;
// Локализованное форматирование
string formattedUS = value.ToString("C", new CultureInfo("en-US")); // $1,234,567.89
string formattedRU = value.ToString("C", new CultureInfo("ru-RU")); // 1 234 567,89 ₽
string formattedDE = value.ToString("C", new CultureInfo("de-DE")); // 1.234.567,89 €
// Форматирование с разделителями
string numberRU = value.ToString("N2", new CultureInfo("ru-RU")); // 1 234 567,89
string numberUS = value.ToString("N2", new CultureInfo("en-US")); // 1,234,567.89
Библиотеки для продвинутой локализации числительных
Для сложных случаев рекомендую использовать специализированные библиотеки:
Humanizer — наиболее популярное решение
// Установка: Install-Package Humanizer.Core.ru
// Преобразование чисел в слова
1.ToWords(new CultureInfo("ru")); // "один"
2.ToWords(new CultureInfo("en")); // "two"
123.ToWords(new CultureInfo("ru")); // "сто двадцать три"
// Склонение числительных с существительными
1.ToWords(GrammaticalGender.Masculine); // "один"
1.ToWords(GrammaticalGender.Feminine); // "одна"
1.ToWords(GrammaticalGender.Neuter); // "одно"
// Форматирование с учётом контекста
"month".ToQuantity(5, new CultureInfo("ru")); // "5 месяцев"
"day".ToQuantity(21, new CultureInfo("ru")); // "21 день"
SmartFormat — для сложных шаблонов
var smart = Smart.CreateDefaultSmartFormat();
smart.AddExtensions(new ChooseFormatter());
var data = new { Count = 5, Item = "сообщение" };
string result = smart.Format("{Count} {Item:|сообщение|сообщения|сообщений}", data);
// Результат: "5 сообщений"
Кастомная реализация для полного контроля
В проектах с особыми требованиями создаю собственные провайдеры:
public interface INumeralLocalizer
{
string FormatNumber(decimal number, string culture);
string NumberToWords(long number, string culture, GrammaticalCase grammaticalCase);
string QuantityWithNoun(long number, string noun, string culture);
}
public class RussianNumeralLocalizer : INumeralLocalizer
{
private static readonly string[][] Units =
{
new[] { "", "один", "два", "три", "четыре", "пять", "шесть", "семь", "восемь", "девять" },
new[] { "", "одна", "две", "три", "четыре", "пять", "шесть", "семь", "восемь", "девять" }
};
public string NumberToWords(long number, string culture, GrammaticalCase grammaticalCase)
{
if (number == 0) return "ноль";
var parts = new List<string>();
var groups = SplitIntoGroups(Math.Abs(number));
for (int i = 0; i < groups.Length; i++)
{
var groupValue = groups[i];
if (groupValue > 0)
{
parts.Add(ProcessGroup(groupValue, i, groups.Length));
}
}
return string.Join(" ", parts);
}
private long[] SplitIntoGroups(long number)
{
// Реализация разбиения на группы (тысячи, миллионы и т.д.)
return new long[0]; // Упрощённая реализация
}
}
Архитектурные рекомендации
- Слои абстракции: Создайте интерфейс
INumeralLocalizationServiceдля инверсии зависимостей - Кэширование: Результаты преобразования чисел в слова часто кэшируются
- Фабричный метод: Для создания локализаторов по коду культуры
- Fallback-стратегия: При отсутствии локализации для конкретной культуры
Пример внедрения в ASP.NET Core
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<INumeralLocalizerFactory, NumeralLocalizerFactory>();
services.AddScoped<INumeralLocalizationService>(provider =>
{
var factory = provider.GetRequiredService<INumeralLocalizerFactory>();
var httpContext = provider.GetRequiredService<IHttpContextAccessor>().HttpContext;
var culture = httpContext?.Request.GetCulture() ?? "ru-RU";
return factory.Create(culture);
});
}
// Использование в контроллере
public class ProductsController : Controller
{
private readonly INumeralLocalizationService _localizer;
public IActionResult GetProduct(int id)
{
var product = _productService.GetProduct(id);
var viewModel = new ProductViewModel
{
Name = product.Name,
PriceFormatted = _localizer.FormatCurrency(product.Price),
ItemsInStockText = _localizer.QuantityWithNoun(
product.StockQuantity,
"товар",
GrammaticalCase.Nominative
)
};
return View(viewModel);
}
}
Особые случаи и граничные условия
- Отрицательные числа: В некоторых языках отрицание выражается по-разному
- Дробные числа: Форматирование дробей (1/2, 0.5, ½)
- Диапазоны: "от 1 до 5" vs "1–5"
- Падежи после числительных: "5 товаров", "с 5 товарами", "о 5 товарах"
- Порядковые числительные: "1-й", "первый", "первая"
Тестирование локализации
[Theory]
[InlineData(1, "ru-RU", "один")]
[InlineData(2, "ru-RU", "два")]
[InlineData(21, "ru-RU", "двадцать один")]
[InlineData(101, "en-US", "one hundred one")]
public void NumberToWords_ValidInput_ReturnsCorrectString(
long number,
string culture,
string expected)
{
var localizer = _factory.Create(culture);
var result = localizer.NumberToWords(number, culture);
Assert.Equal(expected, result);
}
Ключевые выводы: Локализация числительных требует комплексного подхода. Начинайте со стандартных средств .NET для базового форматирования, используйте Humanizer для большинства стандартных задач и создавайте кастомные решения для специфических бизнес-требований. Всегда проектируйте систему с учётом расширяемости для поддержки новых языков и культурных особенностей.