Features Apograph CMS on GitHub

Translating content

Localization plugin @apograph/i18n-server@apograph/i18n-admin

How a translation is created and edited, and which fields you are editing for every language at once.

Documents 0.5.2 Updated Edit this page Report a problem

On this page

A content type marked localized stores one row per language, and the rows that are translations of each other form a group.

The interface for this comes from the localization plugin, which fills extension points the content library defines — which is why translation controls appear inside the editor without the editor knowing what a locale is.

The locale switcher

On a localized type, the records table gains a locale switcher and a Locales column showing which languages each record exists in.

In the editor, the Locale widget in the properties panel switches between translations. Switching re-targets the editor at that language’s record — and lands you on the same tab you were working in, rather than dropping you back to General.

Translated and Shared

The editor groups fields into two:

GroupWhat it means
TranslatedMarked localized in the content type. Each language has its own value.
SharedNot marked. One value for the record, the same in every language.

This is the thing to understand before editing. A field in the Shared group is not “the English one” — editing it writes it to every language’s row.

Editing a shared field edits every translation

Saving a shared field propagates it across the whole group. Any published translation it rewrites is moved back to Modified, keeping its published date — so what is live in German stays live, with your change pending on top of it, exactly as it would for the language you were editing.

If a rewritten sibling would become invalid, the whole save is refused with a 422. A draft edit cannot silently invalidate a live translation.

Each rewritten sibling also gets its own entry in version history, so a translation whose shared values moved has the record of it.

Creating a translation

From an existing record, adding a language creates a sibling in the same group. It arrives carrying the record’s shared values, and its translated fields are empty and waiting.

Relations follow the rules on relations: a link that belongs to the record propagates, and where the target is itself localized the link resolves to that target’s row in the same language — so the German article gets the German tag rather than the English one.

A link may never cross locales.

Coverage

The Insights dashboard has a translation coverage card: how much of each content type exists in each language.

It comes from the localization plugin rather than from content, because that plugin owns the configured locale set and therefore owns the question of what “complete” means.

Configuring languages

Locales are declared once, on the server plugin, and the admin reads them from the API — nothing is duplicated client-side. Each has a slug, a display name, one is the default, and text direction is inferred from the slug unless stated.

See localization for the configuration and for what happens if you remove a locale that still has content in it. The short version: the server refuses to boot, on purpose.

No machine translation, and no translation workflow

Apograph stores translations; it does not produce them. There is no machine translation, no translation memory, no glossary and no vendor integration — a translation is written and published like any other record.

The copilot can draft a translation as a proposed change, which is the nearest thing that exists. See the copilot.

Review is not part of translation, but it is available to it: a protection rule on the content type applies per locale, because approvals are bound to a revision and revisions are per locale.