Интернационализация PHP-приложения — это не только перевод кнопок и сообщений. Приложение, подготовленное для нескольких языков или рынков, должно разделять язык, региональные форматы, часовой пояс, валюту, контент и бизнес-политики. Если эти слои смешаны, каждое расширение может превратиться в функциональное ответвление, которое трудно тестировать и поддерживать.
Что меняется при работе с несколькими языками и рынками

Язык определяет, как выражается текст. Региональная настройка, или locale, задаёт соглашения представления, такие как десятичный разделитель, порядок дат или группировка разрядов. Locale может включать регион, например es-ES или fr-CA, но этот регион не должен подменять рынок, юридическое лицо, резидентство, коммерческую политику или страну ведения деятельности.
Также следует различать контексты, которые часто передаются вместе, но означают разное:
- Часовой пояс: интерпретирует расписания, сроки, календари и операционные закрытия.
- Валюта: определяет сумму транзакции или прайс-листа; она не выводится из языка.
- Организация или юридическое лицо: может определять налоги, разрешения, выставление счетов или хранение данных.
- Рынок: может влиять на каталог, логистику, способы оплаты или доступные каналы.
- Пользовательские предпочтения: выбранные язык, locale и часовой пояс, которые могут отличаться от корпоративной конфигурации.
Человек может использовать интерфейс на английском языке, работать в европейском часовом поясе и управлять организацией, которая выставляет счета в другой валюте. Сведение этой реальности к единственной переменной locale создаёт неявные решения.
Дорогостоящая ошибка: превращать язык в бизнес-правило
Плохой признак появляется, когда код принимает доменные решения на основе языка интерфейса: if ($locale === 'es'). Это условие может начаться с отображения другой метки и закончиться применением налогов, скрытием способа оплаты или изменением согласования.
Правильный вопрос: «какие данные или политика объясняют это различие?». Если оно зависит от юридического лица, необходимо обратиться к этому лицу. Если оно определяется коммерческой политикой, должна существовать идентифицируемая и версионируемая политика. Если оно влияет только на представление, ему место на границе ввода или вывода.
Язык переводит пользовательский опыт; он сам по себе не авторизует, не рассчитывает и не определяет поведение домена.
Что должно оставаться в домене
Домен должен работать со стабильными понятиями и каноническими значениями. Заказу нужны количества, суммы, позиции, статусы и правила расчёта; ему не нужно знать, будет ли сумма отображаться как 1,234.50 или 1.234,50. Политика соответствия условиям должна получать явные атрибуты, а не считывать пользовательское представление.
- Домен: инварианты, состояния, расчёты, бизнес-авторизации, политики и события.
- Приложение: сценарии использования, загрузка контекста, координация и выбор политик.
- Входные адаптеры: формы, заголовки, API или файлы; валидация и нормализация.
- Выходные адаптеры: перевод, сериализация и форматирование дат, сумм и единиц измерения.
В PHP не допускайте, чтобы сущности и центральные сервисы напрямую обращались к сессии, HTTP-заголовкам, переменным окружения или глобальному locale процесса. Такие зависимости приводят к тому, что один и тот же сценарий использования ведёт себя по-разному в зависимости от канала или времени выполнения.
Проектирование явного и ограниченного контекста
ExecutionContext может включать идентификатор организации, актора, locale представления и предпочитаемый часовой пояс. У каждого поля должна быть ясная семантика. Однако валюта операции должна быть частью суммы или применённой ценовой политики, а не глобальным изменяемым предпочтением.
Разрешайте контекст на границе каждого канала. Веб-запрос может использовать сохранённое предпочтение или контролируемое согласование; API должен получать явные и документированные поля; процесс в очереди должен сохранять требуемые идентификаторы при создании. Асинхронная задача не должна предполагать, что унаследует сессию, пользователя или часовой пояс.
Переводимый контент и операционные данные
Тексты интерфейса, шаблоны коммуникаций и редакционный контент имеют жизненный цикл, отличный от операционных данных. Используйте стабильные и семантические ключи, например billing.invoice.overdue, вместо исходного текста. Так можно изменить формулировку, не нарушая код, тесты или интеграции.
Управляемому контенту нужна публикация: перевод может существовать в черновике, быть утверждённым или опубликованным. Определите fallback: запрошенный язык, базовый язык организации и, при необходимости, видимое и контролируемое отсутствие. Альтернативный перевод может быть приемлем для внутренней заметки, но не обязательно для договорной коммуникации.
Не дублируйте полную операционную запись для каждого языка, если только данные не локализованы. У продукта могут быть переводимые название и описание, тогда как идентификатор, вес, статус и правила доступности остаются общими. Если существует реальное коммерческое различие, моделируйте его как вариант или политику, а не как перевод.
Даты, суммы, единицы измерения и округления
Храните моменты времени однозначно и сохраняйте часовой пояс, когда значение является локальным. «Встреча начинается в 09:00» требует знать пояс, в котором она была определена; «событие произошло в это время» требует абсолютного момента. Сезонные переходы создают несуществующие или повторяющиеся часы, поэтому ввод должен валидироваться, а принятое решение по разрешению неоднозначности должно фиксироваться, когда это уместно.
Для денег храните целое количество в младших единицах вместе с кодом валюты, но не предполагайте два десятичных знака. Масштаб или показатель определяется метаданными валюты, применимыми к операции. Некоторые валюты используют масштаб, отличный от двух, а исторические требования или требования платёжной сети могут потребовать сохранения фактического масштаба или версии использованного правила.
final class Money {
public function __construct(
public readonly int $minorUnits,
public readonly string $currency,
public readonly int $scale
) {}
}
Масштаб позволяет правильно интерпретировать младшие единицы, но не заменяет политику округления. Определите момент округления, применяемый режим и правило наличных расчётов, если оно существует, поскольку кассовое округление может отличаться от бухгалтерского. Избегайте float, локализованных форматов во время расчётов и неявных преобразований.
Тот же подход применим к измерениям: сохраняйте исходную единицу, когда она имеет операционное значение, нормализуйте, когда этого требует расчёт, и конвертируйте только при вводе или представлении. Форма должна указывать допустимые единицу и формат; она не должна угадывать, означает ли 1,500 полтора или тысячу пятьсот.
Архитектура потоков ввода и вывода
Разделение слоёв должно быть видно в полном и повторяемом потоке:
- Захватить контекст и входные данные: получить организацию, актора, канал, locale, часовой пояс и полученное значение.
- Проверить допустимый формат: проверить обязательные поля, синтаксис, единицу, валюту, часовой пояс и ограничения канала.
- Нормализовать в канонические значения: преобразовать локализованный текст в однозначные суммы, даты, единицы и идентификаторы.
- Выполнить доменный сценарий использования: применить явные правила и политики к каноническим значениям.
- Перевести и отформатировать вывод: выбрать опубликованные сообщения и представить значения для получателя или контракта API.
API может решить принимать только канонические форматы, такие как даты с явно указанным поясом и структурированные суммы. Форма для человека может принимать локализованные форматы, если её парсер является явным. В обоих случаях домен получает одно и то же стабильное представление.
Настраиваемая политика или отдельное бизнес-правило
Вариация обычно настраиваема, если она использует общий процесс и меняет декларируемые параметры, например лимит, список праздничных дней или определённый конфигурацией метод расчёта. Такой конфигурации нужны схема, версия, ответственный и тесты.
Различие указывает на отдельное правило, когда оно изменяет инварианты, состояния, ответственность, источники данных или юридические последствия. В таком случае скрытие его в опциях создаёт непрозрачную конфигурацию. Смоделируйте политику через явный интерфейс или выделите отдельный поток, если процесс действительно отличается. Цель не в том, чтобы навязать единую абстракцию, а в том, чтобы не дублировать всё приложение из-за локализованного различия.
Тесты и контрольный список

Тесты должны проверять расчёт и представление. Изменение языка или locale не должно менять итоговую сумму, авторизацию или коммерческую политику, если только это не установлено явным требованием.
- Тестируйте даты при переходах времени, с неоднозначными и несуществующими часами.
- Покрывайте нулевые и отрицательные суммы, разные масштабы, бухгалтерское и наличное округление.
- Проверяйте разрешённый локализованный ввод и отклонение неоднозначных форматов.
- Проверяйте fallback, отсутствие опубликованного контента и интерполированные переменные.
- Запускайте асинхронные задачи без сессии, используя только сохранённый контекст.
- Тестируйте контракты API с каноническими значениями и метаданными формата, когда они необходимы.
Перед открытием нового языка или рынка определите, что действительно меняется, разделите их источники истины, проверьте денежные и временные правила и вводите доступность постепенно, когда операции требуется контролируемая валидация. Эта дисциплина позволяет расширять приложение, не превращая каждый рынок в параллельную версию продукта.



