Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .changeset/create-target-org-translated-3517.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
'@object-ui/i18n': patch
'@object-ui/app-shell': patch
---

The group-tenancy write-target badge is now translated in all ten locales (objectui#3517)

`form.createTargetOrg` — the ADR-0105 badge `RecordFormPage` shows in create mode
to name the organization a new record will land in — was defined in **no** locale
pack, not even `en`. i18next therefore genuinely missed the key and rendered the
call site's inline `defaultValue`, so the badge read English `Creates in <org>` in
all ten languages: a Chinese console creating a record on an org-walled object
showed `Creates in 某某组织`.

`all-locales-key-parity.test.ts` could not see this. It asserts that every pack
defines every **`en`** key, so a key `en` itself lacks is outside the comparison —
ten packs missing it identically kept parity fully green.

## What changed

- `createTargetOrg` is backfilled into `en` as `Creates in {{org}}`, which makes
the parity gate demand it from the other nine; each is translated to its pack's
existing `form`-section tone rather than copied or machine-filled.
- The inline `defaultValue` in `RecordFormPage.tsx` is deleted, finishing what
objectui#3469 started — that key was the file's last remaining exception, and
every `t()` on the page now passes bare. Declared = enforced: the packs are the
single source of this copy, and a missing key must surface (raw key + dev
missing-key warning) instead of being papered over at the call site.
- The two exception-pinning tests objectui#3516 left behind invert. `form.createTargetOrg`
joins the `BARE_KEYS` list (pinned present in all ten packs), and the render
assertion now checks the badge against the **pack** copy in both `en` and `zh` —
the deleted English default could not satisfy the Chinese assertion, so the
badge fails loudly if the packs ever stop driving it.
61 changes: 41 additions & 20 deletions packages/app-shell/src/views/RecordFormPage.i18n.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
* `New {object}` while the pack says `Create {{object}}`. The day the key was
* renamed or dropped, the page title would have silently changed verb with
* every test still green. #3469 deleted them (declared = enforced), leaving
* exactly one deliberate exception documented below.
* exactly one deliberate exception — `form.createTargetOrg`, the only key the
* packs genuinely lacked. #3517 backfilled it into all ten, deleted that last
* default, and inverted the exception's pin (final describe below), so the
* rule now holds for every `t()` on the page with no carve-out.
*
* Direction note — a "put the deleted limb back and watch this go red"
* reverse-verification is IMPOSSIBLE for this change, by construction: with
Expand Down Expand Up @@ -214,6 +217,10 @@ describe('the keys RecordFormPage passes bare are defined in every pack (#3469)'
'form.createSuccess',
'form.updateSuccess',
'form.saveRecord',
// Joined this list in #3517. It was the file's one exception — defined in
// NO pack, not even `en` — until the backfill; the describe below pins the
// rendered badge now that it resolves like every other key here.
'form.createTargetOrg',
'common.back',
'common.cancel',
] as const;
Expand All @@ -233,28 +240,42 @@ describe('the keys RecordFormPage passes bare are defined in every pack (#3469)'
});
});

