Set the instance language
The instance manifest's locale field picks the language behaviors write into
persisted content — human-task titles, notification bodies, messages to
the record — not the language of the console UI. This recipe sets that field
and confirms it took effect.
Before you proceed, make sure this is the setting you actually want: see Instance vs UI locale for why these are two independent settings and what happens if you conflate them.
1. Confirm what locale affects
locale (libs/shared/src/plugin/instance.ts, InstanceManifest) is the
language of the instance's own content, delivered to node behaviors as
ctx.locale / ctx.t(...) (libs/shared/src/plugin/behavior.ts,
PluginNodeContext):
/**
* Langue de l'INSTANCE — celle du contenu PERSISTÉ (notifications au dossier,
* titres de validation…). Une instance est monolingue : un établissement
* français écrit son dossier en français.
*/
locale: z.string().default("fr"),
It does not affect the config-plane/EHR chrome, the plugin catalogue, or
the plugin referential — those follow each user's own display locale (the
meridian-locale cookie), resolved per request via ?locale= on
/api/catalog and /api/plugins (apps/api/src/i18n.ts). Changing the
instance locale will not change what the console reads in for any user.
An instance is monolingual by design: this is a one-time deployment choice, not a per-user preference, and content already written is never retranslated when you change it later.
2. Set locale in the instance manifest
In the file INSTANCE_CONFIG points at (default instance.yaml at the repo
root), set:
apiVersion: meridian/instance-v1
locale: fr # language of content PERSISTED by runs (validations, notifications)
locale is a plain string with no enum constraint in the schema — it
defaults to fr if omitted. In practice the values worth using are the ones
your loaded plugins actually ship translations for (next step); an arbitrary
locale code is accepted at parse time but will simply fall through to
untranslated strings everywhere.
3. Check plugin translation coverage for that locale
Persisted strings come from each plugin's i18n/<locale>.yaml bundle, under
its free-form runtime: section (behaviors call ctx.t("some.key", params)
for text that isn't derived from the manifest). @posos/clinical is a
concrete example — it ships both
external-plugins/clinical/i18n/fr.yaml:
runtime:
_:
confirm-condition.title: Ajouter la condition « {display} » ?
acknowledge-critical.title: "Acquitter le résultat critique : {display} = {value} {unit} ?"
and external-plugins/clinical/i18n/en.yaml with the same two keys in
English. For each plugin your instance requires, check whether
i18n/<your-locale>.yaml exists and has a runtime: section. This is
diagnostic, not a hard gate — a missing bundle never fails the boot. The
resolution order (libs/shared/src/plugin/i18n.ts, resolveRuntimeKey) is:
- the requested instance locale's bundle,
- the plugin's own source locale (its manifest
locale:,frby default), - the raw key itself, verbatim.
So an uncovered locale degrades to a visible-but-untranslated string in
production content, not an error. meridian-plugin i18n check <plugin-dir>
(libs/plugin-cli/src/commands/i18n.ts) only validates the manifest-derived
keys (node labels, descriptions, value-set concepts) used for UI
localization — it does not check runtime: coverage, since those keys are
free-form and not derivable from the manifest. To close a translation gap in
a plugin you maintain, see
Translate a plugin.
4. Restart the endpoint and proxy
Both processes read the instance manifest once, at process startup, not on every request:
// apps/api/src/restate/services.ts
const instance = loadInstance();
// apps/api/src/server/api.ts
const instance = loadInstance();
There is no hot reload — a manifest edit only takes effect after both processes restart:
docker compose restart endpoint proxy
(In pnpm run dev, restart the dev process instead.)
5. Verify
GET /api/catalog echoes the locale the running proxy loaded, without
needing to trigger a run:
curl -s http://localhost:4000/api/catalog | jq .instanceLocale
For end-to-end confirmation, trigger a workflow that raises a human-task from
a plugin you checked in step 3 (e.g. @posos/clinical's confirm-condition)
and read the validation title produced — see
Add human validation for
how that title reaches the record. It should read in the language you set,
not the language of whichever browser you're viewing it from.
Related
- Instance vs UI locale — why these are two independent settings.
- Instance manifest — full manifest schema.
- Translate a plugin — add or complete an
i18n/<locale>.yamlbundle. - Add human validation — where validation titles surface.
- First instance — setting up a manifest from scratch.