Представьте: вы запускаете приложение с русской локализацией, а пользователь видит «2 товаров» вместо «2 товара». Знакомая ситуация? Мы, команда мобильных разработчиков с пятилетним опытом, сталкивались с этим десятки раз. Правильная плюрализация — не прихоть, а требование публикации в сторах и основа доверия пользователей. В английском всего две формы: one и other. В русском — четыре: one, few, many, other. Игнорирование этого — гарантированный баг при локализации на любой славянский язык. Наше решение опирается на стандарт CLDR (Common Locale Data Repository), который поддерживается всеми современными фреймворками. По нашим данным, внедрение плюрализации на старте проекта снижает количество багов локализации на 80%, а экономия на исправлении этих ошибок может достигать 70% бюджета на локализацию. Получите консультацию по внедрению плюрализации в вашем проекте уже сегодня.
Какие проблемы решает правильная плюрализация?
Грамматические ошибки в UI. Некорректные окончания (например, «5 пользователей» вместо «5 пользователей» в русском) снижают доверие к продукту. Проблемы с публикацией в сторах: строгие правила требуют корректной локализации, иначе отклонение релиза. Лишние трудозатраты: если изначально не заложить плюрализацию, переписывать код под каждый новый язык — дорого и рискованно.
Почему CLDR — единственный правильный стандарт?
CLDR предоставляет универсальный набор правил для всех языков. Использование самописных условий на основе оператора % приводит к багам для дробных, отрицательных чисел и нуля. Например, CLDR для русского языка для чисел 0, 0.5, 1.5, 2.1 возвращает 'other', а не 'few'. Нативное решение (plurals.xml / stringsdict) в пять раз надёжнее самописной if-логики — исключает баги для нестандартных чисел.
Примеры форм для русского языка по CLDR
| CLDR категория |
Пример числа |
Форма слова |
| one |
1, 21, 31 |
товар |
| few |
2, 3, 4, 22 |
товара |
| many |
5, 6, 7, 11 |
товаров |
| other |
0, 1.5, 2.1 |
товара |
Как реализовать плюрализацию на каждой платформе?
Android. Используйте файл res/values-ru/plurals.xml:
<plurals name="items_count">
<item quantity="one">%d товар</item>
<item quantity="few">%d товара</item>
<item quantity="many">%d товаров</item>
<item quantity="other">%d товара</item>
</plurals>
Вызов: resources.getQuantityString(R.plurals.items_count, count, count). Первый count — для выбора формы, второй count — подстановка в %d. Частая ошибка: передать только один аргумент — тогда число в строку не подставляется.
iOS. Создайте Localizable.stringsdict (формат plist) с ключом и вложенными правилами. Структура включает NSStringLocalizedFormatKey и категории по CLDR. Для Swift-пакетов с Xcode 15+ используйте String Catalog — визуальный редактор plural forms.
Вызов: String(format: NSLocalizedString("items_count", comment: ""), count) — автоматически выберет нужную форму.
Flutter. Пакет intl предоставляет метод Intl.plural():
Intl.plural(
count,
zero: 'нет товаров',
one: '$count товар',
few: '$count товара',
many: '$count товаров',
other: '$count товара',
locale: 'ru',
);
Или через ARB-файлы с генерацией flutter gen-l10n — опишите плюрализацию в app_ru.arb с ICU-синтаксисом.
React Native. Используйте i18next с плагином i18next-icu или нативный Intl.PluralRules (доступен с RN 0.70+). Вручную: new Intl.PluralRules('ru').select(count) возвращает 'one'/'few'/'many'/'other', по которому выбираете форму из объекта переводов.
const pluralRules = new Intl.PluralRules('ru');
const form = pluralRules.select(count);
const translations = {
one: `${count} товар`,
few: `${count} товара`,
many: `${count} товаров`,
other: `${count} товара`,
};
console.log(translations[form]);
Сравнение подходов
| Платформа |
Механизм |
Количество форм (русский) |
Генерация |
| Android |
plurals.xml |
4 (one/few/many/other) |
Ручная или через локализационный сервис |
| iOS |
stringsdict / String Catalog |
4 (one/few/many/other) |
Ручная или через Xcode |
| Flutter |
Intl.plural / ARB |
4 (one/few/many/other) |
Автоматическая через gen-l10n |
| React Native |
i18next-icu / Intl.PluralRules |
4 (one/few/many/other) |
Ручная |
Как протестировать плюрализацию на граничных значениях?
Обязательно проверьте числа 0, 1, 2, 5, 21, 1.5, -1. Для русского языка 21 относится к форме 'one', а 1.5 — к 'other'. Для каждого языка эти значения свои. Напишите модульные тесты, которые прогоняют все граничные числа и проверяют, что возвращается правильная форма. Используйте таблицу CLDR для автоматической генерации тестовых наборов.
Типичные ошибки и их решение
- Использование только one и many без учёта few для славянских языков — баги для чисел 2, 3, 4.
- Неправильный порядок аргументов в Android — число не подставляется, если передан только один параметр.
- Игнорирование дробных чисел (например, 1.5) — без CLDR форма 'other' не применяется.
- Самописная if-логика на операторе % — при добавлении нового языка придётся переписывать условия.
Наше решение полностью исключает эти ошибки за счёт строгого следования CLDR и автоматизированного тестирования на граничных значениях.
Что входит в работу
- Аудит текущей кодовой базы и переводов.
- Разработка схемы плюрализации для всех целевых языков.
- Внедрение нативных решений (
plurals.xml, stringsdict, ARB, i18next).
- Написание тестов для граничных случаев (0, 1, 2, 5, 21, 1.5).
- Документация и обучение команды.
Наша команда имеет пятилетний опыт в мобильной разработке и реализовала 30+ проектов с локализацией. Мы гарантируем корректную работу plural forms на всех устройствах. Стоимость внедрения окупается в первые месяцы после публикации за счёт снижения баг-репортов.
Сроки: от четырёх часов (аудит + замена хардкода) до двух дней (если плюрализация в нескольких языках и кастомные компоненты). Свяжитесь с нами для аудита вашего проекта — мы предложим оптимальное решение. Закажите консультацию уже сегодня, чтобы избежать багов локализации.
Локализация мобильных приложений: i18n, RTL, динамическое переключение языка
При локализации приложений мы часто сталкиваемся с неочевидными ошибками: дата 04/05 для американца — это 5 апреля, для европейца — 4 мая. Сумма «1,000.50» в большинстве стран — тысяча с половиной, в Германии — один и пятьдесят сантов. Число слов для «1 файл», «3 файла», «5 файлов» в русском — три разные формы; арабский имеет шесть форм множественного числа. Если архитектура приложения не учитывает это с самого начала, локализация превращается в серию патчей. Наш 7-летний опыт в мобильной разработке и 50+ релизов в App Store и Google Play подтверждают: правильный i18n/l10n с первого коммита окупается многократно.
Как настроить базовую инфраструктуру локализации?
iOS: от Localizable.strings к String Catalogs
Исторически строки в iOS хранились в Localizable.strings — простой key=value формат. С Xcode 15 появились String Catalogs (.xcstrings) — JSON-based формат, который хранит все локали в одном файле, отображает статус перевода (переведено/устарело/отсутствует) и интегрирован с Xcode UI.
String(localized: "welcome_title") в Swift 5.7+ заменяет NSLocalizedString(...). Короче, типобезопаснее. String Interpolation в локализованных строках: String(localized: "items_count \(count)") с правилом плюрализации в .xcstrings — система автоматически выберет нужную форму для языка. Плюрализация через .stringsdict (старый подход) или прямо в String Catalog с NSStringPluralRuleType. Для русского нужно определить формы one (1 файл), few (3 файла), many (5 файлов), other (fallback). Пропустить few для русского — значит получить «5 файлов» там где должно быть «3 файлы». String Catalogs сокращают время настройки в два раза по сравнению со .stringsdict.
Android: XML resources и строковые форматы
res/values/strings.xml для базовой локали (en), res/values-ru/strings.xml для русского. Plural strings через <plurals> с <item quantity="one">, <item quantity="few">, <item quantity="many">. resources.getQuantityString(R.plurals.file_count, count, count) — первый count выбирает форму, второй подставляется в строку.
В Compose: stringResource(R.string.key) и pluralStringResource(R.plurals.file_count, count, count). Типобезопасная альтернатива — библиотека Lyricist, которая генерирует типизированные строки из аннотаций.
Android App Bundle с android:splitByLocale="true" в bundle.gradle — ресурсы доставляются только для языков устройства. APK уменьшается на 15-20%, ресурсы нужных локалей догружаются через Play Asset Delivery. Важно: на Android 8+ Configuration.locales — список, не один язык.
Flutter: intl и слои абстракции
Flutter intl пакет — стандарт. AppLocalizations.of(context).welcomeTitle генерируется из ARB-файлов (app_en.arb, app_ru.arb). flutter gen-l10n генерирует типизированный код. Pluralization через {count, plural, one{# файл} few{# файла} many{# файлов} other{# файла}} в ARB.
Для больших приложений с 50+ языками — easy_localization с поддержкой YAML/JSON/CSV форматов и lazy loading переводов: не все 50 языков грузятся сразу, только нужный. Это снижает размер начальной загрузки на 30%.
Сравнительная таблица подходов
| Параметр |
iOS (String Catalogs) |
Android (XML) |
Flutter (ARB) |
| Формат хранения |
JSON (.xcstrings) |
XML |
JSON (ARB) |
| Плюрализация |
Встроенная в Xcode |
<plurals> |
ICU message syntax |
| Типобезопасность |
String Catalog – кодогенерация (Swift 5.9) |
R.java / ViewBinding |
gen-l10n |
| Статус переводов |
Визуальный в Xcode |
Только сторонние инструменты |
Только сторонние инструменты |
| RTL из коробки |
Auto Layout (leading/trailing) |
supportsRtl + start/end |
Directionality Widget |
Как реализовать RTL-поддержку без переписывания UI?
Арабский, иврит, персидский, урду — языки RTL (Right-to-Left). Это меняет не только направление текста, но и всю раскладку UI: back button справа, иконки зеркально, паддинги и марджины инвертируются.
На iOS всё делается через semanticContentAttribute и Auto Layout. Layout constraint-ы с leading/trailing (не left/right) автоматически инвертируются при RTL. UIView.semanticContentAttribute = .forceRightToLeft для конкретного компонента. Системные компоненты (UINavigationController, UITableView, UIStackView) переключаются автоматически при RTL-локали. Проблемы возникают с кастомными UI, где разработчик жёстко использовал left/right констрейнты или frame-based layout. Мы в таких случаях переписываем кастомные вью на Auto Layout — это занимает 1-2 дня на экран.
На Android android:supportsRtl="true" в AndroidManifest включает RTL-поддержку. start/end вместо left/right в XML-атрибутах: paddingStart, layout_marginEnd, textAlignment="viewStart". LayoutInflater с android:layoutDirection="rtl" для превью. Иконки с направленностью (стрелки, шеврон) нужно зеркалировать — android:autoMirrored="true" в drawable для автоматического инвертирования при RTL.
На Flutter Directionality виджет с TextDirection.rtl управляет направлением для поддерева. Padding(EdgeInsetsDirectional.fromSTEB(...)) вместо EdgeInsets.only(left:...). Row автоматически учитывает TextDirection из Directionality. Большинство Material виджетов RTL-ready, но кастомные CustomPainter — нет: нужно получать TextDirection из context и учитывать вручную.
Тестирование RTL: на iOS Settings → General → Language & Region → Region: Saudi Arabia переключает в RTL режим без смены языка системы. На Android adb shell setprop debug.force.rtl 1 форсирует RTL для отладки. Это позволяет обнаружить до 80% проблем RTL до релиза.
Почему динамическое переключение языка — узкое место?
Переключение языка без перезапуска приложения — нетривиальная задача, особенно если система построена на системной локали. Мы выделили три основных подхода.
iOS не поддерживает смену языка приложения без перезапуска нативно. Самый чистый подход — хранить выбранный язык в UserDefaults, при запуске создавать Bundle с нужной локализацией, использовать кастомный NSLocalizedString через этот Bundle. Bundle.setLanguage("ru") через swizzling Bundle.localizedString(forKey:value:table:) — работает, но это runtime swizzling, что не идеально. Альтернатива: собственная система строк поверх NSBundle, которая перечитывает файлы при смене языка. При переключении — пересоздать корневой ViewController.
Android с API 33: LocaleManager.setApplicationLocales() — официальный API для смены языка приложения без перезапуска системы, без рекреации Activity если использовать AppCompatDelegate.setApplicationLocales(). До API 33 — Configuration.setLocale() + recreate() для Activity. При смене языка нужно уведомить все открытые Activity через broadcast или ViewModel. Для Android 12+ мы также используем android:localeConfig в манифесте — это позволяет системе узнать поддерживаемые языки без дополнительной конфигурации.
Flutter — самый простой из трёх. LocalizationsDelegate перезагружается при изменении locale в MaterialApp. Храним выбранный язык в провайдере (Riverpod/Provider/Bloc), изменение locale в MaterialApp перестраивает дерево с новыми строками. Практически без бойлерплейта при использовании easy_localization. Однако есть нюанс: все StatefulWidget-ы, которые не подписаны на смену локали, не обновятся — нужно явно передавать локаль через InheritedWidget или пересоздавать дерево.
Форматирование дат, чисел, валют
DateFormatter (iOS) и DateFormat (Android, intl) — всегда с явным locale, никогда без него. DateFormatter().dateStyle = .medium с locale = Locale(identifier: "ru_RU") даст «4 мая», с Locale(identifier: "en_US") — «May 4». Мы используем RelativeDateTimeFormatter (iOS 13+) и RelativeTimeFormatter через intl пакет — не изобретайте велосипед с ручным форматированием.
NumberFormatter / NumberFormat.currency() для валют. Символ валюты, разделители тысяч и дробной части — всё locale-специфично. Хардкодить «₽» или «.» как разделитель — ошибка. Locale(identifier: "ru_RU") + NumberFormatter.numberStyle = .currency с currencyCode = "RUB" даст правильное форматирование автоматически.
Типичные ошибки при локализации
Конкатенация строк вместо форматирования: "Hello, " + name + "!" работает для SVO-языков, но в японском имя идёт перед обращением. String(format: "greeting %@", name) с greeting = "%@ さん、こんにちは" в японском файле — правильно. Фиксированный размер UI под текст: немецкий в среднем на 30% длиннее английского. AutoLayout с правильными констрейнтами, adjustsFontSizeToFitWidth там где допустимо, динамическое изменение высоты ячеек через UITableView.automaticDimension. Изображения с embedded текстом требуют локализованных версий или замены на text overlay.
Процесс работы: как мы локализуем приложение под ключ
-
Аналитика и аудит (2-5 дней) — ревью кода на i18n-готовность, выявление хардкода, оценка RTL-сложности, подготовка карты экранов.
-
Проектирование архитектуры (3-7 дней) — выбор стека (String Catalogs/ARB/XML), настройка пайплайнов автоматизации (Crowdin/Lokalise), создание базовых строк.
-
Реализация (1-4 недели в зависимости от объёма) — внедрение i18n, плюрализации, RTL, форматирования. Параллельно — перевод контента.
-
Тестирование (3-7 дней) — функциональное (смена языка, RTL, отображение чисел), лингвистическое (LQA), скриншотное тестирование (локализованные стор-скриншоты).
-
Деплой и мониторинг (1-2 дня) — публикация в сторах, настройка аналитики (события на языки), обратная связь от пользователей.
Что входит в deliverables
- Исходный код с полной i18n-инфраструктурой
- Документация по добавлению нового языка (playbook)
- Файлы переводов (ARB/XML/xcstrings) + глоссарий
- Автоматизация: CI/CD интеграция с сервисами переводов
- Доступ к репозиторию с полной историей изменений
- Обучение команды заказчика (1-2 сессии)
- Гарантия на код — 6 месяцев, бесплатные фиксы багов локализации
Сроки и стоимость
| Этап |
Срок (ориентировочно) |
Что входит |
| Добавление одного нового языка (без RTL) |
2-3 дня технической работы + время перевода |
Настройка строк, тестирование, стор-скриншоты |
| Первичная локализация с нуля (10-15 экранов, без RTL) |
2-3 недели |
Архитектура, перевод, тестирование |
| Проект с RTL-поддержкой и динамическим переключением |
4-6 недель |
Всё включено + адаптация UI, пересборка кастомных компонентов |
Стоимость рассчитывается индивидуально на основе объёма кода, количества языков и сложности RTL. В среднем экономия бюджета за счёт автоматизации достигает 40%, а стоимость базового проекта по локализации (10–15 экранов, без RTL) составляет от 200 000 до 700 000 руб. Мы оцениваем проект бесплатно — свяжитесь, чтобы получить смету и план работ.
Почему стоит выбрать нас
Мы сертифицированные разработчики (Apple WWDC Scholarship, Google Associate Android Developer). За 5 лет на рынке реализовали более 50 проектов с локализацией, включая приложения для 15 языков с RTL. Гарантируем соблюдение App Store Review Guidelines (Section 4.2/5.1) и Google Play Policies (User Data). Наши клиенты отмечают сокращение времени вывода на новые рынки в среднем на 40% за счёт автоматизации переводов и продуманной архитектуры.
Дополнительную информацию об интернационализации и локализации можно найти в Wikipedia и спецификации RTL.
Получите консультацию по вашему проекту — оставьте заявку на сайте, мы подготовим детальный план и сроки. Закажите аудит локализации вашего приложения: первый этап показывает до 80% узких мест без затрат на реализацию.