Content locales. A localized content type stores one row per language, tied together by a locale group, and the plugin extends the Content Library so those rows are edited as one record with a language switcher rather than as separate entries.
Install
npm install @apograph/i18n-server @apograph/i18n-adminRegister
// apps/server/src/plugins.ts
import { I18nServerPlugin } from '@apograph/i18n-server';
I18nServerPlugin(config.plugins.i18n);// apps/admin/src/plugins.ts
import { I18nPlugin } from '@apograph/i18n-admin';
I18nPlugin();Register the admin half after ContentPlugin() — it fills the Content
Library’s locale extensions, so it has to read after the thing it extends.
Configuration
Locales are not environment variables. They are stable product
configuration, so they live in apograph.config.ts as literals:
i18n: {
locales: [
{ slug: 'en', name: 'English', isDefault: true },
{ slug: 'de', name: 'Deutsch' }
],
orphanedLocales: 'fail'
}| Setting | What it does |
|---|---|
locales | The languages content can exist in. Exactly one is the default |
orphanedLocales | What to do when the database holds rows in a locale no longer listed |
Removing a locale hides rows, it does not delete them
Locale slugs are stored on entry rows. Drop de from the array and every
German row is intact and unreachable — the worst shape a silent failure can
take.
orphanedLocales: 'fail' refuses to boot and puts the choice in front of
whoever edited the array. Change it only for a deployment that is knowingly
mid-migration.
Shared fields
A field marked shared holds one value across every language. Editing it in the German view changes it in the English one, because there is only one of it. That is the rule that surprises everybody once, and it is worth reading the localization page before modelling a type around it.
The fallback chain
A request for a locale that has no row falls back through a chain ending at the default. Publishing can require a set of locales before an entry may go live — so a record cannot be published half-translated if you do not want it to be.