describe('form.createTargetOrg keeps its fallback — the one LOAD-BEARING default (#3469)', () => {
// Ledger entry, not decoration. Unlike every key above, this one is defined
// in NO pack (not even `en`), so i18next genuinely misses and the inline
// `defaultValue` is what renders. It was therefore NOT deleted by #3469.
// When the key is backfilled into `en` (parity carries it to the other
// nine), delete the inline default in the same change — this test says so.
it('is absent from all ten packs, which is why the inline default survives', () => {
const defining = Object.entries(builtInLocales)
.filter(([, pack]) => typeof (pack as any)?.form?.createTargetOrg === 'string')
.map(([lang]) => lang);
expect(
defining,
'form.createTargetOrg is now translated — backfill done, so remove the inline defaultValue in RecordFormPage.tsx',
).toEqual([]);
});

it('renders the fallback copy, so deleting it would put a raw key on screen', async () => {
describe('the ADR-0105 write-target badge resolves from the locale packs (#3517)', () => {
// This describe used to pin the OPPOSITE fact. `form.createTargetOrg` was
// the file's one load-bearing `defaultValue`: defined in NO pack — not even
// `en` — so i18next genuinely missed and the inline default rendered, which
// is why the badge read English `Creates in <org>` in all ten locales
// (#3517). #3469 kept that default deliberately and left an assertion here
// that would go red the moment any pack defined the key, instructing the
// backfill to delete it. The backfill landed, so the assertion has served
// its purpose and inverts: the key is now an ordinary member of BARE_KEYS
// above (pinned present in all ten packs) and what needs pinning is that the
// BADGE reads the pack copy.
const badgeIn = async (language: string) => {
authState.activeOrganization = { name: 'Acme Inc' };
getAuthConfig.mockResolvedValue({ features: { tenancyPosture: 'group' } });
renderPage('create');
const badge = await screen.findByTestId('record-form-write-target-org');
renderPage('create', { language });
return screen.findByTestId('record-form-write-target-org');
};

it('renders the en pack copy, never a raw key', async () => {
const badge = await badgeIn('en');
expect(badge).toHaveTextContent('Creates in Acme Inc');
// A missing key would surface the key itself — the failure mode the
// deleted `defaultValue` used to hide.
expect(badge).not.toHaveTextContent('form.createTargetOrg');
});

it('follows the active locale — the deleted English default could not produce this', async () => {
// Same proof the title describe uses, and the point of the whole issue:
// under `zh` the badge is Chinese. The removed default was a hardcoded
// `Creates in ${org}`, so it could never satisfy this assertion — if the
// packs ever stop driving this badge, it fails loudly instead of quietly
// reverting to English.
const badge = await badgeIn('zh');
expect(badge).toHaveTextContent(
String(builtInLocales.zh.form.createTargetOrg).replace('{{org}}', 'Acme Inc'),
);
expect(badge).toHaveTextContent('创建到Acme Inc');
});
});
19 changes: 5 additions & 14 deletions packages/app-shell/src/views/RecordFormPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,10 @@ export function RecordFormPage({ mode }: RecordFormPageProps) {
// would have silently changed verb with every test still green (#3469).
// Declared = enforced: the packs are the single source of this copy, and a
// missing key must surface (raw key + dev missing-key warning), not be
// papered over at the call site. The one deliberate exception in this file
// is `form.createTargetOrg` below — see the note there.
// papered over at the call site. This now holds for EVERY `t()` in the file:
// `form.createTargetOrg` was the last exception (#3469 kept its default
// because the key existed in no pack, not even `en`) and #3517 backfilled it
// into all ten, so the write-target badge is translated and passes bare too.
const pageTitle =
mode === 'create'
? t('form.createTitle', { object: label })
Expand Down Expand Up @@ -314,18 +316,7 @@ export function RecordFormPage({ mode }: RecordFormPageProps) {
data-testid="record-form-write-target-org"
>
<Building2 className="h-3.5 w-3.5 shrink-0" />
{/* The one LOAD-BEARING `defaultValue` in this file (#3469):
unlike every other key here, `form.createTargetOrg` is
defined in NO locale pack — not even `en` — so i18next
genuinely misses and this default is what renders. Removing
it would put the raw key on screen. It is also why the badge
is English-only in all ten locales. Backfilling the key into
`en` (the parity test then carries it to the other nine) is
the real fix; delete this default in the same change. */}
{t('form.createTargetOrg', {
org: activeOrganization.name,
defaultValue: `Creates in ${activeOrganization.name}`,
})}
{t('form.createTargetOrg', { org: activeOrganization.name })}
</span>
)}
</header>
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const ar = {
createTitle: "إنشاء {{object}}",
editTitle: "تعديل {{object}}",
viewTitle: "عرض {{object}}",
createTargetOrg: "الإنشاء في {{org}}",
saveRecord: "حفظ السجل",
create: "إنشاء",
update: "تحديث",
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const de = {
createTitle: "{{object}} erstellen",
editTitle: "{{object}} bearbeiten",
viewTitle: "{{object}} anzeigen",
createTargetOrg: "Wird in {{org}} erstellt",
saveRecord: "Datensatz speichern",
create: "Erstellen",
update: "Aktualisieren",
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ const en = {
createTitle: 'Create {{object}}',
editTitle: 'Edit {{object}}',
viewTitle: 'View {{object}}',
createTargetOrg: 'Creates in {{org}}',
saveRecord: 'Save',
create: 'Create',
update: 'Update',
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ const es = {
createTitle: "Crear {{object}}",
editTitle: "Editar {{object}}",
viewTitle: "Ver {{object}}",
createTargetOrg: "Se crea en {{org}}",
saveRecord: "Guardar registro",
create: "Crear",
update: "Actualizar",
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const fr = {
createTitle: "Créer {{object}}",
editTitle: "Modifier {{object}}",
viewTitle: "Afficher {{object}}",
createTargetOrg: "Création dans {{org}}",
saveRecord: "Enregistrer",
create: "Créer",
update: "Mettre à jour",
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const ja = {
createTitle: "{{object}}を作成",
editTitle: "{{object}}を編集",
viewTitle: "{{object}}を表示",
createTargetOrg: "作成先: {{org}}",
saveRecord: "レコードを保存",
create: "作成",
update: "更新",
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const ko = {
createTitle: "{{object}} 생성",
editTitle: "{{object}} 편집",
viewTitle: "{{object}} 보기",
createTargetOrg: "{{org}}에 생성",
saveRecord: "레코드 저장",
create: "생성",
update: "업데이트",
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/pt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const pt = {
createTitle: "Criar {{object}}",
editTitle: "Editar {{object}}",
viewTitle: "Ver {{object}}",
createTargetOrg: "Criação em {{org}}",
saveRecord: "Salvar registro",
create: "Criar",
update: "Atualizar",
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const ru = {
createTitle: "Создать {{object}}",
editTitle: "Редактировать {{object}}",
viewTitle: "Просмотреть {{object}}",
createTargetOrg: "Создаётся в {{org}}",
saveRecord: "Сохранить запись",
create: "Создать",
update: "Обновить",
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ const zh = {
createTitle: '新建{{object}}',
editTitle: '编辑{{object}}',
viewTitle: '查看{{object}}',
createTargetOrg: '创建到{{org}}',
saveRecord: '保存',
create: '创建',
update: '更新',
Expand Down
Loading