Один товар может иметь название и описание на нескольких языках, а цену, категорию и изображения — общие для всех переводов. Для этого не нужно создавать отдельную запись товара на каждом языке.
Языки задаются у каждой сущности отдельно. Например, у Product можно включить английский, русский и китайский, а у внутренней OwnerBonus оставить только русский. Язык данных не выдаёт права доступа: кто видит сущность и её записи, по-прежнему определяет Access. Интерфейс генератора и готовой админки остаётся английским.
Настройте языки сущности
- В Content выберите persistent-сущность, например
Product, и откройте Entity settings. - Перейдите во вкладку Languages после Basic settings и включите Content languages.
- В Languages выберите нужные языки. Например,
en— английский,ru— русский,zh-Hans— упрощённый китайский,zh-Hant— традиционный китайский. Можно выбрать и один язык. - В Default language задайте основной язык. Он используется при чтении без параметра
localeи показывается первым в языковых вкладках и селектах админки. - В Localized fields отметьте поля, для которых нужны переводы.
- Выберите обязательность заполнения и поведение при отсутствии перевода по таблице ниже.
- Нажмите Finish, затем выполните Generate. Примените созданную миграцию и пересоберите приложение.
| Настройка | Что она меняет |
|---|---|
| Require all languages выключено | Обязательные переводимые поля нужно заполнить только на основном языке. |
| Require all languages включено | Обязательные переводимые поля нужно заполнить на каждом выбранном языке. Необязательные поля остаются необязательными. Проверка выполняется при сохранении, а не только при публикации. |
| Missing translation → Return empty value | Если нужного перевода нет, API возвращает null. Это исходное поведение: другой язык автоматически не подставляется. |
| Missing translation → Use default language | Вместо отсутствующего значения API возвращает значение этого поля на основном языке сущности. Если и его нет, результат — null. |
Подстановка работает для каждого поля отдельно. Если русское название заполнено, а русское описание отсутствует, при включённой подстановке API сохранит русское название и возьмёт только описание с основного языка. Эта настройка чтения не заменяет обязательность заполнения и не создаёт перевод в БД.
Выберите, какие поля переводить
В примере Product есть поля name, description, price и categoryId:
| Поле | Настройка | Результат |
|---|---|---|
| name: string | Localized, Required | У товара свои названия по языкам; обязательность проверяется по настройке Require all languages. |
| description: text | Localized | Описание можно заполнить отдельно на каждом языке, в том числе через Markdown-редактор. |
| price: int64 | Общее поле | Одна цена для всех языков. |
| categoryId: relation | Общее поле | Одна связь с категорией для всех переводов товара. |
Переводить можно обычные поля string и text, включая текст с Markdown. Идентификаторы, связи, автогенерируемые поля, enum, числа, даты, JSON, изображения, скрытые и password-поля остаются общими. Чтобы переводить подпись категории, настройте Languages у самой Category.
Переключатель Localized есть и в редакторе отдельного поля. Если он недоступен, сначала настройте языки сущности. Подсказка рядом с ним напоминает, где настроить языки: Entity settings → Languages. В Content языки сущности и переводимые поля отмечаются бейджами.
Заполните переводы в админке
Откройте запись Product. Над полями формы появятся языковые вкладки; основной язык идёт первым. При единственном языке вместо вкладок может отображаться его подпись.
Переключайтесь между вкладками и вводите переводы. Поля остаются в выбранном вами порядке: меняются только переводимые значения. Цена, категория и другие общие поля не получают отдельных копий. Например, подпись name-ru означает русское значение поля name; поле с именем name-ru в модели или API не создаётся.
Нажмите Save, чтобы сохранить запись. Сервер проверяет обязательные переводы; если их не хватает, форму нужно дополнить. Переключение вкладки само по себе данные не сохраняет. Readonly продолжает запрещать изменение переводов уже созданной записи; такие значения заполняйте при создании.
В списке Content language выбирает язык отображения, поиска, фильтров и сортировки. Админка запоминает выбранный язык. Поэтому основной язык будет первым в списке, но после возвращения может остаться выбранным другой язык, с которым вы работали.
Как языки работают в virtual
Virtual читает переводы исходных persistent-сущностей. Отдельные языки или копии переводов у неё создавать не нужно; редактируют данные в исходных сущностях.
Например, CatalogVirtual построена на Category и выбирает связанные products.name и products.description. Даже если у Category языки не включены, а у Product настроены en, ru и zh-Hans, у virtual появится переключатель этих языков.
В него входят языки всех сущностей, чьи переводимые поля выбраны в virtual: основного источника, belongsTo и hasMany. Если переводимые поля не выбраны, переключатель не нужен и не показывается. Начальный основной язык берётся у основного источника, если его переводимые поля участвуют в выборке; иначе — у первой выбранной связанной сущности с такими полями.
Выбранный язык применяется и к вложенным коллекциям: их поиск, фильтры и сортировка работают с соответствующими переводами. Если у связанной сущности нужного языка или значения нет, её переводимые поля возвращаются как null либо используют её собственную настройку подстановки. Общие поля остаются доступными; само отсутствие перевода не удаляет родительскую запись из ответа. Подробнее — виртуальные сущности.
Получите нужный язык через API
Добавьте ?locale=ru к list/get-запросу, чтобы получить русские значения. Без locale каждая исходная сущность использует свой основной язык. Параметр не обходит Access и правила чтения. Если у persistent-сущности с включёнными языками запрошенный язык не настроен, API вернёт HTTP 400; virtual принимает языки выбранных переводимых полей всех своих источников.
External API возвращает обычные имена полей — например, name и description, уже на выбранном языке. В нём нет карты всех переводов. Admin API дополнительно использует _translations: карту языков и значений. Примеры запросов и сохранения есть в документации API.
Если в БД уже есть данные
При первом включении локализации существующие значения становятся значениями основного языка. Остальные переводы можно заполнить позже — Admingen не переводит тексты автоматически.
Добавление локализации, новых языков и включение Require all languages не требует заранее заполнять старые записи. Сохраните нужные настройки, выполните Generate, примените новую миграцию и пересоберите приложение. Миграция не останавливается только из-за того, что в старых записях ещё нет обязательных переводов; такие значения могут оставаться null.
При создании или сохранении записи уже действуют текущие требования: обязательные поля нужно заполнить на основном языке либо на всех выбранных языках. Это относится и к редактированию старой записи, даже если вы меняете другое поле. Статус draft не отменяет проверку. Поэтому старые записи можно читать сразу после обновления, а перед их сохранением — дополнить недостающие значения.
При необходимости заполните старые данные вручную в админке или своей SQL-миграцией. Генератор не придумывает переводы, пустые строки или нулевые значения для выполнения новых требований. Если у вас уже есть ранее созданная блокирующая SQL-миграция, новое Generate не переписывает её автоматически — её применение и историю нужно разобрать отдельно.
Удаление языка из настройки сохраняет его переводы в БД, но убирает язык из доступных вариантов. При смене основного языка отсутствующие переводы также не блокируют новую миграцию; перед сохранением записи их проверят по актуальным правилам. Переименование уже сгенерированного переводимого поля пока блокируется и требует отдельной миграции данных — см. ограничения Rename.
Хранение и собственный код
У записи один ID. Общие поля остаются в основной таблице, переводы — в соседней таблице <table>_translations, например products_translations. Значения основного языка также сохраняются в исходных колонках основной таблицы. Уникальность переводимого поля проверяется отдельно в каждом языке.
Отсутствующий перевод в API может быть null, даже если поле обязательно на основном языке. В Go переводимое поле имеет тип *string: custom-коду нужно учитывать nil. Генератор не переписывает ваши custom-файлы. Для собственного контрола формы доступны текущая locale и inputName — имя ввода для конкретного перевода; обычное имя поля остаётся прежним. Подробнее — расширения приложения.
Дополнительно: манифест и CLI
Настройки сохраняются в .admingen/project.yaml: у сущности — localization, у выбранных полей — localized: true. Обычно удобнее менять их через Languages. Отдельных CLI-флагов для настройки языков сейчас нет; после сохранения модели через UI можно использовать обычные команды генерации.