Как мы перестали страдать с мультиязычностью в Payload CMS: грабли, фолбэки и контроль доступа
В 2022 году мы переносили корпоративный портал с четырьмя языковыми версиями со старого монолита на Payload CMS. Заказчик поставил простую задачу: контент на английском, немецком, испанском и французском, при этом локальные редакторы не должны видеть черновики друг друга. Мы пожали плечами, выставили localized: true почти на все поля в конфиге и думали, что уложимся за пару недель.
Через месяц база данных начала выдавать странные аномалии, а немецкий переводчик случайно стер неопубликованный релиз для глобального рынка. Пришлось на ходу переписывать архитектуру данных. Этот опыт стоил нам кучи нервов и 40 часов чистого рефакторинга в выходные.
Поле против документа: фундаментальная развилка
Когда вы проектируете мультиязычный контент в CMS, вам нужно выбрать один из двух путей: хранить языковые копии как отдельные документы или локализовать конкретные поля внутри одной записи. Большинство разработчиков без раздумий выбирают локализацию полей. В Payload это делается буквально одной строчкой в схеме поля. В этом и кроется ловушка.
Если структура страницы абсолютно идентична на всех языках — field-level localization работает идеально. Но как только маркетологам требуется убрать блок с отзывами для японской аудитории или добавить юридический дисклеймер строго для Германии, локализация полей превращается в монстра. Приходится городить костыли с флагами видимости внутри каждого элемента массива.
Наш практический вывод: для плотных контентных страниц со сложным гибким лейаутом (Page Builder на блоках) часто надежнее делать отдельные документы под каждую локаль со связью через общее поле-группиратор. Для каталогов товаров, профилей и типовых статей — строго field-level localization.
Коварство фолбэков в Rich Text и связях
Грамотная локализация headless CMS всегда упирается в логику fallback-значений. Payload умеет отдавать дефолтный язык (например, английский), если запрошенный перевод отсутствует. На бумаге выглядит отлично. На практике фронтенд часто получает франкенштейна.
Представьте статью, где заголовок уже перевели на испанский, а тело статьи в Lexical/Slate еще нет. Если Payload настроен с наивным fallbackLocale: 'en', REST или GraphQL API вернет испанский заголовок и английский текст. Для читателя это выглядит как баг интерфейса. Еще хуже ситуация со связями (Relationships): если вы случайно локализуете поле связи с автором, вам придется дублировать эту привязку руками во всех языковых версиях одной и той же статьи.
Мы решили эту проблему жестким правилом: никогда не локализовать структурные связи и массивы, если их элементы не требуют уникального ID под конкретную страну. А для богатого текста пишем хуки afterRead, которые проверяют полноту перевода и отдают либо полностью локализованный объект, либо флаг неполной локализации, чтобы фронтенд мог корректно среагировать.
Роли доступа: спасаем продакшен от языковых команд
Когда с CMS работает больше двух редакторов, стандартных прав «админ / пользователь» становится недостаточно. Типичный сценарий: внештатный переводчик заходит в панель управления, чтобы перевести готовый текст на польский. Если система прав настроена небрежно, он может случайно нажать кнопку публикации и выкатить в прод сырой английский оригинал, который пока лежал в черновиках.
Грамотно выстроенные роли доступа в Payload CMS завязаны на контекст запроса. Payload дает полный контроль над операциями через функции в свойстве access. Мы проверяем три вещи одновременно: роль пользователя, статус документа (draft / published) и текущую рабочую локаль.
Логика простая, но железобетонная:
Переводчик имеет право на update только в рамках назначенной ему локали. Публикация всего документа блокируется до тех пор, пока ответственный редактор мастер-версии не снимет флаг черновика. А анонимные пользователи через публичный API получают доступ только к тем языковым версиям, где статус строго равен published, без утечки черновых переводов через фолбэки.
Что мы вынесли в сухой остаток
Качественная настройка Payload CMS под международные проекты требует дисциплины в схемах:
Во-первых, разделяйте контентные поля и системную метаинформацию. Слаги, даты создания, внутренние идентификаторы должны оставаться общими. Локализуйте только то, что реально читает человек.
Во-вторых, тестируйте API-ответы при частичном заполнении полей. Всегда проверяйте, что именно вернет API клиенту, если в базе заполнено 30% перевода.
В-третьих, ограничивайте права на уровне полей, а не только коллекций. Это убережет от человеческого фактора, когда международная команда разрастается до десятков человек.
Если вы проектируете сложную платформу или хотите навести порядок в текущей архитектуре без переписывания бэкенда с нуля, посмотрите на наше решение: Локализация и роли доступа в headless CMS (Payload). Мы в GuardLabs проектируем и настраиваем такие системы сразу с прицелом на продакшен, чтобы база не разваливалась от фолбэков, а редакторы работали строго в рамках своих прав.