Skip to content

Commit aa6236f

Browse files
committed
docs(skills): correct objectstack-i18n behavioral claims against the implementation
Flight 8 of the published-skills factual sweep (#13658). Every correction is settled against the implementing code plus an executed probe; net -1 line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnE7G31tqbxN1rqpQmzurT
1 parent 6b285ec commit aa6236f

2 files changed

Lines changed: 49 additions & 50 deletions

File tree

‎skills/objectstack-i18n/SKILL.md‎

Lines changed: 47 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ and integration with the I18nService.
4747

4848
1. **Runtime format — `objects.*` (`TranslationData`)**: each locale is authored as one
4949
`TranslationData` value. All translatable content for an object (label, fields,
50-
options, views, sections, actions) is grouped under `objects.{object_name}`, with
51-
global groups (`apps`, `messages`, `globalActions`, `dashboards`, `settings`,
52-
`metadataForms`) at the top level.
50+
options, views, sections, tabs, actions) is grouped under `objects.{object_name}`,
51+
with global groups (`apps`, `messages`, `globalActions`, `dashboards`, `pages`,
52+
`flows`, `settings`, `metadataForms`, `settingsCommon`) at the top level.
5353

5454
2. **Bundle registration**: per-locale files are assembled with
5555
`defineTranslationBundle({ en, 'zh-CN': … })` into a `TranslationBundle`
@@ -152,7 +152,7 @@ i18n/
152152

153153
The canonical authoring path: one `TranslationData` per locale, assembled with
154154
`defineTranslationBundle` and registered on the stack. This mirrors the shipped
155-
example apps (`src/translations/{en,zh-CN}.ts` + `index.ts`):
155+
`examples/app-todo` (`src/translations/{en,zh-CN,ja-JP}.ts` + `index.ts`):
156156

157157
<!-- os:check -->
158158
```typescript
@@ -256,15 +256,16 @@ All translatable content for a single object is aggregated under
256256

257257
| Sub-key | Holds |
258258
|:--------|:------|
259-
| `label` / `pluralLabel` / `description` | Object-level text (`label` is required) |
259+
| `label` / `pluralLabel` / `description` | Object-level text (every key optional) |
260260
| `fields.{field_name}` | `label`, `help`, `placeholder`, `options` (option value → label) per field |
261261
| `_views.{view_name}` | `label`, `description`, `emptyState.title` / `emptyState.message` |
262-
| `_actions.{action_name}` | `label`, `confirmText`, `successMessage`, `params.{param_name}`, `resultDialog` |
263-
| `_sections.{section_name}` | Form section / tab `label`, `description` |
262+
| `_actions.{action_name}` | `label`, `description`, `confirmText`, `successMessage`, `params.{param_name}`, `resultDialog` |
263+
| `_sections.{section_name}` | Form section `label`, `description` |
264+
| `_tabs.{tab_name}` | Filter-preset tab `label`, keyed by `ViewTabSchema.name` |
264265

265266
Top-level groups alongside `objects`: `apps` (label, description, navigation),
266-
`messages`, `globalActions` (object-less actions), `dashboards`, `settings`,
267-
`metadataForms`, `settingsCommon`.
267+
`messages`, `globalActions` (object-less actions), `dashboards`, `pages`, `flows`,
268+
`settings`, `metadataForms`, `settingsCommon`.
268269

269270
> **Validation messages are not a translation group.** `validationMessages` was
270271
> removed in spec 17.0.0 — nothing ever read it, so a translated rule
@@ -297,10 +298,10 @@ parse, ship, and resolve to nothing.
297298

298299
`os validate` / `os lint` / `os compile` check this direction and report it as
299300
warnings (`translation-target-unknown`, `translation-option-key-unknown`): a key
300-
naming an object, field, view, action, param, section, app, nav item, dashboard
301-
or widget that does not exist is listed alongside the names that do. A bundle
302-
keyed to something since renamed still parses — the label just renders silently
303-
in its source locale while every neighbouring label resolves.
301+
naming an object, field, view, action, param, section, app, nav item, dashboard,
302+
widget, flow or flow-screen field that does not exist is listed alongside the
303+
names that do. A bundle keyed to something since renamed still parses — the label
304+
just renders silently in its source locale while every neighbouring one resolves.
304305

305306
---
306307

@@ -342,9 +343,9 @@ export default defineTranslation({
342343
Rules that differ from a file bundle:
343344

344345
- **`locale` is required.** A file bundle names its locales as map keys; an item
345-
carries its own. An item whose locale cannot be resolved is skipped by the
346-
runtime sync — a silent skip, which is why the field is mandatory rather
347-
than inferred from the item name.
346+
carries its own. The runtime sync falls back to the item *name* when that looks
347+
like a BCP-47 tag, then skips the item with an `[i18n] … — skipped` warning
348+
naming the row — a server log line the author never sees. Always set `locale`.
348349
- **One locale per item.** Author `zh-CN` and `ja-JP` as two items.
349350
- Published items are loaded at boot and on every publish (no restart), and
350351
layer **over** the file bundles — an authored value wins over a shipped one
@@ -358,10 +359,8 @@ Exact Zod shape: `node_modules/@objectstack/spec/src/system/translation.zod.ts`
358359
A second object-first shape keyed on `o.{object_name}` (with `app`, `nav`,
359360
`dashboard`, `reports`, `notifications`, `errors`, `_globalOptions`, `_meta`,
360361
`namespace`, and `_actions.confirmMessage`) was once documented for
361-
Studio-authored translations. **No resolver ever read it**, so items authored
362-
that way saved successfully and rendered nothing. It was removed —
363-
those keys are now rejected at save time with a message naming the group to
364-
use instead. Never author them, in files or at runtime.
362+
Studio-authored translations. **No resolver ever read it.** Both doors now reject
363+
it — files as well as items — each key carrying its own replacement guidance.
365364

366365
---
367366

@@ -411,11 +410,12 @@ os i18n check --locales=zh-CN # scope to specific locales
411410
os i18n check --strict --threshold=95 # CI gate: locale parity + minimum coverage
412411
```
413412

