Skip to content

Add i18n-report CLI tool for translation maintenance#10107

Draft
jandubois wants to merge 17 commits intorancher-sandbox:mainfrom
jandubois:i18n-report-tool
Draft

Add i18n-report CLI tool for translation maintenance#10107
jandubois wants to merge 17 commits intorancher-sandbox:mainfrom
jandubois:i18n-report-tool

Conversation

@jandubois
Copy link
Copy Markdown
Member

Summary

Add i18n-report, a Go CLI tool for maintaining Rancher Desktop's translation files. This is the first PR in a series that adds i18n support to Rancher Desktop; it introduces only the tooling, with no runtime changes.

The tool scans source code and YAML locale files to detect translation issues and automate the translation workflow:

  • Reporting: unused, missing, stale, untranslated, references, dynamic

  • Translation pipeline: translate (generate prompts for translators), merge (import translations back into locale YAML files)

  • Validation: validate (placeholder parity, ICU structure, HTML tag preservation), check (policy-aware CI gate for experimental and shipping locales)

  • Maintenance: drift (detect keys whose English source changed since last translation), meta (generate/regenerate source-text metadata), manifest (cross-validate locale registrations against command-api.yaml and settingsValidator.ts), remove (bulk key removal)

Design decisions

AST-level YAML manipulation. The merge command operates on gopkg.in/yaml.v3 node trees rather than marshaling through Go structs. This preserves translator comments, @override annotations, and whitespace through merge operations.

Three input formats for merge. The tool accepts JSONL (raw agent conversation logs), markdown with ```yaml fences, and plain key=value text. Auto-detection examines the first non-blank line.

Source-text metadata. Each translated locale has a meta/<locale>.yaml file recording the English source text at the time of translation. The drift command compares current English text against stored metadata to identify keys that need retranslation.

Policy-aware checking. The check command enforces different standards for experimental (stale keys, structural validity) and shipping (all of the above plus complete coverage and no drifted keys) locales.

@jandubois jandubois force-pushed the i18n-report-tool branch 2 times, most recently from 8e9ecdb to 757b915 Compare April 8, 2026 05:48
jandubois added 17 commits April 7, 2026 23:07
Add go.work entry, .gitignore entry for the binary, and go.mod/go.sum.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Split the monolithic main.go into files by concern:
- main.go: subcommand dispatch
- repo.go: repository root detection, path helpers
- yaml.go: YAML flatten/unflatten, scalar formatting, nested YAML writer
- scan.go: source file scanning, key reference detection
- output.go: shared text/JSON output formatter
- report_*.go: one file per subcommand

CLI uses positional subcommands instead of --report=<type>.
Each subcommand has its own flag.FlagSet with scoped flags.
The --locale flag has no default (required where needed).

New subcommand: "check" runs unused + stale + translate as a combined
lint check with pass/fail output and exit code.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Table-driven tests for:
- yaml.go: flattenYAML, yamlScalar, stripYAMLQuotes, isValidDottedKey,
  writeNestedYAML
- scan.go: key reference regex patterns, dotted key literals, indirect
  key patterns
- report_remove.go: key removal, stdin filtering, empty parent pruning
- report_merge.go: parseMergeInput (key=value, key: value, @Reason
  comments, blank lines, mixed formats), extractTranslationText (JSONL,
  markdown fences, raw text)
- report_untranslated.go: attribute patterns, skip patterns, title case,
  HTML text, bare text, bound literals, error push patterns

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Document all subcommands, flags, common workflows, scanning
heuristics, merge pipeline, and file layout.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
The merge command loaded existing locale files with yaml.Unmarshal,
which discards all YAML comments. Existing @Reason annotations were
silently destroyed on every re-merge.

Switch to yaml.Node-based loading (loadYAMLWithComments) that extracts
HeadComment from each leaf key node. Existing entries that aren't
overridden by new input now keep their comments.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
The translate report now surfaces @context, @meaning, and @no-translate
comments from en-us.yaml alongside each key=value pair. This gives
translators inline context without requiring them to cross-reference
the source file manually.

Both text and JSON output formats include the annotations.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Preview additions and overwrites without modifying the locale file.
Also report overwritten key count in normal merge output.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Introduce locales.yaml to declare all supported locales with their
status (source, experimental, shipping). The manifest loader validates
uniqueness, exactly one source locale, and existence of translation
files for each non-source locale.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Replace the flatten-and-rebuild merge strategy with direct yaml.Node
tree manipulation. The new approach parses the existing locale file
into a node tree, updates or inserts leaf nodes by dotted key path,
and serializes the tree back — preserving all existing leaf comments
(HeadComment, LineComment) verbatim.

Round-tripping an unchanged file now produces zero diff.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Detect @OverRide in leaf key comments and expose it via the override
field on mergeEntry and through nodeHasOverride(). Validate that
@OverRide appears only on leaf keys, not on parent mapping nodes.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Merge now accepts --mode (normal, drift, improve) to control how
@OverRide keys are handled:
- normal: write all entries unconditionally
- drift: write all entries, warn when overwriting @OverRide keys
- improve: skip @OverRide keys unless --include-overrides is set

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Store the English source string for each translated key so the drift
command can detect when the source text changed after translation.
This enables showing what changed, not just that it changed.

Add the meta subcommand to generate metadata for existing locales.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Detect translated keys whose English source text has changed since
the translation was last merged, by comparing the current en-us value
against the stored source in the metadata file.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Run structural checks on a locale file: placeholder parity (with
depth-aware parsing that handles ICU plural/select), ICU structure
parity (variable, keyword, and branch labels must match), HTML tag
parity, manifest consistency, @OverRide placement validity, and
metadata coherence. All three existing locales pass with zero false
positives.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Extend the translate command with --mode to control which keys are
output for translation: missing (absent from locale, the default),
improve (already translated, skip @OverRide and drifted keys unless
--include-overrides), and drift (English source changed since last
merge, via metadata).

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Extend `check` with `--policy=experimental|shipping`. Experimental
requires: valid manifest, locale present, no stale keys, validation
passing, and coherent metadata. Shipping adds: no missing keys and
no drifted keys. The original check behavior (without --policy) is
unchanged.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
The `manifest --cross-validate` flag checks all three registration-drift
surfaces: the locale enum in command-api.yaml must match the manifest,
settingsValidator.ts must use dynamic locale discovery via
availableLocales, and settingsValidator.spec.ts test values must exist
in the manifest.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant