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
10 changes: 10 additions & 0 deletions .changeset/25922-studio-i18n-studio-previews.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@object-ui/i18n': patch
---

feat(i18n): the rest of the Studio strings and the metadata preview panes move into the `studio` namespace

Clause-②: no
Metadata-protocol: none

- **Fifth slice of the move ruled on objectstack#25922** (6431032267, Q2 = A; kept by 6431234244). Every remaining `engine.studio.*` key except `engine.studio.inspector.*`, and the preview panes' `engine.*Preview.*` and `engine.previewShell.*` keys (the doc, book, flow, page-block and translation previews stay for a later slice), 596 keys in both `en` and `zh`, now live in `@object-ui/i18n/namespaces/studio` instead of `@object-ui/app-shell`'s private table, text unchanged. Studio reads the same strings; a Chinese viewer sees these labels in English for the moment the `zh` catalogue takes to load, as with the earlier slices. No export changes.
1,379 changes: 0 additions & 1,379 deletions ui/packages/app-shell/src/views/metadata-admin/i18n.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

/**
* objectstack#25922 slices S2 to S4 — the designer rows that moved into
* objectstack#25922 slices S2 to S5 — the designer rows that moved into
* `@object-ui/i18n`'s `studio` namespace (every `engine.inspector.*` row;
* `engine.packages.*`, `engine.form.*`, `engine.edit.*`; five `engine.studio.*`
* pillars, the data source pages and the field types), and app-shell's `t()` /
* `tOptional()` still answer every one of them the way they did when the rows
* sat in its own table.
* `engine.packages.*`, `engine.form.*`, `engine.edit.*`; `engine.studio.*` but
* its inspector; the data source pages, the field types and the metadata
* preview panes), and app-shell's `t()` / `tOptional()` still answer every one
* of them the way they did when the rows sat in its own table.
*
* What would break it:
* - a row copied back into the app-shell table with a different value — the
Expand Down Expand Up @@ -54,6 +54,30 @@ const FAMILY_FLOOR: Readonly<Record<string, number>> = Object.freeze({
'engine.fieldType.': 45,
'engine.fieldTypeDesc.': 45,
'engine.fieldCategory.': 9,
// S5 — the rest of engine.studio (its inspector aside) and the preview panes
'engine.studio.': 600,
'engine.studio.rules.': 55,
'engine.studio.interfaces.': 30,
'engine.studio.designer.': 20,
'engine.actionPreview.': 50,
'engine.permissionPreview.': 35,
'engine.validationPreview.': 30,
'engine.jobPreview.': 18,
'engine.agentPreview.': 15,
'engine.skillPreview.': 15,
'engine.datasetPreview.': 12,
'engine.toolPreview.': 12,
'engine.picklistPreview.': 12,
'engine.dashboardPreview.': 9,
'engine.appPreview.': 9,
'engine.emailTemplatePreview.': 9,
'engine.pagePreview.': 8,
'engine.screenPreview.': 7,
'engine.positionPreview.': 6,
'engine.reportPreview.': 6,
'engine.viewPreview.': 4,
'engine.objectPreview.': 1,
'engine.previewShell.': 1,
});

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import { render, screen, cleanup } from '@testing-library/react';
import { t } from '../i18n';
import { AppPreview } from './AppPreview';

// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
// Resident before any case, so the zh rows below read synchronously.
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
await loadStudioCatalogue('zh');

afterEach(cleanup);

const NAV = [{ id: 'accounts', type: 'object', label: 'Accounts', objectName: 'account' }];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ vi.mock('@object-ui/react', async (importOriginal) => {
import { DashboardPreview } from './DashboardPreview';
import type { MetadataSelection } from '../preview-registry';

// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
// Resident before any case, so the zh rows below read synchronously.
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
await loadStudioCatalogue('zh');

afterEach(cleanup);

type Patch = Record<string, unknown>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ import { ScreenPreview } from './ScreenPreview';
import { buildScreenSpec } from './screen-spec';
import { ScreenView, initialScreenValues, type ScreenFieldSpec, type ScreenSpec } from '../../ScreenView';

// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
// Resident before any case, so the zh rows below read synchronously.
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
await loadStudioCatalogue('zh');

afterEach(cleanup);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ import { SkillPreview } from './SkillPreview';
import { ToolPreview } from './ToolPreview';
import { JobPreview } from './JobPreview';

// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
// Resident before any case, so the zh rows below read synchronously.
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
await loadStudioCatalogue('zh');

afterEach(() => {
cleanup();
vi.unstubAllGlobals();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ import { FlowSimulatorPanel } from './FlowSimulatorPanel';
import { FlowNodeInspector } from '../inspectors/FlowNodeInspector';
import type { SimEdge, SimNode } from './simulator/flow-sim-types';

// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
// Resident before any case, so the zh rows below read synchronously.
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
await loadStudioCatalogue('zh');

afterEach(() => {
cleanup();
state.canvas = 'draw';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import { createI18n, I18nProvider } from '@object-ui/i18n';
import { t } from './i18n';
import { AppNavCanvas } from './previews/AppNavCanvas';

// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
// Resident before any case, so the zh rows below read synchronously.
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
await loadStudioCatalogue('zh');

afterEach(cleanup);

// Representative keys spanning shared chrome, every pillar, and AppNavCanvas.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,24 @@
* objectstack#25922, (M) slice 1 — a Studio designer screen reads the strings
* that moved into `@object-ui/i18n`'s `studio` namespace.
*
* The Data pillar's "Add field" popover mixes both homes on one screen: its
* field labels (`designer.field.label` / `.apiName` / `.type`) moved into the
* namespace, its Cancel button (`engine.studio.cancel`) is still in the
* app-shell table (its title and Create button followed in slice S4). It takes its locale from `useMetadataLocale()`, the hook
* every designer component uses, so this is the path a real screen takes:
* The Data pillar's "Add field" popover reads the namespace through both
* doors: its field labels (`designer.field.label` / `.apiName` / `.type`,
* slice 1) straight from it, its chrome (`engine.studio.*`, slices S4 and S5)
* through app-shell's `t()`, which answers from the namespace once the
* designer table no longer holds the row. It takes its locale from
* `useMetadataLocale()`, the hook every designer component uses, so this is
* the path a real screen takes:
*
* - zh: the namespace's `zh` catalogue is NOT resident when the screen first
* renders (this file runs in an isolated `dom` project and preloads
* nothing). The first paint reads the moved labels in English — the
* fallback, never the raw key — and the same mount re-renders in Chinese
* once the catalogue lands. The table's keys read Chinese throughout.
* - de: a language neither home ships renders English from both.
* once the catalogue lands, chrome included.
* - de: a language the namespace does not ship renders English through both
* doors.
*
* Expectations are read from the namespace and the table rather than typed
* out, so no case restates a translation.
* Expectations are read from the namespace and `t()` rather than typed out, so
* no case restates a translation.
*/