414-
It compares registered bundles against source metadata and reports missing
415-
object/field/option/view/action keys per locale. Missing keys in the default
413+
It compares registered bundles against source metadata and reports missing keys
414+
per locale across every declared surface: objects (fields, options, views,
415+
sections, tabs, actions, params), global actions, apps and navigation, dashboards
416+
and widgets, pages, flow screens, metadata forms. Missing keys in the default
416417
locale are errors; `--strict` promotes non-default gaps to errors and
417-
`--show-keys` lists every missing key. `os lint --i18n-strict` folds the same
418-
gate into linting.
418+
`--show-keys` lists every missing key. `os lint --i18n-strict` folds it into lint.
419419

420420
### `os i18n extract --check` — freshness, not coverage
421421

@@ -434,10 +434,9 @@ missing file and printing the regenerate command.
434434
**Use both gates — they answer different questions.** `os i18n check` asks *are
435435
the strings translated?* (coverage: human work). `extract --check` asks *are the
436436
generated bundles still what the schema produces?* (freshness: machine output).
437-
Renaming a label, adding an object, or removing a spec key leaves coverage at
438-
100% while the bundles quietly go stale — which is exactly how the platform's
439-
own bundles ended up carrying translations for keys the schema had already
440-
deleted, plus fields with no entry in any locale.
437+
Renaming a label or removing a spec key leaves coverage at 100% while the
438+
bundles go stale — which is how the platform's own bundles ended up carrying
439+
translations for keys the schema had deleted, plus fields with no entry anywhere.
441440

442441
It runs in the same **merge mode** as a normal extract, so it never asks for
443442
re-translation: an up-to-date bundle re-extracts byte-identically. Requires
@@ -448,8 +447,8 @@ re-translation: an up-to-date bundle re-extracts byte-identically. Requires
448447
The spec models coverage results for tooling: `TranslationCoverageResult`
449448
(totals, `coveragePercent`, per-group `breakdown`) and `TranslationDiffItem` —
450449
`key` (dot path), `status` (`missing | redundant | stale`), `locale`, optional
451-
`sourceHash` for stale detection, and AI-enrichment fields (`aiSuggested`,
452-
`aiConfidence`). Full Zod shape:
450+
`objectName`, optional `sourceHash` for stale detection, and AI-enrichment
451+
fields (`aiSuggested`, `aiConfidence`). Full Zod shape:
453452
`node_modules/@objectstack/spec/src/system/translation.zod.ts` —
454453
`TranslationCoverageResultSchema`, `TranslationDiffItemSchema`.
455454

@@ -498,12 +497,13 @@ registers when no i18n plugin is present):
498497
- **`getTranslations(locale)`** — full snapshot for a locale
499498
- **`loadTranslations(locale, data)`** — programmatic load; deep-merges, so multiple
500499
plugins can each contribute their own `objects.*` slice
501-
- **`getLocales()`** / **`getDefaultLocale()`** / **`setDefaultLocale()`**
500+
- **`getLocales()`** / **`setSupportedLocales()`** (narrows `getLocales` to the app's
501+
declared `i18n.supportedLocales`) / **`getDefaultLocale()`** / **`setDefaultLocale()`**
502502

503503
The in-memory fallback additionally resolves locale codes
504504
(exact → case-insensitive → base language `zh-CN` → `zh` → variant `zh` → `zh-CN`).
505505

