From 9620486d10051794636f8f10e66cc1c4f7e2ef21 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 00:57:42 +0000 Subject: [PATCH] docs(spec): stop the translation @example blocks teaching an unresolvable messages id `messages` is a flat `Record` while `t()` resolves a key by walking its dot path, so an id that merely contains a dot is unreachable. Both docblock `@example` blocks on `TranslationDataSchema` and `TranslationItemSchema` demonstrated exactly such an id (`'common.save'`); they now author `commonSave` and state the rule. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude --- ...slation-messages-example-single-segment.md | 19 +++++++++++++++++++ packages/spec/src/system/translation.zod.ts | 15 +++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 .changeset/18190-translation-messages-example-single-segment.md diff --git a/.changeset/18190-translation-messages-example-single-segment.md b/.changeset/18190-translation-messages-example-single-segment.md new file mode 100644 index 00000000000..a8152152c1f --- /dev/null +++ b/.changeset/18190-translation-messages-example-single-segment.md @@ -0,0 +1,19 @@ +--- +'@objectstack/spec': patch +--- + +docs(translation): the two `TranslationData` / `TranslationItem` `@example` blocks stop teaching a `messages` id that cannot resolve (#18190) + +`messages` is declared a flat `Record` (`translation.zod.ts` — `messages: z.record(z.string(), z.string())`), while `t()` resolves a key by walking its dot path segment by segment. Both implementations do this, identically: + +- `packages/core/src/fallbacks/memory-i18n.ts` — `resolveKey()`, `key.split('.')`, walked by `t()`; +- `packages/services/service-i18n/src/file-i18n-adapter.ts` — a second `resolveKey()` with the same body, walked by `t()` through `resolveFromLocale()`. + +So an id that merely *contains* a dot is one flat key named `common.save`, and `t('messages.common.save', …)` looks for a nested `common` object, finds a string or nothing at the first hop, and returns the key itself. Both docblock `@example` blocks on this schema demonstrated exactly that id — the doorway an author (or an authoring agent) copies from. + +- The JSON example on `TranslationDataSchema` and the TypeScript example on `TranslationItemSchema` now author `commonSave`, the single-segment spelling `content/docs/protocol/kernel/i18n-standard.mdx` already prescribes and `packages/plugins/plugin-audit/src/translations/messages.ts` already applies to its own bundle. +- Both docblocks now state the rule, so the counter-example is named as one rather than demonstrated. + +⚠️ **The schema still accepts a dotted id** — nothing is narrowed here and no key is retired. Whether the door should refuse a dotted `messages` key narrows a published accept set and rides its own card; this change is the doorway half only. + +For authors: a `messages` id containing a dot never resolved, so re-spelling one single-segment (`'common.save'` → `commonSave`, looked up as `messages.commonSave`) turns a key that was returning itself into one that translates. No key that resolved before stops resolving. diff --git a/packages/spec/src/system/translation.zod.ts b/packages/spec/src/system/translation.zod.ts index a6bea0239b0..a0ae44a6894 100644 --- a/packages/spec/src/system/translation.zod.ts +++ b/packages/spec/src/system/translation.zod.ts @@ -574,9 +574,16 @@ const TRANSLATION_KEY_GUIDANCE: Record`, + * so an id that itself contains a dot (`'common.save'`) is looked up as a + * nested `common` object and resolves to nothing; `messages.commonSave` + * resolves. Both implementations walk identically — see + * `content/docs/protocol/kernel/i18n-standard.mdx`. */ /** * The translation groups, as a shape rather than a schema. @@ -1414,6 +1421,10 @@ export type TranslationConfig = z.input; * sync skips an item whose locale it cannot resolve, and a skip is invisible * to whoever — or whatever — authored it. * + * `messages` ids are single-segment, for the reason spelled out on + * {@link TranslationDataSchema}: `t()` walks the dot path, so an id containing + * a dot resolves to nothing. + * * @example * ```typescript * const zhCN = defineTranslation({ @@ -1427,7 +1438,7 @@ export type TranslationConfig = z.input; * }, * }, * apps: { crm: { label: '客户关系管理' } }, - * messages: { 'common.save': '保存' }, + * messages: { commonSave: '保存' }, * }); * ``` */