Documentation / Développement / Guides pratiques / Translate a plugin

Translate a plugin

Your plugin.yaml is written in one language (its locale: field, fr by default) — labels, descriptions, value-set concept displays. This recipe adds a translation for another locale without touching the manifest, using the meridian-plugin i18n CLI, then covers the separate runtime strings your behaviors emit through ctx.t. It assumes a scaffolded plugin (see Scaffold a plugin); for the design behind derived keys and edge-resolved fallback, see Instance vs UI locale.

1. Extract a bundle skeleton

From the plugin's directory, run extract for the locale you want to add:

meridian-plugin i18n extract --locale en

(libs/plugin-cli/src/commands/i18n.ts, i18nExtractCommand). This writes or refreshes i18n/en.yaml next to plugin.yaml, creating the i18n/ directory if it doesn't exist yet. The command reports what it did:

@acme/vitals → i18n/en.yaml (14 clé(s) au total, 14 ajoutée(s)).

Run it again any time you add, rename, or remove a manifest entry — it is non-destructive: every key already translated in the bundle is kept untouched, only new keys are appended and stale ones (renamed or removed manifest entries) are reported as orphans rather than deleted:

  ⚠ 1 clé(s) orpheline(s) (hors manifeste, conservée(s) telles quelles) :
    - nodes.old-node-id.label

2. Fill in the translations

Open i18n/en.yaml. Every key extract couldn't find an existing value for is left empty, with the source-language text as a # source: comment right above it so you know what to translate:

nodes:
  patient.load-conditions:
    label: ""
    # source: Load conditions
    inputs.count.description: ""
    # source: Maximum number of conditions to return.

Replace each empty "" with the translation and remove the now-redundant comment (leaving it is harmless — check, in the next step, ignores comments). The bundle format is two levels — section → id → {path: translation} — because ids themselves contain dots (e.g. patient.load-conditions):

Section Key derived from
plugin._.description the manifest's top-level description
nodes.<id>.label / .description a node's label/description
nodes.<id>.inputs.<port>.description / outputs.<port>.description / config.<field>.description a node's port and config field docs
categories.<name>.label a node's category (one entry per distinct label)
types.<name>.doc / fields.<field>.doc a contributed type's docs
valueSets.<id>.doc / concepts.<code> a value-set's docs and concept displays
dimensions.<name>.label a contributed dimension's label
conversions.<from>-><to>.label a conversion's label
agents.<slug>.description / inputs.<port>.description / outputs.<port>.description an agent's docs (its name, which drives the node id, is never translated)
eventSources.<name>.description / eventBindings.<name>.description an event source's or binding's description

This table mirrors the manifest walker that both extract and the running host use (libs/shared/src/plugin/i18n.ts, manifestI18nEntries) — you never invent a key here; if a string doesn't show up after extract, it isn't a translatable field. For the fields that back each section, see Plugin manifest.

3. Add runtime strings your behaviors emit

Strings a behavior builds at execution time (validation titles, notification bodies) don't come from the manifest, so extract can't derive them — they go under the bundle's free-form runtime section, keyed however you like:

runtime:
  _:
    drug-safety-alert.title: Medication safety alert
    drug-safety-alert.issue-line: "• [{severity}] {detail}"

Add the same keys, translated, to every locale bundle you ship. extract preserves this section verbatim across re-runs (it's the runtimeRaw passed through untouched in libs/plugin-cli/src/commands/i18n.ts), so it's safe to run extract again after editing runtime by hand. For how a behavior reads these keys through ctx.t(key, params) — instance locale → your plugin's source locale → the raw key — see Implement a node behavior §5.

4. Check coverage

meridian-plugin i18n check

(i18nCheckCommand) walks every i18n/*.yaml file and, for each, lists keys the manifest expects but the bundle doesn't have (✗ … manquante(s)) and keys the bundle has but the manifest doesn't recognize (⚠ … orpheline(s), typically a stale key after a rename). It exits 1 if any locale is missing a key — wire it into CI:

✓ en: à jour (14 clé(s)).
fr: aucun bundle i18n/ (rien à vérifier).

Two things to know before you rely on this gate:

  • The source locale is exempt. A bundle whose filename matches the manifest's locale: (fr.yaml by default) is not required to repeat the manifest-derived keys — they already live in plugin.yaml. Its bundle, if present, exists only to carry runtime translations for that locale.
  • check never looks at runtime coverage. Those keys are free-form and not derivable from the manifest, so there is nothing to diff them against — a missing runtime key degrades silently to the untranslated key text at run time (see step 3 above and Set the instance language §3). Track runtime coverage by review, not by this command.

5. Ship the bundles

i18n/*.yaml must reach dist/ for an externally loaded plugin — npm run build (meridian-plugin build) copies every file under i18n/ into dist/i18n/ automatically, next to the compiled manifest. See Build and bundle for the full build output.

6. Verify

Confirm the host actually resolves your new locale, without needing a running instance in that language. Start the API (or your dev server) and request the plugin referential with ?locale=:

curl -s "http://localhost:4000/api/plugins?locale=en" | jq '.[] | select(.name=="@acme/vitals")'

Labels and descriptions should come back in English; any key you haven't translated yet falls back to the source-locale text, then to the raw key — never to an error. The same ?locale= parameter works on /api/catalog for node/type metadata as consumed by the visual editor.

75 documents7 sectionssource : /docs · généré au build