506-
The contract also declares optional methods — `getCoverage`,
506+
The contract also declares optional methods — `getFieldLabels`, `getCoverage`,
507507
`suggestTranslations` — that **no shipped implementation provides**. Treat them
508508
as extension points for a custom workbench or TMS adapter. (`getAppBundle` /
509509
`loadAppBundle` were removed along with the `o.*` shape they returned.)
@@ -545,10 +545,12 @@ Scaffold ready-to-edit translation files from your stack config:
545545
os i18n extract --locales=zh-CN --out=./src/translations
546546
```
547547

548-
This writes `<locale>.objects.generated.ts` TypeScript modules (not JSON) — the
549-
default locale is filled from schema labels, other locales follow `--fill`
550-
(`empty | default | todo`). Other flags: `--default-locale`, `--filter` (regex
551-
over object/app names or key paths), `--dry-run`, `--json`.
548+
This writes `<locale>.objects.generated.ts` TypeScript modules (not JSON), plus
549+
`<locale>.metadata-forms.generated.ts` unless `--no-metadata-forms` — the default
550+
locale is filled from schema labels, other locales follow `--fill`
551+
(`empty | default | todo`). Other flags: `--default-locale`, `--filter` (regex over
552+
object/app names or key paths), `--no-merge`, `--no-objects-only`,
553+
`--source-hashes`, `--dry-run`, `--json`.
552554

553555
### 2. Translate
554556

@@ -572,18 +574,17 @@ Commit the translation files, import them into your bundle, and register it via
572574

573575
## CRM I18n Blueprint
574576

575-
Reference implementation shape:
576-
577-
- Bundle entry: `src/translations/index.ts` (or `crm.translation.ts`)
578-
- Locale files: `src/translations/{en,zh-CN,ja-JP,es-ES}.ts`
577+
The shipped `examples/app-crm` is the bundled layout: one
578+
`src/translations/crm.translation.ts` holding `en` + `zh-CN`. `examples/app-todo`
579+
is the per-locale layout — `src/translations/{en,zh-CN,ja-JP}.ts` + `index.ts`.
579580

580581
Use this structure for metadata apps:
581582

582583
| Layer | CRM Pattern |
583584
|:--|:--|
584-
| Stack config | `i18n` with an explicit locale list; per-locale source files by convention |
585-
| Translation assembly | One `defineTranslationBundle` call that imports per-locale files |
586-
| Locale content | Object-scoped translations (`objects.account.fields.*`, `_views`, `_actions`) + global app/messages |
585+
| Stack config | `i18n` with an explicit locale list; the source layout is a convention |
586+
| Translation assembly | One `defineTranslationBundle` call — inline locales, or importing per-locale files |
587+
| Locale content | Object-scoped translations (`objects.crm_account.fields.*`, `_views`, `_actions`) + global app/messages |
587588
| Naming integrity | Translation object/field keys exactly match metadata machine names |
588589

589590
For new locales, copy one locale file as a baseline, then run `os i18n check`
@@ -595,10 +596,8 @@ before release.
595596

596597
### ❌ The Retired `o.*` Shape
597598

598-
Everything reads `objects.*`. The `o.*` dialect was removed — it is
599-
not a "Studio format", not a secondary format, just gone. Files registered in
600-
that shape resolve to nothing; runtime items in that shape are rejected at
601-
save time.
599+
Everything reads `objects.*`. The `o.*` dialect was removed — not a "Studio
600+
format", not a secondary format, just gone. Both doors reject it, files included.
602601

603602
```typescript
604603
// WRONG — in a file bundle AND in a `translation` item
@@ -647,7 +646,7 @@ options: { in_progress: '进行中' }
647646

648647
### ❌ Ignoring Coverage Reports
649648

650-
Stale translations can cause confusion. Always run `os i18n check` before releases.
649+
Run `os i18n check` before releases; `extract --check` is what sees staleness.
651650

652651
---
653652

‎skills/objectstack-i18n/evals/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ When implemented, evals will follow this structure:
1313
```
1414
evals/
1515
├── bundle-shape/
16-
│ ├── test-objects-vs-o-keys.md # runtime `objects.*` vs secondary `o.*` format
16+
│ ├── test-objects-vs-o-keys.md # runtime `objects.*`; retired `o.*` is rejected
1717
│ ├── test-snake-case-keys.md # object/field keys match metadata machine names
1818
│ └── test-option-machine-values.md # lowercase option values, not display labels
1919
├── interpolation/
20-
│ └── test-double-brace-params.md # {{userName}}, not {userName}; ICU is experimental
20+
│ └── test-double-brace-params.md # {{userName}}, not {userName}; no ICU engine
2121
├── coverage-workflow/
2222
│ ├── test-extract-command.md # os i18n extract --locales/--out flags & TS output
2323
│ └── test-check-command.md # os i18n check --strict/--threshold CI gate

0 commit comments

Comments
 (0)