Клиент выбрал русский язык, но получает шаблон WhatsApp на английском. Другому пользователю приходит испанская версия, хотя его профиль и номер не менялись. Иногда ошибка возникает только в напоминаниях из очереди или после переноса интеграции между аккаунтами. Обычно WhatsApp не переводит такой шаблон самостоятельно: интеграция отправляет конкретное имя шаблона и конкретный language code, а значит искать причину нужно в сформированном запросе и в данных, из которых выбран язык.
Правильная диагностика начинается с одного ошибочного сообщения. Нужны время отправки, получатель, внутренний идентификатор задания, message id ответа API, имя шаблона и фактический JSON-запрос без токена доступа. Настройки языка в CRM или профиле сами по себе ничего не доказывают: к моменту вызова WhatsApp они могли быть преобразованы, заменены значением по умолчанию или взяты из старого задания очереди.
Как устроен язык шаблона WhatsApp
Локализованная версия шаблона определяется сочетанием имени и кода языка. Для отправки приложение передает объект template, внутри которого указывает name и language.code. Код должен соответствовать версии, созданной и одобренной в нужном WhatsApp Business Account.
{ "messaging_product": "whatsapp", "to": "<recipient>", "type": "template", "template": { "name": "order_ready", "language": {"code": "ru"}, "components": [] } }Коды языков нельзя формировать догадкой. Некоторые локали различаются по региону, а некоторые используют общий код. Используйте список локалей, реально созданных для шаблона, и храните явное соответствие между языком клиента и разрешенным language.code.
Почему отправляется неправильная языковая версия
В запрос попадает язык по умолчанию
Если locale клиента пустой, неизвестный или записан в неожиданном формате, код часто делает fallback на английский. Ошибка появляется и тогда, когда значение ru-RU сравнивается с ru, а нормализация не предусмотрена. Проверьте не только итоговый fallback, но и причину, по которой исходная локаль не прошла сопоставление.
Язык берется не из того источника
В системе могут одновременно существовать язык профиля, язык последнего диалога, язык оператора, locale браузера и язык заказа. Если приоритет не задан явно, разные обработчики выбирают разные значения. Для сервисного сообщения обычно нужен язык конкретного получателя или диалога, зафиксированный на момент создания уведомления.
Очередь хранит старый или неполный payload
Пользователь изменил язык после постановки задания, но worker отправил сохраненную старую локаль. Возможен и обратный сценарий: задание хранит только user id, а worker заново читает текущий язык, хотя текст заказа должен был остаться на языке исходной операции. Нужно заранее решить, язык фиксируется при создании события или вычисляется непосредственно перед отправкой.
Кеш локали общий для нескольких пользователей
Ключ кеша вроде template:order_ready без user id, tenant или locale может сохранить первую найденную версию и раздать ее всем последующим запросам. Похожая ошибка возникает при использовании изменяемой глобальной переменной в долгоживущем worker-процессе.
Запрос уходит в другой WABA или окружение
В test и production могут существовать шаблоны с одинаковым именем, но разным набором переводов. После замены phone number id, access token или Business Account интеграция продолжает считать, что работает со старым каталогом шаблонов. Всегда сопоставляйте phone number id, WABA, имя и язык в одном окружении.
Ветка retry формирует запрос иначе
Первичная отправка может использовать корректный language.code, а повторная — собирать payload заново с дефолтной локалью. Поэтому ошибку иногда замечают только у сообщений, отправленных после временного сбоя или rate limit. Основной и повторный путь должны использовать один и тот же валидированный контракт задания.
Пошаговая диагностика неправильного языка
- Выберите одно ошибочное сообщение и найдите внутреннее задание по получателю и времени отправки.
- Получите точный исходящий payload после всех преобразований. Секретный токен и персональные данные замаскируйте.
- Сверьте template.name и template.language.code с версиями шаблона в том WABA, к которому относится используемый phone number id.
- Проследите происхождение locale: профиль, диалог, заказ, CRM, сегмент рассылки или значение по умолчанию.
- Проверьте нормализацию: регистр, дефис и подчеркивание, общий язык и региональная локаль.
- Посмотрите содержимое задания очереди и сравните первичную попытку с retry.
- Отключите кеш на тестовом сценарии или добавьте locale в ключ и повторите отправку на тестовый номер.
- Проверьте логи деплоя: версия приложения, конфигурация окружения, phone number id и время последней синхронизации шаблонов.
Логируйте язык в нескольких точках: source_locale до нормализации, resolved_language_code после сопоставления и фактически отправленный template language. Тогда будет видно, где именно значение изменилось.
Как построить надежное сопоставление локалей
Не передавайте пользовательскую строку locale напрямую в Cloud API. Создайте разрешенную таблицу соответствий для каждого шаблона. Она должна учитывать только реально одобренные варианты и явный fallback.
template_locales = { order_ready: { ru: 'ru', 'en-US': 'en_US', 'en-GB': 'en_GB' } } source_locale -> normalize -> allowlist lookup -> explicit fallback- нормализуйте входные значения в одном модуле, а не отдельно в каждом worker;
- не заменяйте неизвестную локаль молча — записывайте метрику fallback_reason;
- храните доступные языки на уровне конкретного шаблона, потому что их наборы различаются;
- проверяйте наличие имени и языка до постановки задания в очередь;
- не позволяйте оператору передавать произвольный language.code через форму или CSV.
Как определить правильный источник языка
Для каждого типа сообщения сформулируйте правило. Подтверждение заказа может использовать язык заказа, уведомление поддержки — язык активного диалога, а системное предупреждение — язык профиля. Важен не универсальный источник, а предсказуемый приоритет.
- Если событие уже содержит зафиксированную locale, используйте ее.
- Иначе возьмите язык активного диалога, если он подтвержден сообщениями пользователя.
- Затем используйте сохраненный язык профиля.
- При отсутствии данных примените документированный язык по умолчанию для проекта.
- Если шаблон не имеет нужного перевода, выберите согласованный fallback и зафиксируйте причину.
Не определяйте язык только по телефонному коду. Номер может использоваться в другой стране, а один регион — быть многоязычным. Такая эвристика допустима лишь как последний fallback, если бизнес осознанно принимает ее ограничения.
Очередь, retry и идемпотентность
Задание на отправку должно содержать либо уже разрешенные template name и language code, либо версию правила, по которой их можно воспроизводимо получить. Если worker перечитывает изменяемые настройки без версии, повтор через час способен отправить другой текст.
- сохраняйте notification id, recipient, template name, language code, параметры и версию конфигурации;
- используйте idempotency key, чтобы retry не отправлял два сообщения на разных языках;
- не меняйте payload при повторной попытке, кроме технических данных доставки;
- отделяйте ошибки шаблона от временных сетевых ошибок и rate limit;
- помещайте неисправимые задания в DLQ, а не заменяйте язык автоматически до успешной отправки.
Что делать с уже отправленными сообщениями
Не запускайте массовый повтор сразу после исправления. Сначала определите, было ли сообщение понятно получателю и не вызовет ли дубль новое уведомление. Для чеков, кодов доступа и платежных событий повтор может иметь юридические или финансовые последствия.
- Найдите сообщения с неправильным resolved_language_code за период инцидента.
- Разделите их на информационные, транзакционные и требующие действия.
- Для каждой группы согласуйте, нужен ли повтор и какой текст объяснит исправление.
- Создайте новые задания с отдельным idempotency key и ссылкой на исходное сообщение.
- Отправляйте небольшими партиями, контролируя качество, ограничения и жалобы.
Как проверить исправление
- каждый поддерживаемый язык профиля дает ожидаемый template language code;
- неизвестная и пустая locale приводят к документированному fallback с записью причины;
- одинаковый шаблон корректно отправляется из test и production WABA;
- изменение языка до постановки задания и после нее соответствует выбранному правилу;
- retry использует тот же язык, что и первая попытка;
- два параллельных задания разных пользователей не обмениваются локалью через кеш;
- региональные варианты, например разные версии английского, выбираются явно;
- в журнале можно связать внутреннее задание, API payload, message id и статус доставки.
Типичные ошибки при исправлении
- поменять язык по умолчанию и не найти, почему потерялась исходная locale;
- формировать language.code простой заменой дефиса на подчеркивание;
- считать, что WhatsApp автоматически выберет перевод по языку телефона;
- кешировать шаблон только по имени без языка и окружения;
- логировать только настройки пользователя, но не фактический API payload;
- собирать retry другим кодом, чем первичную отправку;
- повторно отправлять все ошибочные сообщения без оценки последствий.
Как предотвратить повторение проблемы
Добавьте автоматическую сверку каталога шаблонов с разрешенной таблицей локалей при деплое или по расписанию. Если перевод удален, переименован или еще не одобрен, система должна сообщить об этом до массовой рассылки. Отдельные метрики нужны для source locale, resolved language, fallback и ошибок по сочетанию template name + language code.
Перед запуском новой локализации прогоняйте тестовую матрицу на контролируемых номерах. Проверяйте не только успешный ответ API, но и фактический текст на устройстве, параметры, кнопки и статусы доставки.
Когда нужна помощь с WhatsApp Business API
Если шаблоны WhatsApp уходят на неправильном языке, я могу проследить locale от CRM до Cloud API, исправить mapping, кеш и очередь, синхронизировать шаблоны между окружениями и добавить тесты локализации. Для оценки пришлите обезличенный исходящий payload, имя шаблона, ожидаемый и фактический язык, phone number id и фрагмент логов без access token.