From b1cf72cca7610526a9382d39f5d8a60739a5196f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 21:28:05 +0000 Subject: [PATCH 1/2] refactor(i18n): retire the dead `appDesigner.fieldDesigner.formula` key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit objectui#6043 retired the Field Designer's formula-expression textarea, the key's only call site. The value outlived it in eleven places: the ten locale packs and `DESIGNER_DEFAULT_TRANSLATIONS`. Re-measured on this branch rather than inherited from the card: zero `t()`/ `tt()` call sites, no dynamic template head that could reach it (`appDesigner.fieldDesigner.typeCategory.` is the namespace's only one), and its sole textual occurrence anywhere in the repo was the defaults-map row removed here with it. Map and packs move in one commit so `defaults-maps-mirror-en-pack` stays green — that gate fails a map row whose key the `en` pack lacks, and `all-locales-key-parity` fails a pack left behind. `designer.field.formula` ('Formula (CEL)') is untouched: a different, live key belonging to metadata-admin's `ObjectFieldInspector`. --- .../6310-designer-formula-key-retired.md | 29 +++++++++++++++++++ packages/i18n/src/locales/ar.ts | 1 - packages/i18n/src/locales/de.ts | 1 - packages/i18n/src/locales/en.ts | 1 - packages/i18n/src/locales/es.ts | 1 - packages/i18n/src/locales/fr.ts | 1 - packages/i18n/src/locales/ja.ts | 1 - packages/i18n/src/locales/ko.ts | 1 - packages/i18n/src/locales/pt.ts | 1 - packages/i18n/src/locales/ru.ts | 1 - packages/i18n/src/locales/zh.ts | 1 - .../src/hooks/useDesignerTranslation.ts | 1 - 12 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 .changeset/6310-designer-formula-key-retired.md diff --git a/.changeset/6310-designer-formula-key-retired.md b/.changeset/6310-designer-formula-key-retired.md new file mode 100644 index 0000000000..fddfe2ba66 --- /dev/null +++ b/.changeset/6310-designer-formula-key-retired.md @@ -0,0 +1,29 @@ +--- +'@object-ui/i18n': patch +'@object-ui/plugin-designer': patch +--- + +`appDesigner.fieldDesigner.formula` is retired — one row removed from each of the ten +locale packs plus the designer defaults map, 11 lines, zero readers (objectui#6310). + +objectui#6043 retired the Field Designer's formula-expression textarea, which was the +key's only call site (`FieldDesigner.tsx`, the `{ name: 'formula', label: +t('appDesigner.fieldDesigner.formula') }` field descriptor). The value outlived it in +eleven places: `DESIGNER_DEFAULT_TRANSLATIONS` in +`packages/plugin-designer/src/hooks/useDesignerTranslation.ts`, and the `appDesigner > +fieldDesigner > formula` leaf of `packages/i18n/src/locales/{en,de,es,fr,pt,ru,ja,ko,zh,ar}.ts`. + +Removed under objectui#4658's evidence standard, re-measured on this branch rather than +inherited from the card: zero `t()`/`tt()` call sites, no dynamic template head that could +reach it (`appDesigner.fieldDesigner.typeCategory.` is the namespace's only one), and its +sole textual occurrence anywhere in the repo was the defaults-map row this change removes +with it — so the key goes from NEEDS-REVIEW to no footprint at all. + +The map and all ten packs move in one commit, which is what keeps +`defaults-maps-mirror-en-pack` green: that gate fails a map row whose key the `en` pack +lacks, and `all-locales-key-parity` fails a pack left behind. + +Not touched: `designer.field.formula` (`'Formula (CEL)'`) in +`packages/app-shell/src/views/metadata-admin/i18n.ts`, a different and live key belonging +to metadata-admin's `ObjectFieldInspector` — the surface that still authors formula +expressions. diff --git a/packages/i18n/src/locales/ar.ts b/packages/i18n/src/locales/ar.ts index 4ddcc01832..656c70aab0 100644 --- a/packages/i18n/src/locales/ar.ts +++ b/packages/i18n/src/locales/ar.ts @@ -1360,7 +1360,6 @@ const ar = { defaultValue: "القيمة الافتراضية", placeholder: "نص تلميحي", referenceTo: "مرجع إلى", - formula: "صيغة", options: "خيارات", addOption: "إضافة خيار", validationRules: "قواعد التحقق", diff --git a/packages/i18n/src/locales/de.ts b/packages/i18n/src/locales/de.ts index 3f01acac1f..0e77773394 100644 --- a/packages/i18n/src/locales/de.ts +++ b/packages/i18n/src/locales/de.ts @@ -1353,7 +1353,6 @@ const de = { defaultValue: "Standardwert", placeholder: "Platzhalter", referenceTo: "Verweis auf", - formula: "Formel", options: "Optionen", addOption: "Option hinzufügen", validationRules: "Validierungsregeln", diff --git a/packages/i18n/src/locales/en.ts b/packages/i18n/src/locales/en.ts index 9f5bfda13f..03a42eb8c8 100644 --- a/packages/i18n/src/locales/en.ts +++ b/packages/i18n/src/locales/en.ts @@ -1522,7 +1522,6 @@ const en = { defaultValue: 'Default Value', placeholder: 'Placeholder', referenceTo: 'Reference To', - formula: 'Formula', options: 'Options', addOption: 'Add Option', validationRules: 'Validation Rules', diff --git a/packages/i18n/src/locales/es.ts b/packages/i18n/src/locales/es.ts index 400e1327e4..8d562a5e47 100644 --- a/packages/i18n/src/locales/es.ts +++ b/packages/i18n/src/locales/es.ts @@ -1357,7 +1357,6 @@ const es = { defaultValue: "Valor predeterminado", placeholder: "Marcador de posición", referenceTo: "Referencia a", - formula: "Fórmula", options: "Opciones", addOption: "Agregar opción", validationRules: "Reglas de validación", diff --git a/packages/i18n/src/locales/fr.ts b/packages/i18n/src/locales/fr.ts index bbeef56b8b..dd36313851 100644 --- a/packages/i18n/src/locales/fr.ts +++ b/packages/i18n/src/locales/fr.ts @@ -1355,7 +1355,6 @@ const fr = { defaultValue: "Valeur par défaut", placeholder: "Espace réservé", referenceTo: "Référence à", - formula: "Formule", options: "Options", addOption: "Ajouter une option", validationRules: "Règles de validation", diff --git a/packages/i18n/src/locales/ja.ts b/packages/i18n/src/locales/ja.ts index ce2ee4b103..aa87b46fda 100644 --- a/packages/i18n/src/locales/ja.ts +++ b/packages/i18n/src/locales/ja.ts @@ -1353,7 +1353,6 @@ const ja = { defaultValue: "デフォルト値", placeholder: "プレースホルダー", referenceTo: "参照先", - formula: "数式", options: "オプション", addOption: "オプションを追加", validationRules: "検証ルール", diff --git a/packages/i18n/src/locales/ko.ts b/packages/i18n/src/locales/ko.ts index c4cf04d2e7..fc5d2a2b40 100644 --- a/packages/i18n/src/locales/ko.ts +++ b/packages/i18n/src/locales/ko.ts @@ -1353,7 +1353,6 @@ const ko = { defaultValue: "기본값", placeholder: "자리 표시자", referenceTo: "참조 대상", - formula: "수식", options: "옵션", addOption: "옵션 추가", validationRules: "유효성 검사 규칙", diff --git a/packages/i18n/src/locales/pt.ts b/packages/i18n/src/locales/pt.ts index 92feb548ed..fb9e078bc6 100644 --- a/packages/i18n/src/locales/pt.ts +++ b/packages/i18n/src/locales/pt.ts @@ -1352,7 +1352,6 @@ const pt = { defaultValue: "Valor padrão", placeholder: "Espaço reservado", referenceTo: "Referência a", - formula: "Fórmula", options: "Opções", addOption: "Adicionar opção", validationRules: "Regras de validação", diff --git a/packages/i18n/src/locales/ru.ts b/packages/i18n/src/locales/ru.ts index d95db07099..d352728ccc 100644 --- a/packages/i18n/src/locales/ru.ts +++ b/packages/i18n/src/locales/ru.ts @@ -1363,7 +1363,6 @@ const ru = { defaultValue: "Значение по умолчанию", placeholder: "Заполнитель", referenceTo: "Ссылка на", - formula: "Формула", options: "Параметры", addOption: "Добавить параметр", validationRules: "Правила валидации", diff --git a/packages/i18n/src/locales/zh.ts b/packages/i18n/src/locales/zh.ts index 25ecb78164..30c3369ab5 100644 --- a/packages/i18n/src/locales/zh.ts +++ b/packages/i18n/src/locales/zh.ts @@ -1418,7 +1418,6 @@ const zh = { defaultValue: '默认值', placeholder: '占位文本', referenceTo: '引用对象', - formula: '公式', options: '选项', addOption: '添加选项', validationRules: '验证规则', diff --git a/packages/plugin-designer/src/hooks/useDesignerTranslation.ts b/packages/plugin-designer/src/hooks/useDesignerTranslation.ts index cca667b757..f540bc504e 100644 --- a/packages/plugin-designer/src/hooks/useDesignerTranslation.ts +++ b/packages/plugin-designer/src/hooks/useDesignerTranslation.ts @@ -190,7 +190,6 @@ export const DESIGNER_DEFAULT_TRANSLATIONS: Record = { 'appDesigner.fieldDesigner.defaultValue': 'Default Value', 'appDesigner.fieldDesigner.placeholder': 'Placeholder', 'appDesigner.fieldDesigner.referenceTo': 'Reference To', - 'appDesigner.fieldDesigner.formula': 'Formula', 'appDesigner.fieldDesigner.options': 'Options', 'appDesigner.fieldDesigner.addOption': 'Add Option', 'appDesigner.fieldDesigner.validationRules': 'Validation Rules', From ccacdc43b25dc974277e4bd85c8c3b0a25332562 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 22:01:42 +0000 Subject: [PATCH 2/2] test(i18n): pin `appDesigner.fieldDesigner.formula` out of the ten packs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fifth in the retirement-pin series (objectui#4145, objectui#4392, objectui#4730, objectui#5504), same shape: the retired leaf asserted absent from all ten packs, plus surviving-sibling assertions so a green cannot be bought by deleting the neighbourhood. Every i18n gate in this repo runs call site -> key, so none of them can see a dead key come BACK into the packs: the reverse sweep that found this one is report-only by design, `all-locales-key-parity` is fully satisfied by ten packs agreeing on a dead key, and `check:i18n-drift` only fires when a value changes. Reverse-verified rather than asserted: reviving the row in all ten packs turns exactly the one case red, naming each pack, while `all-locales-key-parity` and `defaults-maps-mirror-en-pack` stay green — which is the whole reason the pin exists. The surviving list deliberately excludes the eight `appDesigner.fieldDesigner.*` keys the reverse sweep still lists as NEEDS-REVIEW (`options`, `addOption`, `addRule`, `noFields`, `searchPlaceholder`, `systemBadge`, `ungrouped`, `validationRules`) — pinning "these survive" against keys that may themselves be dead would bake an unverified claim into the guard. Every key it does list was confirmed live by call site in `FieldDesigner.tsx`. --- .../6310-designer-formula-key-retired.md | 9 + ...fieldDesigner-formula-retired-6310.test.ts | 179 ++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 packages/i18n/src/__tests__/appDesigner-fieldDesigner-formula-retired-6310.test.ts diff --git a/.changeset/6310-designer-formula-key-retired.md b/.changeset/6310-designer-formula-key-retired.md index fddfe2ba66..905322ab4c 100644 --- a/.changeset/6310-designer-formula-key-retired.md +++ b/.changeset/6310-designer-formula-key-retired.md @@ -27,3 +27,12 @@ Not touched: `designer.field.formula` (`'Formula (CEL)'`) in `packages/app-shell/src/views/metadata-admin/i18n.ts`, a different and live key belonging to metadata-admin's `ObjectFieldInspector` — the surface that still authors formula expressions. + +`packages/i18n/src/__tests__/appDesigner-fieldDesigner-formula-retired-6310.test.ts` pins the +removal by name, following the four prior retirements (objectui#4145, objectui#4392, +objectui#4730, objectui#5504). Every i18n gate here runs call site → key, so none of them can +see a dead key come BACK into the packs: the reverse sweep that found this one is report-only +by design, `all-locales-key-parity` is fully satisfied by ten packs agreeing on a dead key, and +`check:i18n-drift` only fires when a value changes. Reverse-verified rather than asserted — +reviving the row in all ten packs turns exactly that one case red, naming each pack, while the +parity gate and the defaults-map mirror stay green. diff --git a/packages/i18n/src/__tests__/appDesigner-fieldDesigner-formula-retired-6310.test.ts b/packages/i18n/src/__tests__/appDesigner-fieldDesigner-formula-retired-6310.test.ts new file mode 100644 index 0000000000..8ffea7fc68 --- /dev/null +++ b/packages/i18n/src/__tests__/appDesigner-fieldDesigner-formula-retired-6310.test.ts @@ -0,0 +1,179 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * `appDesigner.fieldDesigner.formula` is retired from all ten packs, and the + * Field Designer vocabulary it sat in must keep naming real controls + * (objectui#6310). + * + * ## What was removed and why + * + * One row per pack (`en: 'Formula'`, `zh: '公式'`, `ru: 'Формула'`, …). objectui#6043 + * retired the Field Designer's formula-expression textarea, which held the key's + * ONLY call site — the `{ name: 'formula', label: + * t('appDesigner.fieldDesigner.formula') }` field descriptor in + * `FieldDesigner.tsx`. The label outlived the control it labelled by eleven + * files: the ten packs, plus the `DESIGNER_DEFAULT_TRANSLATIONS` row that mirrors + * them. + * + * ## Why this pin is NEGATIVE, and why it is needed at all + * + * Every i18n gate in this repo runs **call site -> key**, never key -> call site + * (objectui#4145's mechanism, restated by objectui#4392, objectui#4730 and + * objectui#5504): + * + * - `scripts/check-i18n-call-site-keys.mjs` asks whether each call site's key + * resolves in `en`. A key with no call site is never visited. + * - `all-locales-key-parity.test.ts` compares the ten packs' key SETS to each + * other. One dead key present in all ten is exactly what it wants. + * - `scripts/check-i18n-en-drift.mjs` only fires when an `en` value CHANGES. + * - `scripts/check-i18n-dead-keys.mjs` IS the reverse direction, and it is + * report-only by design and wired into no workflow (objectui#4658). + * + * So the retired row can return to all ten packs with every gate green, and a + * translator filling in "the missing formula label" is a plausible way for that + * to happen — the surrounding vocabulary still describes a field editor, so the + * gap reads like an oversight rather than a decision. Restoring it goes red here. + * + * ## The other half of the retirement is guarded elsewhere, on purpose + * + * The `DESIGNER_DEFAULT_TRANSLATIONS` row is pinned by + * `app-shell/src/__tests__/defaults-maps-mirror-en-pack.test.tsx` (objectui#4401), + * which fails any map row whose key the `en` pack lacks. This file cannot assert + * it: `@object-ui/plugin-designer` depends on `@object-ui/i18n`, so importing the + * map back into this package inverts the dependency — the same reason #4401's + * gate lives in `app-shell` and `gantt-count-interpolation-4157.test.ts` asserts + * `en` values as literals. The two halves interlock: re-add the pack key and this + * file reds; re-add the map row alone and #4401's gate reds. + * + * ## What this file does NOT claim + * + * - **`designer.field.formula` (`'Formula (CEL)'`) is a DIFFERENT, LIVE key** — + * metadata-admin's `ObjectFieldInspector`, the surface that still authors + * formula expressions, reads it from + * `packages/app-shell/src/views/metadata-admin/i18n.ts`. It is not in the + * locale packs and is out of this file's reach by the same dependency + * direction as above. Two keys end in `.formula`; only the + * `appDesigner.fieldDesigner.*` spelling is retired. Grep the full dotted path. + * - **`appDesigner.fieldDesigner` is a live namespace.** Only the one leaf went; + * {@link SURVIVING} exists so a green here cannot be bought by deleting the + * neighbourhood. + * - **{@link SURVIVING} is deliberately not "the rows next to it".** The row that + * followed the retired one (`options`) is absent from that list, along with + * `addOption`, `addRule`, `noFields`, `searchPlaceholder`, `systemBadge`, + * `ungrouped` and `validationRules`: objectui#6310 measured all eight as + * NEEDS-REVIEW candidates of the same reverse sweep, with the same + * defaults-map-only footprint the retired key had, and none of them has been + * individually confirmed either way (that is objectui#4730's job). Pinning + * "these survive" against keys that may themselves be dead would bake an + * unverified claim into the guard. Every key in {@link SURVIVING} was confirmed + * live by call site in `packages/plugin-designer/src/FieldDesigner.tsx`. + */ +import { describe, it, expect } from 'vitest'; +import { builtInLocales } from '../locales/index'; + +type LocaleCode = keyof typeof builtInLocales; +const LANGS = Object.keys(builtInLocales) as LocaleCode[]; + +const at = (pack: unknown, path: string): unknown => + path.split('.').reduce((n, k) => (n as Record | undefined)?.[k], pack); + +/** The retired leaf, named rather than counted. */ +const RETIRED = 'appDesigner.fieldDesigner.formula'; + +/** + * Rows the deletion swept around — each one confirmed live by a `t()` call site + * in `FieldDesigner.tsx`, not merely by sitting nearby. `referenceTo` is the row + * that immediately PRECEDED the retired one in every pack, and + * `typeSpecificSection` is the drawer section the formula textarea used to be + * rendered in, so a sweep that over-reached would land on them first. + */ +const SURVIVING = [ + 'appDesigner.fieldDesigner.referenceTo', + 'appDesigner.fieldDesigner.typeSpecificSection', + 'appDesigner.fieldDesigner.defaultValue', + 'appDesigner.fieldDesigner.placeholder', + 'appDesigner.fieldDesigner.fieldType', + 'appDesigner.fieldDesigner.title', + 'appDesigner.fieldDesigner.allTypes', +] as const; + +/** + * The namespace's only dynamically built family — + * ``t(`appDesigner.fieldDesigner.typeCategory.${cat}`)`` at + * `FieldDesigner.tsx`. No member is spelled literally at any call site, so a + * future reverse sweep reading only literal arguments is precisely where these + * would look dead. They are the live half of the same namespace and are pinned + * by name here. + */ +const TYPE_CATEGORY = [ + 'appDesigner.fieldDesigner.typeCategory.text', + 'appDesigner.fieldDesigner.typeCategory.number', + 'appDesigner.fieldDesigner.typeCategory.date', + 'appDesigner.fieldDesigner.typeCategory.choice', + 'appDesigner.fieldDesigner.typeCategory.relation', + 'appDesigner.fieldDesigner.typeCategory.advanced', +] as const; + +describe('`appDesigner.fieldDesigner.formula` is retired from the ten packs (objectui#6310)', () => { + it('covers all ten packs and a live `appDesigner.fieldDesigner` root', () => { + // Guards the premise the rest of the file rests on: a pin that iterates an + // empty pack list, or asserts absence inside a namespace that itself + // vanished, is green for the wrong reason. + expect(LANGS).toHaveLength(10); + for (const lang of LANGS) { + const root = at(builtInLocales[lang], 'appDesigner.fieldDesigner'); + expect(root, `${lang} lost the appDesigner.fieldDesigner root`).toBeDefined(); + expect(Object.keys(root as Record).length, lang).toBeGreaterThanOrEqual(20); + } + }); + + it('no pack defines the retired formula label', () => { + const revived: string[] = []; + for (const lang of LANGS) { + if (at(builtInLocales[lang], RETIRED) !== undefined) revived.push(`${lang} :: ${RETIRED}`); + } + // Named, not counted: a half-reverted retirement is repaired pack by pack. + expect( + revived, + 'The retired Field Designer formula label is back in a locale pack. It ' + + 'labelled a formula-expression textarea that objectui#6043 removed, so ' + + 'nothing reads it — and no other i18n gate can see a dead key return, ' + + 'because every one of them runs call site -> key (objectui#6310). If a ' + + 'formula control is being reintroduced to the Field Designer, author ' + + 'its label alongside the control rather than restoring this row. The ' + + 'live key for the surface that DOES author formula expressions is ' + + '`designer.field.formula` in ' + + 'packages/app-shell/src/views/metadata-admin/i18n.ts — a different key, ' + + 'not this one.', + ).toEqual([]); + }); + + it('the deletion swept around its neighbours', () => { + for (const lang of LANGS) { + for (const key of SURVIVING) { + const value = at(builtInLocales[lang], key); + expect(typeof value, `${lang} :: ${key}`).toBe('string'); + expect((value as string).length, `${lang} :: ${key}`).toBeGreaterThan(0); + } + } + }); + + it('keeps every member of the dynamically built `typeCategory` family', () => { + // `all-locales-key-parity` would be satisfied by all ten packs dropping a + // member together, and no literal-argument scan can see these are read at + // all. State the vocabulary here, by name, so the substitution cannot be + // left pointing at a key nothing defines. + const missing: string[] = []; + for (const lang of LANGS) { + for (const key of TYPE_CATEGORY) { + if (typeof at(builtInLocales[lang], key) !== 'string') missing.push(`${lang} :: ${key}`); + } + } + expect( + missing, + 'a member of the `appDesigner.fieldDesigner.typeCategory.*` family is gone; ' + + 'FieldDesigner.tsx builds these keys by substitution, so the loss renders ' + + 'as a raw key in the type filter rather than failing any other gate', + ).toEqual([]); + }); +});