import '@testing-library/jest-dom/vitest';
Expand All @@ -39,8 +42,8 @@ afterEach(cleanup);

/** Keys this screen renders from the namespace. */
const MOVED = ['designer.field.label', 'designer.field.type'] as const;
/** A key this screen renders from the app-shell table. */
const TABLE = 'engine.studio.cancel';
/** A chrome key this screen reads through app-shell's `t()`. */
const CHROME = 'engine.studio.cancel';

function Screen({ lang }: { lang: string }) {
return (
Expand All @@ -58,9 +61,8 @@ const en = (key: string) => studioString(key, 'en')!;

describe('AddFieldPopover — strings from the studio namespace (objectstack#25922)', () => {
it('zh: English while the catalogue is in flight, then Chinese on the same mount', async () => {
// The premise: these keys are the namespace's, and the title is the table's.
for (const key of MOVED) expect(en(key), key).toBeDefined();
expect(studioString(TABLE, 'en')).toBeUndefined();
// The premise: every one of these keys is the namespace's.
for (const key of [...MOVED, CHROME]) expect(en(key), key).toBeDefined();
expect(isStudioCatalogueLoaded('zh-CN')).toBe(false);

render(<Screen lang="zh" />);
Expand All @@ -77,14 +79,14 @@ describe('AddFieldPopover — strings from the studio namespace (objectstack#259
for (const key of MOVED) expect(zh[key], key).not.toBe(en(key));
expect(await screen.findByText(zh['designer.field.label'])).toBeInTheDocument();
for (const key of MOVED) expect(popover()).toHaveTextContent(zh[key]);
expect(popover()).toHaveTextContent(t(TABLE, 'zh-CN'));
expect(t(TABLE, 'zh-CN')).not.toBe(t(TABLE, 'en-US'));
expect(popover()).toHaveTextContent(t(CHROME, 'zh-CN'));
expect(t(CHROME, 'zh-CN')).not.toBe(t(CHROME, 'en-US'));
});

it('de: a third locale renders English from both homes', () => {
it('de: a third locale renders English through both doors', () => {
render(<Screen lang="de" />);
for (const key of MOVED) expect(popover()).toHaveTextContent(en(key));
expect(popover()).toHaveTextContent(t(TABLE, 'en-US'));
expect(popover()).toHaveTextContent(t(CHROME, 'en-US'));
for (const key of MOVED) expect(t(key, 'de')).toBe(en(key));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { CreateItemDialog } from './CreateItemDialog';
import { t, type SupportedLocale } from '../metadata-admin/i18n';

// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
// Resident before any case, so the zh rows below read synchronously.
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
await loadStudioCatalogue('zh');

afterEach(cleanup);

function renderDialog(locale: SupportedLocale = 'en-US') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ vi.mock('@dnd-kit/core', async (importOriginal) => {

import { ObjectFormDesigner } from './ObjectFormDesigner';

// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
// Resident before any case, so the zh rows below read synchronously.
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
await loadStudioCatalogue('zh');

type Field = { name: string; type: string; label: string; group?: string };

const DRAFT = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ import { I18nProvider, createI18n } from '@object-ui/i18n';
import { ObjectFormDesigner } from './ObjectFormDesigner';
import { t, tFormat } from '../metadata-admin/i18n';

// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
// Resident before any case, so the zh rows below read synchronously.
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
await loadStudioCatalogue('zh');

const ROLE_KEY = 'engine.studio.designer.fieldRole';
/** dnd-kit's own defaults: `useSortable`'s, and `useDraggable`'s under it. */
const LIBRARY_DEFAULTS = ['sortable', 'draggable'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ vi.mock('./StudioAiCopilot', () => ({ StudioChatDock: () => null }));

import { StudioDesignSurface } from './StudioDesignSurface';

// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
// Resident before any case, so the zh rows below read synchronously.
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
await loadStudioCatalogue('zh');

window.matchMedia = ((query: string) => ({
matches: false,
media: query,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import { describe, expect, it } from 'vitest';
import type { DraftChange } from '@object-ui/data-objectstack';
import { addedArrayElements, copilotDraftMarkOf, draftSourceText } from './StudioDraftMark';

// The `engine.studio.*` rows this suite reads in zh live in `@object-ui/i18n`'s
// `studio` namespace, whose `zh` catalogue is fetched on demand
// (objectstack#25922). Resident before any case: this file runs in the `unit`
// project, whose shared module graph would otherwise let it pass only when
// another file had loaded `zh` first.
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
await loadStudioCatalogue('zh');

const NOW = Date.parse('2026-10-19T10:00:00.000Z');
const ago = (minutes: number) => new Date(NOW - minutes * 60_000).toISOString();
const entry = (path: string[], by: DraftChange['by'], minutes = 2): DraftChange => ({ path, by, actor: 'usr_1', at: ago(minutes) });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ import {
} from './metadataError';
import { t, tFormat } from '../metadata-admin/i18n';

// The `engine.studio.*` rows this suite reads in zh live in `@object-ui/i18n`'s
// `studio` namespace, whose `zh` catalogue is fetched on demand
// (objectstack#25922). Resident before any case: this file runs in the `unit`
// project, whose shared module graph would otherwise let it pass only when
// another file had loaded `zh` first.
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
await loadStudioCatalogue('zh');

/** Text an author cannot act on: a class or package name, a tracker id, a raw path, code quotes. */
const DEVELOPER_TEXT = /MetadataClient|@objectstack|objectstack#|objectui#|`|fields\.|nodes\.\d|navigation\.\d/;

Expand Down
Loading
Loading