You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(i18n): one translation shape — the translation type speaks objects., not o. (#3778) (#3808)
A translation authored in the product saved successfully and rendered nothing.
Not a resolver gap — a contract split. The `translation` metadata type
(`allowRuntimeCreate: true`) was registered against `AppTranslationBundleSchema`,
an object-first shape keyed on `o.<object>`. Every resolver, `os i18n extract`,
`os i18n check`, the objectui hooks, and all nine shipped bundles read
`objects.<object>`. Nothing bridged them, so the save path and the read path
never met.
A converter was the obvious fix and the wrong one: throwaway code that would
start producing *working* `o.`-shaped rows, closing the migration-free window
that exists precisely because the feature never functioned. The retired shape's
real-world footprint was zero — all three `*.translation.ts` files in the tree
were already `objects.`-shaped, contradicting the type's own registered schema.
Converging is a registration fix, not a migration.
BREAKING: `AppTranslationBundleSchema`, `ObjectTranslationNodeSchema` and their
types are deleted with no deprecation cycle — nothing worked end-to-end through
them, so there is no consumer to protect, and a deprecated-but-present schema is
exactly the exemplar an agent copies into new code. `II18nService.getAppBundle` /
`loadAppBundle` go with them (zero implementers — a capability the runtime never
delivered).
`TranslationItemSchema` replaces them: one locale of the same `TranslationData`
groups a file bundle uses, plus the `locale` it translates, with a
`defineTranslation()` factory. Three details are deliberate, all aimed at making
the failure loud instead of silent:
- `locale` is required, not inferred from the item name. The sync skips an item
whose locale it cannot resolve, and a skip is invisible to whoever authored it.
- Retired keys are rejected, not stripped. Zod drops undeclared keys silently,
which would reproduce this bug exactly. A pre-parse guard turns that silence
into a 422 naming the right group, and runs ahead of the parse so the keys stay
out of the schema — the generated JSON Schema and the Studio editor never
advertise a shape that cannot work.
- `ObjectTranslationData.label` becomes optional. Partial translation is the
normal state; requiring it forced authors to restate the source label just to
validate, filling bundles with fake translations that mask coverage gaps.
Also here: the authored sync warns (naming the row and the fix) on a row still in
the retired shape instead of loading it into nowhere, and no longer merges
publish bookkeeping into the translation layer. `GET /i18n/labels/:object/:locale`
reads the nested field data it is actually given — it scanned for flat dotted
`o.<obj>.fields.<field>` keys, a third dialect no producer ever wrote, so it
always returned `{}`. A `translation` create seed makes the Studio create flow
round-trip. Docs, the i18n skill, and the sweep-test fixture no longer teach the
retired shape.
Claude-Session: https://claude.ai/code/session_017wPvotUdM6WFtJm1KKryLE
Co-authored-by: Claude <noreply@anthropic.com>
const result =ActionResultDialogTranslation.parse(data);
@@ -39,31 +39,6 @@ Translations for an action result dialog
39
39
|**fields**|`Record<string, string>`| optional | Result field labels keyed by the literal field path declared in the action metadata (keys may contain dots) |
40
40
41
41
42
-
---
43
-
44
-
## AppTranslationBundle
45
-
46
-
Object-first application translation bundle for a single locale
0 commit comments