Как поддержать мультиязычность в приложении с микросервисной архитектурой и разными базами данных
Что проверяют
Разделяете ли вы три разные задачи: перевод интерфейса, переводимые данные в базах и форматирование дат, чисел и валют. Следом обычно спрашивают, как язык пользователя доходит до сервиса, вызванного третьим по цепочке или через очередь.
Как отвечать
Каркас по слоям:
- передача локали: язык определяется один раз на входе, в gateway, по профилю или
Accept-Language, и дальше едет заголовком во всех синхронных вызовах и в заголовках сообщений Kafka или RabbitMQ. Внутри Python-сервиса его удобно держать вcontextvars.ContextVar; - строки интерфейса и ошибок: каталоги gettext
.poи.mo, в Djangogettext_lazyсmakemessagesиcompilemessages, во FastAPI черезBabel. Ещё надёжнее, когда бэкенд отдаёт стабильный код ошибки, а текст подставляет фронтенд; - данные в базе: каждый сервис переводит свои сущности сам, потому что и база у него своя. В PostgreSQL это JSONB-поле вида
{"ru": ..., "en": ...}или таблицаproduct_translations(product_id, locale, name)с уникальностью по паре. Таблица лучше, когда языков много и нужны индексы по конкретному языку; - поиск и сортировка: полнотекстовый поиск с конфигурацией языка, например
to_tsvector('russian', ...), и сортировка по ICU-колляции; - форматирование: даты хранятся в UTC и приводятся к локальному виду на краю,
Babelдаётformat_dateиformat_currency, а русские формы множественного числа обеспечиваетngettext.
Нужна цепочка запасных языков, например pt-BR, потом pt, потом язык по умолчанию, чтобы пропущенный перевод не ронял ответ. Кэши учитывают язык: локаль входит в ключ Redis, а ответ API уходит с Vary: Accept-Language.
Где спотыкаются
Предлагают общий сервис переводов с общей базой, к которой все делают join. Это ломает изоляцию данных и создаёт общую точку отказа. Централизовать стоит процесс перевода, а не хранение.
Кладут в событие уже переведённый текст. Потребитель получает строку на языке отправителя, а правильнее передать ключ с параметрами и переводить при показе.
Забывают про кодировку в MySQL: utf8 там исторически трёхбайтовый (utf8mb3) и не хранит эмодзи и часть символов, нужен utf8mb4.