Skip to content

Single-source i18n: raco rivet i18n with parity check and embedded host tables - #275

Merged
turinglambdaai merged 1 commit into
mainfrom
feat/i18n-loader
Oct 16, 2026
Merged

turinglambdaai merged 1 commit into
mainfrom
feat/i18n-loader

Conversation

@turinglambdaai

Copy link
Copy Markdown
Owner

Closes #242.

What

Hosts re-solved user-visible string loading three times, and the third copy was diverging: taskly had the full pattern (shared/i18n JSON → staged res/i18n → per-host loaders → embedded fallbacks → CI parity check), syncpilot built a private generator for the same idea, and fulcrum's Windows/Linux hosts hardcoded English inline while mac read the shared source. The single source silently rotted.

raco rivet i18n verifies the parity contract of shared/i18n/{zh,en}.json (flat string -> string, identical key sets — violations name the file and key) and regenerates the embedded host tables for every host template the project keeps:

  • windows/GeneratedI18n.hpp, linux/GeneratedI18n.hpp — rivet_app::I18nEntry table
  • macos-host/Sources/RivetHost/GeneratedI18n.swift — GeneratedI18n table + tr function

The host templates ship the matching loader: rivet_app::i18n::tr(language, key) consults the active language, then the other language, then the key — byte-for-byte the same lookup order as the generated Swift tr, so a key can never exist in one host's fallback and not another's.

Escaping: C++ encodes non-ASCII as three-digit UTF-8 octal escapes (fixed width — consecutive escapes cannot run together, and no \xNNNN out-of-range char escape, which clang rejects); Swift keeps UTF-8 source literal and escapes only quotes, backslashes, and C0 controls.

Release gate: doctor --release gains a sixth check — projects with a shared/i18n source gate its parity and the freshness of the embedded tables (same plan/writer split as generate-clients!, so gate and writer cannot disagree); projects without one skip it.

Docs: docs/configuration.md documents the single source, the loader lookup order, and the staging relationship.

Verification

  • 21 tests in rivet/tests/i18n.rkt: parity violations (drifted keys, nested values, missing tables), generated-content assertions, the freshness gate (stale/missing/repaired), and — the load-bearing one — the generated C++ table compiled with the shipped loader and tr()'s lookup order asserted against real UTF-8 values.
  • Full suite: 9885 tests pass with the worktree collection precedence.

Every host re-solved user-visible string loading, and the third copy was
already diverging: taskly staged shared/i18n JSON with per-host fallback
tables and a CI parity check, syncpilot built a private generator for the
same idea, and fulcrum's Windows/Linux hosts hardcoded English inline
while mac read the shared source — the single source silently rotted.

raco rivet i18n closes that class (issue #242): it verifies the parity
contract of shared/i18n/{zh,en}.json (flat string->string maps, identical
key sets, named file and key on violation) and regenerates the embedded
tables for every host template the project keeps — windows/ and linux/
GeneratedI18n.hpp and macos-host GeneratedI18n.swift. C++ literals
encode non-ASCII bytes as three-digit UTF-8 octal escapes (fixed width,
so consecutive escapes cannot run together, and no \xNNNN out-of-range
char escape); Swift keeps UTF-8 source literal and escapes only quotes,
backslashes, and control characters.

The host templates ship the matching loader: rivet_app::i18n::tr
consults the active language, then the other language, then the key —
identical to the generated Swift tr — so a key can never exist in one
host's fallback and not another's. doctor --release gains a sixth check:
projects with a shared/i18n source gate its parity and the freshness of
the embedded tables (the plan/writer split from generate-clients!
means the gate and the writer cannot disagree); projects without one
skip it. docs/configuration.md documents the single source and the
lookup order.

The suite compiles the generated table with the shipped loader and
asserts tr()'s lookup order against real UTF-8 values, covers parity
violations (drifted keys, nested values, missing tables), and the
freshness gate (stale, missing, repaired).
@turinglambdaai
turinglambdaai merged commit 1b0186b into main Oct 16, 2026
23 checks passed
@turinglambdaai
turinglambdaai deleted the feat/i18n-loader branch October 16, 2026 17:13
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.

Host template: i18n single-source loader (staged res/i18n + embedded fallback + parity check)

1 participant