UI interface · 0.8.1

Content / Languages: languages and translations

Configure entity languages and translated fields, fill translations in the admin, and select API response language.

On this page

A product can have its own name and description in several languages while sharing one price, category and set of images. You do not need a separate product record for every translation.

Configure languages separately for each entity. For example, Product can use English, Russian and Chinese, while an internal OwnerBonus entity uses Russian only. Content language does not grant access: Access still determines who can see entities and records. Both the generator and generated-admin interfaces remain in English.

Configure the entity languages

  1. In Content, select a persistent entity such as Product and open Entity settings.
  2. Open Languages, after Basic settings, and turn on Content languages.
  3. Select the languages in Languages. For example, en is English, ru Russian, zh-Hans Simplified Chinese and zh-Hant Traditional Chinese. A single selected language is also valid.
  4. Choose Default language. Reads without a locale parameter use it; it appears first in the admin language tabs and selectors.
  5. In Localized fields, select the fields that need translations.
  6. Choose the required-translation and missing-value behavior described below.
  7. Click Finish, then run Generate. Apply the generated migration and rebuild the application.

Setting Effect
Require all languages off Required translated fields must be filled in the default language only.
Require all languages on Required translated fields must be filled in every selected language. Optional fields remain optional. Validation applies on save, not only when publishing.
Missing translation → Return empty value The API returns null when the requested translation is missing. This is the initial behavior: another language is not substituted automatically.
Missing translation → Use default language A missing value is replaced with that field's value in the entity's default language. If that value is also missing, the result is null.

Fallback works separately for each field. If the Russian name exists but the Russian description does not, enabled fallback keeps the Russian name and uses only the default-language description. This read setting neither replaces required-field validation nor creates a translation in the database.

Choose which fields need translations

Our Product example has name, description, price and categoryId:

Field Setting Result
name: string Localized, Required Each language has its own name; Require all languages determines where it is required.
description: text Localized Each language has its own description, including content edited through Markdown.
price: int64 Shared One price for all languages.
categoryId: relation Shared One category relationship for all translations of the product.

Ordinary string and text fields can be translated, including text with Markdown presentation. Identifiers, relationships, generated fields, enums, numbers, dates, JSON, images, hidden fields and password fields stay shared. To translate a category label, configure Languages on Category itself.

The individual field editor also has a Localized switch. If it is unavailable, configure the entity languages first. Its tooltip tells you where to configure them: Entity settings → Languages. Content shows badges for the entity languages and translated fields.

Enter translations in the admin

Open a Product record. Language tabs appear above the fields, with the default language first. A single-language entity may show the language label instead of tabs.

Switch tabs to enter translations. Fields keep your chosen order; only translated values change. Price, category and other shared fields do not gain language-specific copies. A label such as name-ru means the Russian value of name; it does not create a name-ru property in the model or API.

Click Save to save the record. The server checks required translations; complete missing values before saving. Switching a tab does not save the record by itself. Readonly still prevents changes to translations on an existing record; enter those values when creating it.

In a list, Content language selects the language used for display, search, filters and sorting. The admin remembers your choice. The default language is first in the options, but another previously selected language can remain active when you return.

Languages in virtual entities

A virtual entity reads translations from its persistent sources. It needs no separate language configuration or translation copies; edit the data in the original entities.

For example, CatalogVirtual is based on Category and selects related products.name and products.description. If Category has no languages but Product supports en, ru and zh-Hans, the virtual entity still shows those languages in its selector.

The options combine languages from every entity whose translated fields are selected: the root source, belongsTo and hasMany relationships. No selected translated fields means no language selector. The initial default comes from the root when its translated fields participate; otherwise it comes from the first selected related entity with translated fields.

The requested language also applies to nested collections: their search, filters and sorting use the corresponding translations. When a related entity lacks that language or value, its translated fields return null or use its own fallback policy. Shared fields remain available; a missing translation alone does not remove the parent record from the response. See virtual entities.

Request a language through the API

Add ?locale=ru to a list/get request for Russian values. Without locale, each source entity uses its own default language. The parameter does not bypass Access or read rules. If a persistent entity has content languages enabled, requesting a language outside its configured list returns HTTP 400; a virtual entity accepts the languages of selected translated fields across its sources.

External API responses keep ordinary property names such as name and description, already resolved for the chosen language. They do not expose a map of all translations. Admin API additionally uses _translations, a language-to-field map. Request and update examples are in the API guide.

Existing database records

When localization is first enabled, existing values become default-language values. Fill other translations later; Admingen does not translate text automatically.

Enabling localization, adding languages or switching on Require all languages does not require filling existing records first. Save the settings, Generate, apply the new migration and rebuild the application. A migration does not stop merely because old records lack newly required translations; those values may remain null.

Current requirements apply when creating or saving a record: required fields need values in the default language or every selected language. This also applies when editing an old record, even if you change another field. Draft status does not bypass validation. Existing records can therefore be read immediately after the update, but missing required values must be supplied before saving them.

If needed, fill historical values in the admin or through a custom SQL migration. The generator does not invent translations, empty strings or zero values to satisfy new requirements. If an earlier generated SQL migration is already blocking the update, Generate does not rewrite it automatically; review its application state and history separately.

Removing a configured language preserves its stored translations but removes it from available choices. Changing the default language also permits a new migration with missing translations; current rules are enforced before a record is saved. Renaming a previously generated translated field remains blocked and requires a separate data migration; see Rename limitations.

Storage and custom code

A record has one ID. Shared fields stay in the base table; translations live in a neighboring <table>_translations table, such as products_translations. Default-language values are also stored in their original base-table columns. A translated unique field is unique separately within each language.

A missing API translation can be null even when the field is required in the default language. Generated Go fields use *string, so custom code must handle nil. The generator does not rewrite your custom files. A custom form control receives the current locale and inputName, the input name for that translation; the regular field name stays unchanged. See application extensions.

Optional: manifest and CLI

Settings are stored in .admingen/project.yaml: entity localization and localized: true on selected fields. Languages is usually the easier place to edit them. There are currently no dedicated CLI language-setting flags. After saving the model through the UI, you can use the usual generation commands.