Skip to content

Localization sync — EN+FR deep-dive

This is the deep-dive into how Edoxen keeps English and French renderings of the same Resolution aligned inside a single YAML file. For the field reference, see Localization. For the why of the localizations[] pattern, see Multilingual support.

The shape of a Resolution

Every Resolution carries its language-agnostic admin fields exactly once, then a localizations[] array with one entry per language. The EN and FR entries share the same identifier, the same dates, the same DOI — only the per-language content differs.

Resolutionidentifier: CIML/2004/1doi: 10.63493/...urn: urn:oiml:...dates: [...decision, ...meeting]Localization — eng / Latntitle: "Approval of the agenda"actions: - type: approves message: "Approves the agenda..."Localization — fra / Latntitle: "Approbation de l'ordre"actions: - type: approves message: "Approuve l'ordre du jour..."

Read this as: the parent Resolution owns the admin fields. localizations[] is its children, one per language. EN and FR are siblings — neither knows the other; both know the parent.

Why one file, not two

The natural alternative is one file per language (resolutions.en.yaml, resolutions.fr.yaml) and a join key (CIML/2004/1). Edoxen rejects that because EN+FR pairs drift invisibly the moment a translator edits one file but not the other. One single file, with both languages nested, means a git diff and git blame show exactly the EN line and the FR line that changed together.

resolutions/ciml-39-decisions.yaml · single file, both languagesmetadata: { title, dates, venue, city, country_code, source_urls: [...] }resolutions: [ { identifier, doi, urn, dates, localizations: [...] } ]language_code: eng- title: "Approval of the agenda for the 39th CIML Meeting"language_code: fra- title: "Approbation de l'ordre du jour de la 39e réunion du CIML"

Result: translators see EN and FR side by side in any editor. A pull request that updates both blocks sits as one diff. A pull request that updates only one block is visibly suspicious — easy to spot in review.

Validation pipeline

Every .yaml file flows through the same three-stage pipeline. Neither translation drift, nor schema typos, nor model mis-attribution survives all three.

1. ParsePsych.safe_load→ Ruby Hash*.yaml on disk2. DecodeResolutionSet.from_yaml(hash)lutaml-model3. ValidateJSONSchemer.schema(schema/edoxen.yaml)SchemaPass | SchemaError[]
  • Stage 1 confirms the file is well-formed YAML — no tabs-as-indent, no duplicated keys.
  • Stage 2 walks the hash into ResolutionSet / Resolution / Localization via lutaml-model's declared attributes. Anything lutaml can't bind raises an UnknownAttribute or a KeyError.
  • Stage 3 runs the JSON Schema. language_code must match ^[a-z]{3}$, script must match ^[A-Z][a-z]{3}$, Action.type must be in the enum, additionalProperties: false on every object catches typos at the wire level.

The CLI runs all three:

sh
edoxen validate resolutions/*.yaml

Translator workflow, end-to-end

TranslatorRepoCI (edoxen validate)Docs siteEN + FRedit (1 PR)open PRparse + schemalist errorsmergesite rebuild

The translator never has to know which file holds which language. Both languages live in the same file; one PR carries both; one CI run checks both.

Where to next

An open source project of Ribose