Translating a FHIR Implementation Guide
Experimental. This skill has not been verified against a real task since its last change. Verify its output before relying on it.
Produces the translation supplements of an IG-Publisher-based guide and puts them exactly where
the publisher looks for them. The guide's own configuration decides which language is the source and
which are the translations — this skill reads that rather than assuming it.
Preconditions
Locate the guide. From the working directory, expect input/pagecontent/ together with a
sushi-config.yaml or an ig.ini. scripts/ig-translate.sh checks this itself and exits 2 with
a message if the directory is not an IG project — it deliberately does not report "nothing to
translate", which is what a silent failure here looks like.
Derive the language pair — never assume it. Read the guide's own sushi-config.yaml:
parameters:
i18n-default-lang: en # the SOURCE language
i18n-lang:
- de # the TARGET language(s)
translation-sources:
- input/translations/de
i18n-default-lang is the source: the language input/pagecontent/ is written in, and the
authoritative text.
- each
i18n-lang entry is a target: a language that gets a rendering under /<lang>/.
- If
i18n-default-lang is absent, the publisher's default applies and the source language is
not declared. Report that and ask rather than guessing — a guess here puts every file in the
wrong place.
- If no
i18n-lang is configured, there is no target language yet. Adding one is a change to the
guide's configuration and a decision for its maintainer, not for this skill.
This is the step that must not be skipped. The MII reversed its own default from German to
English once; a skill that had hard-coded the old direction would have placed every file wrongly
while reporting success.
Build first. Resource supplements need generated resources: the publisher writes
fsh-generated/resources/ and generates supplement templates per resource on each build.
Translating before a green build means inventing msgid values, which is fabrication.
Check whether this is the guide's problem or the template's. Language support is usually
split between an IG and the template package it renders with, and the split is about
responsibility, not paths:
- This skill covers the guide's own content: supplements and translated pages under
input/translations/<lang>/.
- A template package owns the language mechanism and policy — language-neutral
header/footer/CSS, inherited UI-string catalogues, and the choice of default language.
For the MII KDS module template that template package is
https://github.com/medizininformatik-initiative/ig-template-mii-kds, which carries its own
skill for those obligations. If the task is "keep the template's overrides language-neutral",
that is the template's problem, not this one.
Procedure
Output language: the target language derived in Preconditions 2. These instructions are English;
what this skill produces is prose in the target language. Neither follows from the other, so it is
stated. The examples below use de as the target because that is the common case here; substitute
the language you actually derived.
Resolve the script path first. The commands below name the tool relative to this skill's
own directory, not to your working directory — which is the project you are operating on. Set
SKILL_DIR to the directory containing this SKILL.md (you just read it, so you know where it
is) and use it in every invocation:
SKILL_DIR=<the directory containing this SKILL.md> # e.g. .claude/skills/fhir-ig-translation
Running a bare scripts/... from the project root does not merely fail — if the project happens
to have its own scripts/ directory with a same-named file, it silently runs that instead.
Scan to get the target path for every page and resource:
bash "$SKILL_DIR/scripts/ig-translate.sh" --scan <lang> # cwd = the guide's root
bash "$SKILL_DIR/scripts/ig-translate.sh" --scan <lang> path/to/ig # or point at it
The language argument is required — the script refuses to default it, so no run can silently
translate into a language nobody chose.
Resource supplements. One file per StructureDefinition, CodeSystem or Questionnaire at
input/translations/<lang>/<Type>-<id>.po, where msgid is the exact source text from
fsh-generated/resources/<Type>-<id>.json and msgstr is the translation. Copy the publisher's
generated template rather than hand-writing the msgid.
Narrative pages. One translation per page at
input/translations/<lang>/pagecontent/<same-filename>.md — the same file name, the same
structure, the same links, FHIR identifiers unchanged. Never a <name>-<lang>.md sibling in
input/pagecontent/: the toolchain renders that as a separate page, not as a translation.
Menu, if the guide has one: input/translations/<lang>/includes/menu.xml, mirroring
input/includes/menu.xml. A menu: property in sushi-config.yaml generates one untranslatable
menu and competes with this — it must not be used alongside.
The IG-level catalogue input/translations/<lang>/ImplementationGuide-<ig-id>.po. Not a
resource supplement and not optional: without it the pages:-tree page titles stay in the
default language, so /<lang>/ renders localized page bodies under default-language
breadcrumbs. <ig-id> is the id of fsh-generated/resources/ImplementationGuide-<id>.json;
build the unit set from that file's definition.page tree — the authoritative page set — using
input/includes/menu.xml only as a wording seed, and emit an empty msgstr plus a review
flag for every title left untranslated.
The sibling mii-ig-migration skill bundles a generator for exactly this file. It is a
precondition, not an assumption: a consumer may have installed this skill alone, in which
case the sibling path does not exist. Check first, and when it is absent either install it with
the pinned command below or write the units by hand — never proceed on a path that does not
resolve. Invoke it through a resolved $SKILL_DIR, never a bare scripts/…, which the
project's own scripts/ would shadow:
GEN="$SKILL_DIR/../mii-ig-migration/scripts/gen-page-title-po.py"
if [ -f "$GEN" ]; then python3 "$GEN" --help; else
echo "gen-page-title-po.py not installed — install the sibling skill, or write the units by hand:"
echo 'npx skills add "https://github.com/forschungsgruppe-digital-health/agent-skills/tree/v0.14.0" --skill mii-ig-migration --agent claude-code codex --yes'
fi
The /tree/<ref> form is what pins; owner/repo@<ref> does not — in that CLI @
introduces a skill name and the command silently installs from the default branch.
Read that script's header before regenerating an existing catalogue: the same file also carries
hand-added units (the guide's title, per-artifact names) that the generator does not own, and
its header states how it treats them.
Validate, then build:
bash "$SKILL_DIR/scripts/ig-translate.sh" --validate <lang>
Findings ([WARN]) exit 1; a run that finds nothing to validate says so explicitly and
exits 0 — add --strict to make an empty translation set fail too, which is the right wiring
for CI.
Bilingual human review is mandatory before the translated rendering is trusted. Mark every
machine translation TODO:REVIEW until a human has signed it off.
Harvest mode — adopting an existing rendering
When a rendering in the target language already exists somewhere, harvest it instead of
re-translating:
- Fill in
references/harvest-config.yaml: the source of the
target-language text (a parallel rendered guide for narrative; FSH translation extensions or
designations for resource texts) and the page/artefact mapping.
- Resources: move the existing designations or translation extensions into
input/translations/<lang>/<Type>-<id>.po. The page titles of the existing rendering are a
translation seed for the IG-level catalogue (step 5), not a substitute for it.
- Narrative: copy the page content into
input/translations/<lang>/pagecontent/<name>.md, citing the source path per page. Invent
nothing; mark anything unclear TODO:REVIEW.
- Validate, build and review as above.
What the toolchain actually renders
Verified with IG Publisher 2.2.11 and fhir2.base.template 0.1.0 (2026-07) unless the row says
otherwise — one row rests on HL7's reference IG, whose live build was produced by publisher
2.0.13, and one row is explicitly unverified. Each row states its own basis; do not read the
heading as covering all of them. This is publisher behaviour and applies to any language pair.
| Content |
Translatable? |
Mechanism |
Narrative pages (input/pagecontent/<name>.md) |
Yes, renders |
input/translations/<lang>/pagecontent/<same-filename> — the whole page renders on /<lang>/. No file → falls back to the default-language source. |
Resource texts of StructureDefinition, CodeSystem, Questionnaire (description, designations, element definition) |
Yes, renders |
Supplement input/translations/<lang>/<Type>-<id>.{po|xliff|json} |
Menu (input/includes/menu.xml) |
Yes |
input/translations/<lang>/includes/menu.xml |
ImplementationGuide own title, and per-artifact definition.resource.name |
Yes, renders — but observed on the 2.0.13 reference build, not on our pin |
input/translations/<lang>/ImplementationGuide-<ig-id>.po. Not a resource supplement — the publisher imports the IG's translations at load time (PublisherIGLoader → importFromTranslations, read from the source), so the TRANSLATION_SUPPLEMENT_RESOURCE_TYPES restriction does not apply. Units are keyed by FHIRPath in the #: comment. es header title + es/artifacts.html artifact names were seen on HL7's reference build |
ImplementationGuide description, publisher, name, per-artifact .description |
Unverified |
Units for these exist in the reference catalogue and belong in the same file; no rendering was observed for them, on either build. name is FHIR's computer-friendly name, not display text — do not expect it to render at all. Add them, do not claim them |
Breadcrumbs & titles of pages:-tree pages (breadcrumbs incl. the root label, TOC page body, browser <title>) |
Yes, renders |
ImplementationGuide.definition.page.title units in the same IG-level .po; the publisher feeds them into the per-language titlelang / breadcrumblang maps. Verified on 2.2.11 on a migrated MII KDS module with the breadcrumb override deleted (23 units; temp/pages/_data/pages.json titlelang differs en/de for all 23, zero before). The left-hand navigation menu was not inspected — no claim is made for it. Generate the units from the pages: tree of the generated ImplementationGuide-<id>.json (the authoritative set), using the menus only as a wording seed, and emit an empty msgstr + a review flag for any title left untranslated. HL7's FHIR/multi-lang-test-ig ships such a file and renders es/nl breadcrumbs from it; both MII template repos already ship one on their dev branch. This retires the breadcrumb override (fragment-pagebegin.html + input/includes/breadcrumb-titles-de.txt), which shipped in exactly one release, template v0.5.0 — never in v0.4.0 or earlier, never on dev. A module generated from v0.5.0 should gain this .po and drop the override. Never work around it with sibling pages or menu tricks |
ValueSet, concept.display / concept.definition |
Partial / No — unresolved |
Not applied from a plain .po supplement on this toolchain. Conflict, deliberately left open: references/translate-spec.md § 1 lists concept.display/concept.definition among the fields a supported CodeSystem supplement does translate. Neither claim has a build behind it; the next run that touches a CodeSystem supplement must test it and replace both |
A language in i18n-lang but absent from translation-sources |
No — silently |
Its .po files are ignored without error or warning and the default language renders. Check this pairing before concluding a mechanism is unsupported |
Treat this table as ground truth, and re-verify it whenever the pinned IG Publisher or base
template version changes. Read the pins from the guide's own build workflow, not from this file.
Obsolete claims, recorded so they are not reintroduced:
- An earlier version of this skill used a
<name>-<lang>.md sibling for pages and stated that
narrative pages were "not yet" renderable. Both were wrong. The correct mechanism is a
translation-source folder, as HL7's own
FHIR/multi-lang-test-ig demonstrates.
- This skill claimed that
ImplementationGuide-<id>.po is "ignored by the Publisher", that there is
"no supplement mechanism" for the IG title, and that default-language breadcrumbs on pages:-tree
pages are "a toolchain limitation, not a defect" requiring the MII template's
breadcrumb-titles-de.txt workaround. All wrong: it generalized
TRANSLATION_SUPPLEMENT_RESOURCE_TYPES — which really does restrict resource supplements — to the
IG resource, which is translated by a separate load-time import. The page-title mechanism was
verified on 2.2.11 (our pin) against an MII KDS module with the template's breadcrumb override
deleted; HL7's reference IG ships such a .po and its live build — produced by 2.0.13 —
renders es/nl breadcrumbs from it. That says nothing about since when the publisher supports
this: no support floor is claimed, and none should be inferred from those two version numbers. The
real cause of "translations do not apply" is usually the translation-sources footgun in the table
above.
Verification
bash "$SKILL_DIR/scripts/ig-translate.sh" --validate <lang> # --strict: empty set fails too
- Exit 2 with a clear message when run outside an IG, or when the language argument is missing — a
silent empty scan is the failure mode this guards against.
--validate reports [OK] per supplement and per page, and no [WARN]; any [WARN] exits 1,
and its summary line states how many supplements and pages were actually checked. "Checked: 0"
with exit 0 is an explicit nothing to validate, never a claim that translations are valid
(--strict turns it into a failure).
- The script WARNs when
<lang> is not among the guide's i18n-lang targets or when
i18n-default-lang is undeclared — a best-effort echo of Preconditions 2, not a substitute
for it.
- Every
.po filename is <Type>-<id> and matches a real fsh-generated/resources/<Type>-<id>.json
— including ImplementationGuide-<ig-id>.po, which is a legitimate file, not a naming error.
- No supplement exists for an unsupported type, and no
menu.po exists — the publisher ignores both.
- Every
i18n-lang entry has a matching translation-sources entry; otherwise that language's .po
files are ignored silently.
- Every translated page has a source page of the same name under
input/pagecontent/.
- After a build,
/<lang>/ artefact pages show the translated element texts and /<lang>/ narrative
pages render in the target language.
- After a build with an IG-level
.po, /<lang>/ breadcrumbs, the TOC page body and the browser
<title> are in the target language, and temp/pages/_data/pages.json carries a titlelang that
differs from the default-language title for every page in the pages: tree. A page whose
titlelang still equals the source title has no unit, or an empty msgstr, or the language is
missing from translation-sources.
- The default-language
input/pagecontent/ and the FSH sources are unchanged — git diff on
them is empty.
Guardrails
- The default-language source stays leading and binding. A translation is a rendering aid, never
the normative text. Which language that is comes from
i18n-default-lang, not from this file.
- Never change the source. Translations are additive under
input/translations/<lang>/.
- FHIR identifiers stay untranslated —
name, id, codes and canonical URLs are not language.
- No invention. Mark every machine translation
TODO:REVIEW; bilingual human review is
mandatory.
- Only on confirmation. The default is a dry-run scan.
- Propose, do not merge. Deliver changes as a pull request, and determine the target branch
from the repository's own convention — do not assume one. An earlier version of this skill
hard-coded
dev, which is one repository's convention and wrong everywhere else.
Scope and delimitation
Covers producing and placing a guide's translation supplements, in both directions of
provenance: translating the default-language source, or harvesting an existing rendering.
Deliberately not covered:
- Measuring or comparing guides — see
fhir-ig-analysis.
- Migrating a guide onto the MII KDS module template — see
mii-ig-migration, which sets
translation up as one step of a migration and then hands over here.
- A template package's language mechanism and policy — a different repository's concern; see
Preconditions 4.
- Choosing the guide's languages. Adding an
i18n-lang entry changes the guide's configuration
and is its maintainer's decision.
- Judging whether a translation is good. That is the mandatory human review, not this skill.
If a skill of this name is provided both by this catalog and locally, the local one wins.
References
references/translate-spec.md — the full mechanics, file
conventions and formats.
references/harvest-config.yaml — configuration schema for
harvest mode.
references/triggers.md — the Gate 3 prompt set.
scripts/ig-translate.sh — scan and validate; dry-run by design, it
writes nothing. It scans for the IG-level catalogue alongside the resource supplements and accepts
it in --validate; genuinely unsupported types still WARN. Validation findings exit 1; --strict
also fails an empty translation set.
Provenance
Derived from skills/ig-translate in
medizininformatik-initiative/mii-kds-module-template at commit
b5beedb17a66a4397d597429668c7b6d54202c62, which in turn adapted the ig-translate skill of
forschungsgruppe-digital-health/mii-kds-sample-ig-inoffiziell (CC-BY-4.0) and refocused it on the
module side. Both steps of that lineage are recorded deliberately.
Reworked on 2026-07-31 for this catalog. Beyond the catalog contract, four substantive changes:
- The helper now ships with the skill. It previously lived in the source repository's
scripts/
and was referenced by parent traversal.
scripts/ig-translate.sh operates on the current working directory and detects whether it is
an IG project. It previously did cd "$(dirname "$0")/..", assuming it sat in
<module-repo>/scripts/; installed as part of a skill that cd reaches the skill directory, and
the scan would have reported every page as missing.
- The
dev branch target was removed from the description and the guardrails, and replaced by
discovering the repository's own convention.
- The language pair is derived, not assumed. The inherited skill was written for one direction
(
en → de) and for MII KDS modules specifically, which made a fhir- prefix on its name an
overclaim. It now reads i18n-default-lang and i18n-lang from the guide being worked on, the
script requires an explicit language argument instead of defaulting to de, and the mechanics are
stated for any language pair. That is what earns the prefix.
Revised on 2026-08-01 after the skill's first real-task exercise (a dry run against
kerndatensatz-dokument): --validate findings now exit 1 and an empty translation set is
reported explicitly (and fails under --strict) instead of an unconditional exit 0; the script
cross-checks <lang> against the guide's i18n parameters with best-effort warnings; the harvest
config's page_map example now covers non-English source page names; and the allowed-tools
grant covers executing the bundled bash script. The dry-run findings live in the
mii-kds-dokument-ig-inoffiziell sandbox under docs/reports/dry-run-2026-07-31/.
Revised on 2026-08-05 to retire a documented false claim: that the publisher cannot localize the
titles of pages:-tree pages and that an ImplementationGuide-<id>.po is ignored. Evidence, and
nothing beyond it:
- Our own build on our own pin (IG Publisher 2.2.11) — the migrated MII KDS Dokument guide with
the template's breadcrumb override deleted and 23
page.title units supplied: /de/ breadcrumbs
German including the root label "Inhaltsverzeichnis", the TOC page body German, the browser
<title> German, and temp/pages/_data/pages.json titlelang differing en/de for all 23 pages
(zero before). Build health unchanged (sushi 0 errors, QA err=7 = the established baseline). The
left-hand nav, IG.description, IG.publisher, IG.name and artifact names were not observed
on 2.2.11 and are not claimed for it.
- HL7's reference IG
FHIR/multi-lang-test-ig, live build produced by publisher 2.0.13 —
localized es/nl breadcrumbs, a translated IG title in the /es/ header, translated artifact
names on /es/artifacts.html, and fr (in i18n-lang, absent from translation-sources) as a
controlled negative. A different publisher version from ours; kept separable in the tables.
- Prior art in our own organisation: both MII template repos already carry a correct catalogue on
their
dev branch, and ig-template-mii-kds records the same mechanism in the MII's own
kerndatensatz-basis module, "verified 2026-07-30".
Consequences here: the rendering table attributes every row to the build that supports it and marks
the unobserved fields unverified; the breadcrumb override is recorded as shipping in exactly one
release, template v0.5.0 (never v0.4.0 or earlier, never on dev), as a misdiagnosis now being
retired; a Procedure step for the IG-level catalogue was added, pointing at the generator bundled
with mii-ig-migration; the CodeSystem concept.display/definition conflict between this file and
references/translate-spec.md is flagged as unresolved instead of silently decided; and
scripts/ig-translate.sh no longer WARNs on (nor fails) an ImplementationGuide-<id>.po — the very
file the procedure now mandates — while genuinely unsupported types still do.
Original licence: CC-BY-4.0, as declared by both source repositories. scripts/ is Apache-2.0,
matching this repository's code licence.
1---2name: fhir-ig-translation3description: Sets up the translation supplements an IG-Publisher-based FHIR Implementation Guide needs for its non-default languages, putting each one exactly where the publisher reads it — either by translating from the default-language source or by harvesting an already-published rendering in the target language. The default language stays authoritative and every machine translation needs a bilingual human review. Use this skill when a guide builds green and an additional language rendering is wanted, when the /<lang>/ pages show the default language instead of the translation, when deciding where a .po supplement or a translated page belongs, or when the user mentions Übersetzung, translation supplement, input/translations, po file, i18n-lang or i18n-default-lang. Do not use for measuring or comparing guides, for migrating one onto the MII KDS module template, or for a template package's own language mechanism; see fhir-ig-analysis and mii-ig-migration.4license: CC-BY-4.05---67# Translating a FHIR Implementation Guide89> **Experimental.** This skill has not been verified against a real task since its last change. Verify its output before relying on it.1011Produces the translation *supplements* of an IG-Publisher-based guide and puts them exactly where12the publisher looks for them. The guide's own configuration decides which language is the source and13which are the translations — this skill reads that rather than assuming it.1415## Preconditions16171. **Locate the guide.** From the working directory, expect `input/pagecontent/` together with a18 `sushi-config.yaml` or an `ig.ini`. `scripts/ig-translate.sh` checks this itself and exits 2 with19 a message if the directory is not an IG project — it deliberately does **not** report "nothing to20 translate", which is what a silent failure here looks like.21222. **Derive the language pair — never assume it.** Read the guide's own `sushi-config.yaml`:2324 ```yaml25 parameters:26 i18n-default-lang: en # the SOURCE language27 i18n-lang:28 - de # the TARGET language(s)29 translation-sources:30 - input/translations/de31 ```3233 - `i18n-default-lang` is the **source**: the language `input/pagecontent/` is written in, and the34 authoritative text.35 - each `i18n-lang` entry is a **target**: a language that gets a rendering under `/<lang>/`.36 - If `i18n-default-lang` is absent, the publisher's default applies and the source language is37 not declared. Report that and ask rather than guessing — a guess here puts every file in the38 wrong place.39 - If no `i18n-lang` is configured, there is no target language yet. Adding one is a change to the40 guide's configuration and a decision for its maintainer, not for this skill.4142 **This is the step that must not be skipped.** The MII reversed its own default from German to43 English once; a skill that had hard-coded the old direction would have placed every file wrongly44 while reporting success.45463. **Build first.** Resource supplements need generated resources: the publisher writes47 `fsh-generated/resources/` and generates supplement templates per resource on each build.48 Translating before a green build means inventing `msgid` values, which is fabrication.49504. **Check whether this is the guide's problem or the template's.** Language support is usually51 split between an IG and the template package it renders with, and the split is about52 *responsibility*, not paths:53 - **This skill** covers the guide's own content: supplements and translated pages under54 `input/translations/<lang>/`.55 - **A template package** owns the language *mechanism* and *policy* — language-neutral56 header/footer/CSS, inherited UI-string catalogues, and the choice of default language.5758 For the MII KDS module template that template package is59 <https://github.com/medizininformatik-initiative/ig-template-mii-kds>, which carries its own60 skill for those obligations. If the task is "keep the template's overrides language-neutral",61 that is the template's problem, not this one.6263## Procedure6465**Output language: the target language derived in Preconditions 2.** These instructions are English;66what this skill produces is prose in the target language. Neither follows from the other, so it is67stated. The examples below use `de` as the target because that is the common case here; substitute68the language you actually derived.697071> **Resolve the script path first.** The commands below name the tool relative to **this skill's72> own directory**, not to your working directory — which is the project you are operating on. Set73> `SKILL_DIR` to the directory containing this `SKILL.md` (you just read it, so you know where it74> is) and use it in every invocation:75>76> ```bash77> SKILL_DIR=<the directory containing this SKILL.md> # e.g. .claude/skills/fhir-ig-translation78> ```79>80> Running a bare `scripts/...` from the project root does not merely fail — if the project happens81> to have its own `scripts/` directory with a same-named file, it silently runs **that** instead.82831. **Scan** to get the target path for every page and resource:8485 ```bash86 bash "$SKILL_DIR/scripts/ig-translate.sh" --scan <lang> # cwd = the guide's root87 bash "$SKILL_DIR/scripts/ig-translate.sh" --scan <lang> path/to/ig # or point at it88 ```8990 The language argument is **required** — the script refuses to default it, so no run can silently91 translate into a language nobody chose.92932. **Resource supplements.** One file per StructureDefinition, CodeSystem or Questionnaire at94 `input/translations/<lang>/<Type>-<id>.po`, where `msgid` is the **exact** source text from95 `fsh-generated/resources/<Type>-<id>.json` and `msgstr` is the translation. Copy the publisher's96 generated template rather than hand-writing the `msgid`.97983. **Narrative pages.** One translation per page at99 `input/translations/<lang>/pagecontent/<same-filename>.md` — the *same* file name, the same100 structure, the same links, FHIR identifiers unchanged. **Never** a `<name>-<lang>.md` sibling in101 `input/pagecontent/`: the toolchain renders that as a separate page, not as a translation.1021034. **Menu**, if the guide has one: `input/translations/<lang>/includes/menu.xml`, mirroring104 `input/includes/menu.xml`. A `menu:` property in `sushi-config.yaml` generates one untranslatable105 menu and competes with this — it must not be used alongside.1061075. **The IG-level catalogue** `input/translations/<lang>/ImplementationGuide-<ig-id>.po`. Not a108 resource supplement and not optional: without it the `pages:`-tree page titles stay in the109 default language, so `/<lang>/` renders localized page *bodies* under default-language110 breadcrumbs. `<ig-id>` is the `id` of `fsh-generated/resources/ImplementationGuide-<id>.json`;111 build the unit set from that file's `definition.page` tree — the authoritative page set — using112 `input/includes/menu.xml` only as a wording seed, and emit an **empty `msgstr`** plus a review113 flag for every title left untranslated.114115 The sibling `mii-ig-migration` skill bundles a generator for exactly this file. It is a116 **precondition, not an assumption**: a consumer may have installed this skill alone, in which117 case the sibling path does not exist. Check first, and when it is absent either install it with118 the pinned command below or write the units by hand — never proceed on a path that does not119 resolve. Invoke it through a resolved `$SKILL_DIR`, never a bare `scripts/…`, which the120 project's own `scripts/` would shadow:121122 ```bash123 GEN="$SKILL_DIR/../mii-ig-migration/scripts/gen-page-title-po.py"124 if [ -f "$GEN" ]; then python3 "$GEN" --help; else125 echo "gen-page-title-po.py not installed — install the sibling skill, or write the units by hand:"126 echo 'npx skills add "https://github.com/forschungsgruppe-digital-health/agent-skills/tree/v0.14.0" --skill mii-ig-migration --agent claude-code codex --yes'127 fi128 ```129130 The `/tree/<ref>` form is what pins; `owner/repo@<ref>` does **not** — in that CLI `@`131 introduces a skill name and the command silently installs from the default branch.132133 Read that script's header before regenerating an existing catalogue: the same file also carries134 hand-added units (the guide's `title`, per-artifact names) that the generator does not own, and135 its header states how it treats them.1361376. **Validate, then build:**138139 ```bash140 bash "$SKILL_DIR/scripts/ig-translate.sh" --validate <lang>141 ```142143 Findings (`[WARN]`) exit 1; a run that finds **nothing to validate** says so explicitly and144 exits 0 — add `--strict` to make an empty translation set fail too, which is the right wiring145 for CI.1461477. **Bilingual human review is mandatory** before the translated rendering is trusted. Mark every148 machine translation `TODO:REVIEW` until a human has signed it off.149150### Harvest mode — adopting an existing rendering151152When a rendering in the target language already exists somewhere, harvest it instead of153re-translating:1541551. Fill in [`references/harvest-config.yaml`](references/harvest-config.yaml): the source of the156 target-language text (a parallel rendered guide for narrative; FSH `translation` extensions or157 `designation`s for resource texts) and the page/artefact mapping.1582. **Resources:** move the existing designations or translation extensions into159 `input/translations/<lang>/<Type>-<id>.po`. The page titles of the existing rendering are a160 translation seed for the IG-level catalogue (step 5), not a substitute for it.1613. **Narrative:** copy the page content into162 `input/translations/<lang>/pagecontent/<name>.md`, citing the source path per page. Invent163 nothing; mark anything unclear `TODO:REVIEW`.1644. Validate, build and review as above.165166## What the toolchain actually renders167168Verified with **IG Publisher 2.2.11** and `fhir2.base.template` 0.1.0 (2026-07) unless the row says169otherwise — one row rests on HL7's reference IG, whose live build was produced by publisher170**2.0.13**, and one row is explicitly unverified. Each row states its own basis; do not read the171heading as covering all of them. This is publisher behaviour and applies to any language pair.172173| Content | Translatable? | Mechanism |174| --- | --- | --- |175| **Narrative pages** (`input/pagecontent/<name>.md`) | **Yes, renders** | `input/translations/<lang>/pagecontent/<same-filename>` — the whole page renders on `/<lang>/`. No file → falls back to the default-language source. |176| Resource texts of **StructureDefinition, CodeSystem, Questionnaire** (`description`, designations, element `definition`) | **Yes, renders** | Supplement `input/translations/<lang>/<Type>-<id>.{po\|xliff\|json}` |177| **Menu** (`input/includes/menu.xml`) | **Yes** | `input/translations/<lang>/includes/menu.xml` |178| **ImplementationGuide** own `title`, and per-artifact `definition.resource.name` | **Yes, renders — but observed on the 2.0.13 reference build, not on our pin** | `input/translations/<lang>/ImplementationGuide-<ig-id>.po`. Not a resource supplement — the publisher imports the IG's translations at load time (`PublisherIGLoader` → `importFromTranslations`, read from the source), so the `TRANSLATION_SUPPLEMENT_RESOURCE_TYPES` restriction does not apply. Units are keyed by FHIRPath in the `#:` comment. `es` header title + `es/artifacts.html` artifact names were seen on HL7's reference build |179| **ImplementationGuide** `description`, `publisher`, `name`, per-artifact `.description` | **Unverified** | Units for these exist in the reference catalogue and belong in the same file; **no rendering was observed** for them, on either build. `name` is FHIR's computer-friendly name, not display text — do not expect it to render at all. Add them, do not claim them |180| **Breadcrumbs & titles of `pages:`-tree pages** (breadcrumbs incl. the root label, TOC page body, browser `<title>`) | **Yes, renders** | `ImplementationGuide.definition.page.title` units in the same IG-level `.po`; the publisher feeds them into the per-language `titlelang` / `breadcrumblang` maps. Verified on **2.2.11** on a migrated MII KDS module with the breadcrumb override deleted (23 units; `temp/pages/_data/pages.json` `titlelang` differs en/de for all 23, zero before). The **left-hand navigation menu was not inspected — no claim is made for it.** Generate the units from the **`pages:` tree** of the generated `ImplementationGuide-<id>.json` (the authoritative set), using the menus only as a wording seed, and emit an empty `msgstr` + a review flag for any title left untranslated. HL7's [`FHIR/multi-lang-test-ig`](https://github.com/FHIR/multi-lang-test-ig) ships such a file and renders `es`/`nl` breadcrumbs from it; both MII template repos already ship one on their `dev` branch. This **retires** the breadcrumb override (`fragment-pagebegin.html` + `input/includes/breadcrumb-titles-de.txt`), which shipped in **exactly one release, template v0.5.0** — never in v0.4.0 or earlier, never on `dev`. A module generated from v0.5.0 should gain this `.po` and drop the override. Never work around it with sibling pages or menu tricks |181| **ValueSet**, `concept.display` / `concept.definition` | **Partial / No — unresolved** | Not applied from a plain `.po` supplement on this toolchain. **Conflict, deliberately left open:** [`references/translate-spec.md`](references/translate-spec.md) § 1 lists `concept.display`/`concept.definition` among the fields a *supported* CodeSystem supplement does translate. Neither claim has a build behind it; the next run that touches a CodeSystem supplement must test it and replace both |182| A language in `i18n-lang` but **absent from `translation-sources`** | **No — silently** | Its `.po` files are ignored without error or warning and the default language renders. Check this pairing before concluding a mechanism is unsupported |183184Treat this table as ground truth, and **re-verify it whenever the pinned IG Publisher or base185template version changes.** Read the pins from the guide's own build workflow, not from this file.186187Obsolete claims, recorded so they are not reintroduced:188189- An earlier version of this skill used a `<name>-<lang>.md` sibling for pages and stated that190 narrative pages were "not yet" renderable. Both were wrong. The correct mechanism is a191 translation-source folder, as HL7's own192 [`FHIR/multi-lang-test-ig`](https://github.com/FHIR/multi-lang-test-ig) demonstrates.193- This skill claimed that `ImplementationGuide-<id>.po` is "ignored by the Publisher", that there is194 "no supplement mechanism" for the IG title, and that default-language breadcrumbs on `pages:`-tree195 pages are "a toolchain limitation, not a defect" requiring the MII template's196 `breadcrumb-titles-de.txt` workaround. All wrong: it generalized197 `TRANSLATION_SUPPLEMENT_RESOURCE_TYPES` — which really does restrict *resource supplements* — to the198 IG resource, which is translated by a **separate load-time import**. The page-title mechanism was199 verified on **2.2.11** (our pin) against an MII KDS module with the template's breadcrumb override200 deleted; HL7's reference IG ships such a `.po` and its live build — produced by **2.0.13** —201 renders `es`/`nl` breadcrumbs from it. That says nothing about *since when* the publisher supports202 this: no support floor is claimed, and none should be inferred from those two version numbers. The203 real cause of "translations do not apply" is usually the `translation-sources` footgun in the table204 above.205206## Verification207208```bash209bash "$SKILL_DIR/scripts/ig-translate.sh" --validate <lang> # --strict: empty set fails too210```211212- Exit 2 with a clear message when run outside an IG, or when the language argument is missing — a213 silent empty scan is the failure mode this guards against.214- `--validate` reports `[OK]` per supplement and per page, and no `[WARN]`; any `[WARN]` exits 1,215 and its summary line states how many supplements and pages were actually checked. "Checked: 0"216 with exit 0 is an explicit *nothing to validate*, never a claim that translations are valid217 (`--strict` turns it into a failure).218- The script WARNs when `<lang>` is not among the guide's `i18n-lang` targets or when219 `i18n-default-lang` is undeclared — a best-effort echo of Preconditions 2, not a substitute220 for it.221- Every `.po` filename is `<Type>-<id>` and matches a real `fsh-generated/resources/<Type>-<id>.json`222 — including `ImplementationGuide-<ig-id>.po`, which is a legitimate file, not a naming error.223- No supplement exists for an unsupported type, and no `menu.po` exists — the publisher ignores both.224- Every `i18n-lang` entry has a matching `translation-sources` entry; otherwise that language's `.po`225 files are ignored silently.226- Every translated page has a source page of the same name under `input/pagecontent/`.227- After a build, `/<lang>/` artefact pages show the translated element texts and `/<lang>/` narrative228 pages render in the target language.229- After a build with an IG-level `.po`, `/<lang>/` breadcrumbs, the TOC page body and the browser230 `<title>` are in the target language, and `temp/pages/_data/pages.json` carries a `titlelang` that231 differs from the default-language title for every page in the `pages:` tree. A page whose232 `titlelang` still equals the source title has no unit, or an empty `msgstr`, or the language is233 missing from `translation-sources`.234- The default-language `input/pagecontent/` and the FSH sources are **unchanged** — `git diff` on235 them is empty.236237## Guardrails238239- **The default-language source stays leading and binding.** A translation is a rendering aid, never240 the normative text. Which language that is comes from `i18n-default-lang`, not from this file.241- **Never change the source.** Translations are additive under `input/translations/<lang>/`.242- **FHIR identifiers stay untranslated** — `name`, `id`, codes and canonical URLs are not language.243- **No invention.** Mark every machine translation `TODO:REVIEW`; bilingual human review is244 mandatory.245- **Only on confirmation.** The default is a dry-run scan.246- **Propose, do not merge.** Deliver changes as a pull request, and **determine the target branch247 from the repository's own convention** — do not assume one. An earlier version of this skill248 hard-coded `dev`, which is one repository's convention and wrong everywhere else.249250## Scope and delimitation251252Covers **producing and placing a guide's translation supplements**, in both directions of253provenance: translating the default-language source, or harvesting an existing rendering.254255Deliberately not covered:256257- **Measuring or comparing guides** — see `fhir-ig-analysis`.258- **Migrating a guide onto the MII KDS module template** — see `mii-ig-migration`, which sets259 translation up as one step of a migration and then hands over here.260- **A template package's language mechanism and policy** — a different repository's concern; see261 Preconditions 4.262- **Choosing the guide's languages.** Adding an `i18n-lang` entry changes the guide's configuration263 and is its maintainer's decision.264- **Judging whether a translation is good.** That is the mandatory human review, not this skill.265266If a skill of this name is provided both by this catalog and locally, the local one wins.267268## References269270- [`references/translate-spec.md`](references/translate-spec.md) — the full mechanics, file271 conventions and formats.272- [`references/harvest-config.yaml`](references/harvest-config.yaml) — configuration schema for273 harvest mode.274- [`references/triggers.md`](references/triggers.md) — the Gate 3 prompt set.275- [`scripts/ig-translate.sh`](scripts/ig-translate.sh) — scan and validate; dry-run by design, it276 writes nothing. It scans for the IG-level catalogue alongside the resource supplements and accepts277 it in `--validate`; genuinely unsupported types still WARN. Validation findings exit 1; `--strict`278 also fails an empty translation set.279280## Provenance281282Derived from `skills/ig-translate` in283`medizininformatik-initiative/mii-kds-module-template` at commit284`b5beedb17a66a4397d597429668c7b6d54202c62`, which in turn adapted the `ig-translate` skill of285`forschungsgruppe-digital-health/mii-kds-sample-ig-inoffiziell` (CC-BY-4.0) and refocused it on the286module side. Both steps of that lineage are recorded deliberately.287288Reworked on 2026-07-31 for this catalog. Beyond the catalog contract, four substantive changes:289290- The helper now ships **with** the skill. It previously lived in the source repository's `scripts/`291 and was referenced by parent traversal.292- `scripts/ig-translate.sh` operates on the **current working directory** and detects whether it is293 an IG project. It previously did `cd "$(dirname "$0")/.."`, assuming it sat in294 `<module-repo>/scripts/`; installed as part of a skill that `cd` reaches the skill directory, and295 the scan would have reported every page as missing.296- The `dev` branch target was removed from the description and the guardrails, and replaced by297 discovering the repository's own convention.298- **The language pair is derived, not assumed.** The inherited skill was written for one direction299 (`en` → `de`) and for MII KDS modules specifically, which made a `fhir-` prefix on its name an300 overclaim. It now reads `i18n-default-lang` and `i18n-lang` from the guide being worked on, the301 script requires an explicit language argument instead of defaulting to `de`, and the mechanics are302 stated for any language pair. That is what earns the prefix.303304Revised on 2026-08-01 after the skill's first real-task exercise (a dry run against305`kerndatensatz-dokument`): `--validate` findings now exit 1 and an empty translation set is306reported explicitly (and fails under `--strict`) instead of an unconditional exit 0; the script307cross-checks `<lang>` against the guide's i18n parameters with best-effort warnings; the harvest308config's `page_map` example now covers non-English source page names; and the `allowed-tools`309grant covers executing the bundled bash script. The dry-run findings live in the310`mii-kds-dokument-ig-inoffiziell` sandbox under `docs/reports/dry-run-2026-07-31/`.311312Revised on 2026-08-05 to retire a documented false claim: that the publisher cannot localize the313titles of `pages:`-tree pages and that an `ImplementationGuide-<id>.po` is ignored. Evidence, and314nothing beyond it:315316- **Our own build on our own pin (IG Publisher 2.2.11)** — the migrated MII KDS Dokument guide with317 the template's breadcrumb override deleted and 23 `page.title` units supplied: `/de/` breadcrumbs318 German including the root label "Inhaltsverzeichnis", the TOC page body German, the browser319 `<title>` German, and `temp/pages/_data/pages.json` `titlelang` differing en/de for all 23 pages320 (zero before). Build health unchanged (sushi 0 errors, QA err=7 = the established baseline). The321 left-hand nav, `IG.description`, `IG.publisher`, `IG.name` and artifact names were **not** observed322 on 2.2.11 and are not claimed for it.323- **HL7's reference IG `FHIR/multi-lang-test-ig`**, live build produced by publisher **2.0.13** —324 localized `es`/`nl` breadcrumbs, a translated IG title in the `/es/` header, translated artifact325 names on `/es/artifacts.html`, and `fr` (in `i18n-lang`, absent from `translation-sources`) as a326 controlled negative. A different publisher version from ours; kept separable in the tables.327- **Prior art in our own organisation:** both MII template repos already carry a correct catalogue on328 their `dev` branch, and `ig-template-mii-kds` records the same mechanism in the MII's own329 `kerndatensatz-basis` module, "verified 2026-07-30".330331Consequences here: the rendering table attributes every row to the build that supports it and marks332the unobserved fields unverified; the breadcrumb override is recorded as shipping in **exactly one333release, template v0.5.0** (never v0.4.0 or earlier, never on `dev`), as a misdiagnosis now being334retired; a Procedure step for the IG-level catalogue was added, pointing at the generator bundled335with `mii-ig-migration`; the CodeSystem `concept.display`/`definition` conflict between this file and336`references/translate-spec.md` is flagged as unresolved instead of silently decided; and337`scripts/ig-translate.sh` no longer WARNs on (nor fails) an `ImplementationGuide-<id>.po` — the very338file the procedure now mandates — while genuinely unsupported types still do.339340Original licence: CC-BY-4.0, as declared by both source repositories. `scripts/` is Apache-2.0,341matching this repository's code licence.