Три слова часто путают, хотя они описывают разные этапы работы. Internationalization, сокращённо i18n, - это подготовка программы к разным языкам и рынкам: код не зашивает в себя язык, формат чисел и направление письма, а выносит их в точки расширения. Localization, l10n, - это создание одной конкретной локальной версии: русской, немецкой, арабской. Translation, перевод текста, - лишь одна часть локализации, и далеко не самая сложная. Если архитектура не подготовлена, никакой перевод её не спасёт: строки склеятся неправильно, числа отформатируются не так, а даты сдвинутся на сутки.
Возьмём страницу товара в мультиязычном магазине. Её одновременно видят покупатели из разных стран, поисковые роботы и разные локали. На странице есть значок корзины со счётчиком, цена, срок доставки и список характеристик. Наивный подход выглядит естественно: сначала делаем английскую версию, а перевод добавим потом, просто подменив строки. Пока язык один, всё работает, и соблазн отложить i18n на будущее велик.
Ломается это на первой же живой фразе. Счётчик корзины программист собирает из кусков: число, пробел, слово. Цену - из знака валюты и суммы. Код читается легко и кажется безобидным.
Проблема в том, что порядок слов, падежи и согласование в языках разные. По-английски 3 items, по-русски 3 товара, а один товар - уже другая форма. Знак валюты в одних локалях стоит перед суммой, в других после; разделители групп и дробной части тоже отличаются. Переводчик, которому достался обрывок items, не видит ни числа, ни контекста и не может выбрать верную форму. Склейка предложений из фрагментов - это ошибка проектирования, а не мелкая недоработка.
Профессиональное решение переворачивает поток. Текст - это не литерал в коде, а запись в каталоге сообщений, к которой обращаются по ключу. Число, цена и дата не приклеиваются к строке руками, а подставляются как именованные параметры, форматированием которых занимается платформа. Переводчик получает целое сообщение с плейсхолдерами и видит, что куда встанет.
Отдельная забота - множественное число, потому что языки делят количества по-разному. В английском форм две, в русском четыре, в арабском шесть. Формат сообщений ICU MessageFormat описывает это явно: одно сообщение перечисляет все формы, а движок по правилам локали (данные берутся из Unicode CLDR - общего репозитория локальных данных) выбирает нужную. Символ # подставляет само число.
{count, plural,
=0 {Нет файлов}
one {# файл}
few {# файла}
many {# файлов}
other {# файла}
}Тем же механизмом решаются род и выбор варианта: конструкция select подставляет разные формулировки в зависимости от пола пользователя или типа сущности. За гибкость приходится платить. Литералы в коде превращаются в ключи, появляется каталог переводов, инструменты извлечения строк и память переводов, а разработчик обязан продумывать параметры сообщения заранее. Для одноязычного прототипа это перебор.
Оправдано это, как только у продукта появляется второй язык или живой план на него - переделывать склейки задним числом дороже, чем заложить каркас сразу. Цена пропуска видна на том же магазине: во время акции интерфейс показывает 1 товаров в корзине и Доставка через 1 дней, русский покупатель читает это как небрежность, доверие к оформлению заказа падает, а конверсия вместе с ним. Один невыбранный плюрал стоит реальных денег.
const text = count + ' files';
const price = '$' + value.toFixed(2);const text = t('files', { count });
const price = new Intl.NumberFormat(locale, {
style: 'currency',
currency
}).format(value);