Found while measuring the i18n coverage acceptance of #9 (card 14). The card asked for "100 % label coverage for both locales (the i18n skill's coverage report)"; the report cannot say 100 % for this app, for two reasons that are the tool's, not the bundle's. Recorded here so the number in the release PR is not misread later.
1. The metadata-form baseline is counted against the app
cli 17.3.0, npx objectstack i18n check on claude/issue-9-release with every app-owned key translated:
Coverage by locale
en ████████████████████████ 100.0% (1265/1265, missing 0)
zh-CN █████████░░░░░░░░░░░░░░░ 38.9% (492/1265, missing 773)
All 773 missing keys are metadataForms.* — the Studio's own metadata forms (object, field, view, page, agent, email_template, …), 27 types. Three facts say those are the platform's to translate, not this app's:
os lint hides them by design: platform built-ins: 773 i18n issue(s) hidden — rerun with --include-platform to audit them, and the flag's help text says "the platform packages ship those translations".
os i18n extract has --no-metadata-forms for the same reason ("that baseline belongs to one package, not every plugin").
- The runtime already serves them:
GET /api/v1/i18n/translations/zh-CN on this app carries a metadataForms group with 27 types although the app's bundle ships none — they come from @objectstack/platform-objects (its zh-CN.metadata-forms.generated.ts).
os i18n check has no equivalent switch, so --strict and --threshold are unusable as a CI gate for an app package: they would fail on strings the app does not own and must not duplicate (an app-shipped copy would override the platform's and go stale on every upgrade).
2. The expected-key list double-counts object actions
The check's expected list for this app has 492 app-owned entries but only 482 distinct keys: the ten objects.*._actions.* keys (ats_convert_inquiry label/description/confirmText/successMessage, ats_reject_inquiry label/successMessage, ats_spam_inquiry label/successMessage, ats_public_apply_link label/description) each appear twice in the issue list and in expectedKeys. The bundle therefore reads "492 translated" with 482 keys, and the extractor (os i18n extract) emits 382 object keys where the check expects 392.
What #9 does instead
pnpm lint is objectstack lint --i18n-strict: it fails on any app-owned key that lacks a locale in supportedLocales, hides the platform bucket, and is one of the three gates CI runs. The raw i18n check number is reported in the PR as measured (38.9 % raw, 100 % of the app-owned surface) rather than rounded up.
Where it lands
Upstream, in the CLI (packages/cli/src/utils/i18n-coverage.ts and the i18n check command): a --no-metadata-forms / hide-platform default matching lint, and de-duplication of the action walk. Filed here as the local record; the maintainer may mirror it upstream.
Found while measuring the i18n coverage acceptance of #9 (card 14). The card asked for "100 % label coverage for both locales (the i18n skill's coverage report)"; the report cannot say 100 % for this app, for two reasons that are the tool's, not the bundle's. Recorded here so the number in the release PR is not misread later.
1. The metadata-form baseline is counted against the app
cli 17.3.0,
npx objectstack i18n checkonclaude/issue-9-releasewith every app-owned key translated:All 773 missing keys are
metadataForms.*— the Studio's own metadata forms (object,field,view,page,agent,email_template, …), 27 types. Three facts say those are the platform's to translate, not this app's:os linthides them by design:platform built-ins: 773 i18n issue(s) hidden — rerun with --include-platform to audit them, and the flag's help text says "the platform packages ship those translations".os i18n extracthas--no-metadata-formsfor the same reason ("that baseline belongs to one package, not every plugin").GET /api/v1/i18n/translations/zh-CNon this app carries ametadataFormsgroup with 27 types although the app's bundle ships none — they come from@objectstack/platform-objects(itszh-CN.metadata-forms.generated.ts).os i18n checkhas no equivalent switch, so--strictand--thresholdare unusable as a CI gate for an app package: they would fail on strings the app does not own and must not duplicate (an app-shipped copy would override the platform's and go stale on every upgrade).2. The expected-key list double-counts object actions
The check's expected list for this app has 492 app-owned entries but only 482 distinct keys: the ten
objects.*._actions.*keys (ats_convert_inquirylabel/description/confirmText/successMessage,ats_reject_inquirylabel/successMessage,ats_spam_inquirylabel/successMessage,ats_public_apply_linklabel/description) each appear twice in the issue list and inexpectedKeys. The bundle therefore reads "492 translated" with 482 keys, and the extractor (os i18n extract) emits 382 object keys where the check expects 392.What #9 does instead
pnpm lintisobjectstack lint --i18n-strict: it fails on any app-owned key that lacks a locale insupportedLocales, hides the platform bucket, and is one of the three gates CI runs. The rawi18n checknumber is reported in the PR as measured (38.9 % raw, 100 % of the app-owned surface) rather than rounded up.Where it lands
Upstream, in the CLI (
packages/cli/src/utils/i18n-coverage.tsand thei18n checkcommand): a--no-metadata-forms/ hide-platform default matchinglint, and de-duplication of the action walk. Filed here as the local record; the maintainer may mirror it upstream.