diff --git a/.changeset/form-remaining-hardcoded-english.md b/.changeset/form-remaining-hardcoded-english.md new file mode 100644 index 0000000000..d41a5179ce --- /dev/null +++ b/.changeset/form-remaining-hardcoded-english.md @@ -0,0 +1,10 @@ +--- +'@object-ui/components': patch +'@object-ui/i18n': patch +--- + +The form renderer's last user-visible English literals now go through i18n (#3272). The fullscreen long-text editor (`mobile_fullscreen`) was an entire untranslated dialog — title, screen-reader description, `Cancel` / `Done` footer buttons, and the expand trigger's accessible name — rendering English inside an otherwise translated zh/ja/ar form; it now reads the new `form.fullscreen.*` keys, shipped in all ten locale packs. + +**Behaviour change worth reading if you author forms:** `submitLabel` and `cancelLabel` no longer default to the literals `'Submit'` and `'Cancel'` in the renderer. They default to *unset*, and the action bar falls back at render time to `common.submit` / `common.cancel`, so a form that declares no button copy now follows the session language instead of being silently frozen to English. A label you DO declare still wins verbatim in every locale — including an English one under a zh session, and including an explicit empty string (the fallback uses `??`, so `submitLabel: ''` renders a blank button rather than being overwritten). The only forms whose rendered text changes are those that never declared the labels and are viewed in a non-English session — which is the bug. `FormSchema.submitLabel` / `cancelLabel` stay optional strings; no spec or type change. + +Also removed the built-in `select` branch's second `|| 'Select an option'` fallback. The single call site already supplies `t('common.selectOption')`, so the literal was reachable only through an authored `placeholder: ''` — where it replaced the author's deliberate blank with an untranslated English word. diff --git a/packages/components/src/renderers/form/__tests__/form-action-labels-i18n.test.tsx b/packages/components/src/renderers/form/__tests__/form-action-labels-i18n.test.tsx new file mode 100644 index 0000000000..da3bafa70b --- /dev/null +++ b/packages/components/src/renderers/form/__tests__/form-action-labels-i18n.test.tsx @@ -0,0 +1,107 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * Un-authored submit/cancel button copy follows the session locale — #3272. + * + * `submitLabel` / `cancelLabel` used to carry English DEFAULTS in the + * destructuring (`submitLabel = 'Submit'`). That made two different things + * indistinguishable — "the author said nothing" and "the author typed + * Submit" — and locked every un-labelled form's action bar to English in a + * zh/ja/ar session, which no amount of translating the rest of the form could + * undo. The default is now the ABSENCE of a value; the fallback happens at + * render through `common.submit` / `common.cancel`. + * + * The direction that matters most here is the SECOND one: an authored label + * must still win VERBATIM. A render-time fallback is only safe if it cannot + * reach a form whose author did declare the copy — otherwise the fix would + * have traded one silent override for another, translating text the author + * deliberately wrote. Both directions are pinned below, and the `''` case + * pins `??` (not `||`), which is the whole difference between "unset" and + * "explicitly blank". + */ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { ComponentRegistry } from '@object-ui/core'; +import { I18nProvider } from '@object-ui/i18n'; +// Module scope, not `beforeAll` (objectui#3010/#3021). +import '../../../renderers'; + +const fields = [{ name: 'name', label: 'Name', type: 'input' }]; + +function renderFormIn(language: string, schemaExtra: Record = {}) { + const Form = ComponentRegistry.get('form')!; + return render( + +
+ , + ); +} + +describe('form renderer — action-bar labels fall back through i18n (objectui#3272)', () => { + it('renders the English words under an en provider', () => { + // Byte-identical to the literal defaults this replaced. + renderFormIn('en'); + + expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); + }); + + it('renders Chinese under a zh provider', () => { + renderFormIn('zh'); + + expect(screen.getByRole('button', { name: '提交' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '取消' })).toBeInTheDocument(); + // The literals, asserted absent. + expect(screen.queryByRole('button', { name: 'Submit' })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Cancel' })).not.toBeInTheDocument(); + }); + + it('renders Japanese under a ja provider', () => { + renderFormIn('ja'); + + expect(screen.getByRole('button', { name: '送信' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'キャンセル' })).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Submit' })).not.toBeInTheDocument(); + }); + + it('an authored label wins VERBATIM over the locale fallback', () => { + // The nail this whole change hangs on: the fallback may only fill an + // absence. An authored string is the author's copy — including an English + // one under a zh session, and including one that happens to be a word the + // locale pack also knows. + renderFormIn('zh', { submitLabel: 'Create account', cancelLabel: 'Nevermind' }); + + expect(screen.getByRole('button', { name: 'Create account' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Nevermind' })).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: '提交' })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: '取消' })).not.toBeInTheDocument(); + }); + + it('an authored label wins verbatim in the OTHER direction too', () => { + // Authored Chinese under an `en` session — the fallback is not a + // "translate the button" step, it is a "fill the blank" step. + renderFormIn('en', { submitLabel: '立即提交' }); + + expect(screen.getByRole('button', { name: '立即提交' })).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Submit' })).not.toBeInTheDocument(); + }); + + it('an authored empty label stays empty — `??`, not `||`', () => { + // `submitLabel: ''` is a declaration ("render no text"), not an absence. + // A `||` fallback would silently overwrite it with the locale word, which + // is the same class of override the English default was. + const { container } = renderFormIn('zh', { submitLabel: '', cancelLabel: '' }); + + const submit = container.querySelector('button[type="submit"]'); + expect(submit).not.toBeNull(); + expect(submit!.textContent).toBe(''); + expect(screen.queryByRole('button', { name: '提交' })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: '取消' })).not.toBeInTheDocument(); + }); +}); diff --git a/packages/components/src/renderers/form/__tests__/form-builtin-select-placeholder.test.tsx b/packages/components/src/renderers/form/__tests__/form-builtin-select-placeholder.test.tsx new file mode 100644 index 0000000000..7762ce6086 --- /dev/null +++ b/packages/components/src/renderers/form/__tests__/form-builtin-select-placeholder.test.tsx @@ -0,0 +1,88 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * The built-in `select` branch has ONE placeholder source — objectui#3272. + * + * The branch used to render `placeholder || 'Select an option'` behind a call + * site that already supplies `t('common.selectOption')`, so the literal read + * as unreachable dead code. It was not quite: the call site defaults with `??`, + * which PRESERVES an authored `placeholder: ''`, and an empty string is falsy + * — so the one stack that reached the literal was an author who deliberately + * asked for a blank placeholder and got an untranslated English word instead. + * + * That is also the honest reverse-verification direction for this deletion: + * restoring `|| 'Select an option'` turns the `placeholder: ''` case below red + * and leaves the other two green, because those two never reached the literal + * in the first place. They are here as the surviving pin — if a later refactor + * drops the `t()` at the call site, there is no longer a second fallback to + * mask it, and the zh case fails instead of quietly rendering English. + */ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { ComponentRegistry } from '@object-ui/core'; +import { I18nProvider } from '@object-ui/i18n'; +// Module scope, not `beforeAll` (objectui#3010/#3021). +import '../../../renderers'; + +const options = [ + { label: 'Zhejiang', value: 'zj' }, + { label: 'California', value: 'ca' }, +]; + +function renderSelectIn(language: string, fieldExtra: Record = {}) { + const Form = ComponentRegistry.get('form')!; + return render( + + + , + ); +} + +describe('form renderer — built-in select placeholder (objectui#3272)', () => { + it('uses the locale word when the author declared no placeholder (zh)', () => { + renderSelectIn('zh'); + + expect(screen.getByText('请选择')).toBeInTheDocument(); + expect(screen.queryByText('Select an option')).not.toBeInTheDocument(); + }); + + it('uses the English word under an en provider', () => { + renderSelectIn('en'); + + expect(screen.getByText('Select an option')).toBeInTheDocument(); + }); + + it('honours an authored placeholder verbatim, in any locale', () => { + renderSelectIn('zh', { placeholder: 'Pick a province' }); + + expect(screen.getByText('Pick a province')).toBeInTheDocument(); + expect(screen.queryByText('请选择')).not.toBeInTheDocument(); + expect(screen.queryByText('Select an option')).not.toBeInTheDocument(); + }); + + it('honours an authored EMPTY placeholder instead of substituting English', () => { + // The stack the deleted literal actually reached: `?? ` at the call site + // keeps `''`, then `||` in the branch overrode it. Asserted in BOTH + // locales because the defect was locale-independent — an `en` author who + // asked for a blank placeholder was overridden just the same. + renderSelectIn('zh', { placeholder: '' }); + expect(screen.queryByText('Select an option')).not.toBeInTheDocument(); + expect(screen.queryByText('请选择')).not.toBeInTheDocument(); + + renderSelectIn('en', { placeholder: '' }); + expect(screen.queryByText('Select an option')).not.toBeInTheDocument(); + }); +}); diff --git a/packages/components/src/renderers/form/__tests__/form-fullscreen-textarea-i18n.test.tsx b/packages/components/src/renderers/form/__tests__/form-fullscreen-textarea-i18n.test.tsx new file mode 100644 index 0000000000..6c80969fc9 --- /dev/null +++ b/packages/components/src/renderers/form/__tests__/form-fullscreen-textarea-i18n.test.tsx @@ -0,0 +1,125 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * The fullscreen long-text editor speaks the session locale — objectui#3272. + * + * `FullscreenTextarea` is a whole dialog (title, sr-only description, footer + * buttons) plus the trigger's accessible name, and NONE of it went through + * `t()`: a zh/ja/ar session opened a dialog that read "Edit text" / "Cancel" / + * "Done" while every other sentence in the same form was translated. + * + * The fixture spells the flag `mobile_fullscreen` — the one carrier + * `ObjectForm` actually produces (#3245/#3300). The built-in branch also reads + * an aliased `fullscreen`, but that alias has no producer and is #3303's to + * remove; pinning the canonical spelling keeps this suite green either way. + * + * Every locale case asserts the English literal is GONE as well as that the + * translation is present: a re-inlined literal alongside a translated sibling + * would still satisfy a positive-only assertion. + */ +import { describe, it, expect } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { ComponentRegistry } from '@object-ui/core'; +import { I18nProvider } from '@object-ui/i18n'; +// Registered at module scope, NOT in a `beforeAll` — there the cold transform +// is billed to `hookTimeout` (objectui#3010/#3021). +import '../../../renderers'; + +const fields = [ + { name: 'notes', label: 'Notes', type: 'textarea', mobile_fullscreen: true }, +]; + +function renderFormIn(language: string) { + const Form = ComponentRegistry.get('form')!; + return render( + + + , + ); +} + +/** Open the dialog and hand back the trigger, so its name can be asserted too. */ +function openFullscreen() { + const toggle = screen.getByTestId('form-textarea-fullscreen-toggle'); + fireEvent.click(toggle); + return toggle; +} + +describe('form renderer — fullscreen textarea dialog is translated (objectui#3272)', () => { + it('renders the English copy under an en provider', () => { + renderFormIn('en'); + const toggle = openFullscreen(); + + // Byte-identical to the literals this replaced, so `en` is a no-op change. + expect(toggle).toHaveAttribute('aria-label', 'Edit text fullscreen'); + expect(screen.getByText('Edit text')).toBeInTheDocument(); + expect( + screen.getByText('Edit the full text value, then save or cancel your changes.'), + ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Done' })).toBeInTheDocument(); + }); + + it('renders the whole dialog in Chinese under a zh provider', () => { + renderFormIn('zh'); + const toggle = openFullscreen(); + + expect(toggle).toHaveAttribute('aria-label', '全屏编辑文本'); + expect(screen.getByText('编辑文本')).toBeInTheDocument(); + expect(screen.getByText('编辑完整的文本内容,然后保存或取消更改。')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '取消' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '完成' })).toBeInTheDocument(); + + // The four literals, asserted absent. `Edit text` is matched loosely + // because it was BOTH the dialog title and part of the trigger's name. + expect(screen.queryByText(/edit text/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/save or cancel your changes/i)).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Cancel' })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Done' })).not.toBeInTheDocument(); + expect(toggle.getAttribute('aria-label')).not.toMatch(/fullscreen/i); + }); + + it('renders the whole dialog in Japanese under a ja provider', () => { + // A second non-en pack because the trigger's name is INTERPOLATED + // (`{{label}}` + a translated generic noun): ja puts the noun first + // ("テキストを全画面で編集"), en last. A pack that dropped the + // interpolation would still pass a zh-only assertion by accident. + renderFormIn('ja'); + const toggle = openFullscreen(); + + expect(toggle).toHaveAttribute('aria-label', 'テキストを全画面で編集'); + expect(screen.getByText('テキストを編集')).toBeInTheDocument(); + expect( + screen.getByText('テキスト全体を編集してから、変更を保存またはキャンセルしてください。'), + ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'キャンセル' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '完了' })).toBeInTheDocument(); + + expect(screen.queryByText(/edit text/i)).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Done' })).not.toBeInTheDocument(); + }); + + it('still commits the draft — translating the footer did not unwire it', () => { + // The `Done` button lost its literal child; this pins that the click + // handler still rides on the translated button rather than on some other + // node that happened to carry the old text. + renderFormIn('zh'); + openFullscreen(); + + fireEvent.change(screen.getByTestId('form-textarea-fullscreen-input'), { + target: { value: 'hello' }, + }); + fireEvent.click(screen.getByRole('button', { name: '完成' })); + + expect(screen.queryByTestId('form-textarea-fullscreen-dialog')).not.toBeInTheDocument(); + expect(screen.getByLabelText('Notes')).toHaveValue('hello'); + }); +}); diff --git a/packages/components/src/renderers/form/form.tsx b/packages/components/src/renderers/form/form.tsx index 59c1de8d33..eb8cf43880 100644 --- a/packages/components/src/renderers/form/form.tsx +++ b/packages/components/src/renderers/form/form.tsx @@ -110,6 +110,33 @@ const panePercent = (size: number | undefined): string | undefined => const useSafeFormTranslation = createSafeTranslation( { 'common.selectOption': 'Select an option', + // objectui#3272 — the action bar's button copy when the schema authored no + // `submitLabel` / `cancelLabel`. Deliberately the SHARED `common.*` keys + // rather than new `form.*` twins: "Submit" and "Cancel" are the same words + // the rest of the console already ships in ten packs, and a second copy of + // a one-word string is exactly the drift #3231/#3263 had to undo. The + // defaults here are byte-identical to the English literals they replaced, + // so a form with no I18nProvider renders what it always did. + 'common.submit': 'Submit', + 'common.cancel': 'Cancel', + // objectui#3272 — the fullscreen long-text editor (`mobile_fullscreen`). + // A whole dialog, not a word: title, its sr-only description, the footer's + // confirm button, and the trigger's accessible name. `Cancel` in the footer + // reuses `common.cancel` above for the same reason. + 'form.fullscreen.title': 'Edit text', + 'form.fullscreen.description': 'Edit the full text value, then save or cancel your changes.', + 'form.fullscreen.done': 'Done', + // The trigger's accessible name keeps the literal's shape — + // `Edit ${label ?? 'text'} fullscreen` — as ONE sentence with a + // TRANSLATED generic noun standing in for a missing label. The obvious + // alternative (a second, label-less sentence key) would ship dead in all + // ten packs: `label` is destructured off the field config before + // `...fieldProps`, so the only caller never forwards it and the labelled + // limb is unreachable today (filed separately). Interpolating a + // translated noun keeps BOTH keys on the live path while leaving the + // limb's fate to that issue. + 'form.fullscreen.toggle': 'Edit {{label}} fullscreen', + 'form.fullscreen.textFallback': 'text', // objectui#3231 — the dependency-gate sentence (#2284). Shared with the // option widgets' own fallback so both sides render one wording. 'fields.options.selectFirst': 'Select {{fields}} first', @@ -342,6 +369,10 @@ function FullscreenTextarea({ label?: string; [key: string]: any; }) { + // A real component (rendered as ``), so the hook runs + // unconditionally — unlike `renderFieldComponent`, the plain helper below + // that had to grow `BuiltinSelectEmptyState` to own its own hook (#3263). + const { t } = useSafeFormTranslation(); const [open, setOpen] = React.useState(false); const [draft, setDraft] = React.useState(value ?? ''); const safeOnChange = (v: string) => onChange && onChange(v); @@ -360,7 +391,9 @@ function FullscreenTextarea({ type="button" onClick={openDialog} className="absolute top-1.5 right-1.5 inline-flex items-center justify-center size-7 rounded-md bg-background/80 text-muted-foreground hover:text-foreground hover:bg-background border shadow-sm" - aria-label={`Edit ${label ?? 'text'} fullscreen`} + aria-label={t('form.fullscreen.toggle', { + label: label ?? t('form.fullscreen.textFallback'), + })} data-testid="form-textarea-fullscreen-toggle" > @@ -371,9 +404,9 @@ function FullscreenTextarea({ data-testid="form-textarea-fullscreen-dialog" > - {label ?? 'Edit text'} + {label ?? t('form.fullscreen.title')} - Edit the full text value, then save or cancel your changes. + {t('form.fullscreen.description')}
@@ -388,10 +421,10 @@ function FullscreenTextarea({
@@ -407,8 +440,16 @@ ComponentRegistry.register('form', const { defaultValues: authoredDefaultValues = {}, fields: rawFields = [], - submitLabel = 'Submit', - cancelLabel = 'Cancel', + // No English default here — objectui#3272. A literal default made the + // absence of an authored label indistinguishable from an author who + // typed "Submit", and froze every un-labelled form's buttons to English + // in a zh/ja/ar session. The default is now the ABSENCE of a value, and + // the fallback happens at render through `t()` (see the action bar + // below), so `submitLabel?: string` staying optional in the spec means + // exactly what it says: unset = "whatever this session's language calls + // it". An authored value still wins verbatim, including `''`. + submitLabel, + cancelLabel, showCancel = false, showSubmit = true, layout = 'vertical', @@ -1719,7 +1760,10 @@ ComponentRegistry.register('form', disabled={isSubmitting || disabled} className="w-full sm:w-auto" > - {cancelLabel} + {/* `??`, not `||`: an authored `cancelLabel: ''` is an + explicit choice (an icon-only / deliberately blank + button), not "unset". Only null/undefined falls back. */} + {cancelLabel ?? t('common.cancel')} )} {showSubmit && ( @@ -1729,7 +1773,7 @@ ComponentRegistry.register('form', className="w-full sm:w-auto" > {isSubmitting && } - {submitLabel} + {submitLabel ?? t('common.submit')} )} @@ -2044,7 +2088,13 @@ function renderFieldComponent(type: string, props: RenderFieldProps) { {...selectProps} > - + {/* No `|| 'Select an option'` — objectui#3272. The single call site + already supplies `t('common.selectOption')` for `select`, so the + literal was all but unreachable; the ONE stack that did reach it + was an authored `placeholder: ''` (the call site's `??` keeps an + empty string), where a second fallback overrode the author's + explicit blank with an untranslated English word. */} + {options.map((opt: SelectOption) => ( diff --git a/packages/i18n/src/locales/ar.ts b/packages/i18n/src/locales/ar.ts index 4025573946..76f2ea2965 100644 --- a/packages/i18n/src/locales/ar.ts +++ b/packages/i18n/src/locales/ar.ts @@ -135,6 +135,13 @@ const ar = { createSuccess: "تم إنشاء {{object}}", updateSuccess: "تم تحديث {{object}}", deleteSuccess: "تم حذف {{object}}", + fullscreen: { + title: "تحرير النص", + description: "حرّر قيمة النص الكاملة، ثم احفظ التغييرات أو ألغِها.", + done: "تم", + toggle: "تحرير {{label}} بملء الشاشة", + textFallback: "النص", + }, }, fields: { image: { diff --git a/packages/i18n/src/locales/de.ts b/packages/i18n/src/locales/de.ts index 494f0384b6..fb43b8a6e6 100644 --- a/packages/i18n/src/locales/de.ts +++ b/packages/i18n/src/locales/de.ts @@ -135,6 +135,13 @@ const de = { createSuccess: "{{object}} erfolgreich erstellt", updateSuccess: "{{object}} erfolgreich aktualisiert", deleteSuccess: "{{object}} erfolgreich gelöscht", + fullscreen: { + title: "Text bearbeiten", + description: "Bearbeiten Sie den vollständigen Textwert und speichern oder verwerfen Sie dann Ihre Änderungen.", + done: "Fertig", + toggle: "{{label}} im Vollbild bearbeiten", + textFallback: "Text", + }, }, fields: { image: { diff --git a/packages/i18n/src/locales/en.ts b/packages/i18n/src/locales/en.ts index e09326098f..3fe872f9e4 100644 --- a/packages/i18n/src/locales/en.ts +++ b/packages/i18n/src/locales/en.ts @@ -146,6 +146,13 @@ const en = { createSuccess: '{{object}} created successfully', updateSuccess: '{{object}} updated successfully', deleteSuccess: '{{object}} deleted successfully', + fullscreen: { + title: 'Edit text', + description: 'Edit the full text value, then save or cancel your changes.', + done: 'Done', + toggle: 'Edit {{label}} fullscreen', + textFallback: 'text', + }, }, fields: { relativeDate: { diff --git a/packages/i18n/src/locales/es.ts b/packages/i18n/src/locales/es.ts index 99510c3ca0..8ce1e96e20 100644 --- a/packages/i18n/src/locales/es.ts +++ b/packages/i18n/src/locales/es.ts @@ -140,6 +140,13 @@ const es = { createSuccess: "{{object}} creado", updateSuccess: "{{object}} actualizado", deleteSuccess: "{{object}} eliminado", + fullscreen: { + title: "Editar texto", + description: "Edita el valor de texto completo y luego guarda o cancela los cambios.", + done: "Listo", + toggle: "Editar {{label}} en pantalla completa", + textFallback: "texto", + }, }, fields: { image: { diff --git a/packages/i18n/src/locales/fr.ts b/packages/i18n/src/locales/fr.ts index 78f382475d..2961c8f1c2 100644 --- a/packages/i18n/src/locales/fr.ts +++ b/packages/i18n/src/locales/fr.ts @@ -135,6 +135,13 @@ const fr = { createSuccess: "{{object}} créé", updateSuccess: "{{object}} mis à jour", deleteSuccess: "{{object}} supprimé", + fullscreen: { + title: "Modifier le texte", + description: "Modifiez la valeur texte complète, puis enregistrez ou annulez vos modifications.", + done: "Terminé", + toggle: "Modifier {{label}} en plein écran", + textFallback: "le texte", + }, }, fields: { image: { diff --git a/packages/i18n/src/locales/ja.ts b/packages/i18n/src/locales/ja.ts index e8098e34d6..d068cb0d92 100644 --- a/packages/i18n/src/locales/ja.ts +++ b/packages/i18n/src/locales/ja.ts @@ -135,6 +135,13 @@ const ja = { createSuccess: "{{object}}が作成されました", updateSuccess: "{{object}}が更新されました", deleteSuccess: "{{object}}が削除されました", + fullscreen: { + title: "テキストを編集", + description: "テキスト全体を編集してから、変更を保存またはキャンセルしてください。", + done: "完了", + toggle: "{{label}}を全画面で編集", + textFallback: "テキスト", + }, }, fields: { image: { diff --git a/packages/i18n/src/locales/ko.ts b/packages/i18n/src/locales/ko.ts index de854c7511..f134ac0ede 100644 --- a/packages/i18n/src/locales/ko.ts +++ b/packages/i18n/src/locales/ko.ts @@ -135,6 +135,13 @@ const ko = { createSuccess: "{{object}} 생성됨", updateSuccess: "{{object}} 업데이트됨", deleteSuccess: "{{object}} 삭제됨", + fullscreen: { + title: "텍스트 편집", + description: "전체 텍스트 값을 편집한 다음 변경 사항을 저장하거나 취소하세요.", + done: "완료", + toggle: "{{label}} 전체 화면으로 편집", + textFallback: "텍스트", + }, }, fields: { image: { diff --git a/packages/i18n/src/locales/pt.ts b/packages/i18n/src/locales/pt.ts index d7a395988a..8a74482841 100644 --- a/packages/i18n/src/locales/pt.ts +++ b/packages/i18n/src/locales/pt.ts @@ -135,6 +135,13 @@ const pt = { createSuccess: "{{object}} criado", updateSuccess: "{{object}} atualizado", deleteSuccess: "{{object}} excluído", + fullscreen: { + title: "Editar texto", + description: "Edite o valor de texto completo e depois salve ou cancele as alterações.", + done: "Concluído", + toggle: "Editar {{label}} em tela cheia", + textFallback: "texto", + }, }, fields: { image: { diff --git a/packages/i18n/src/locales/ru.ts b/packages/i18n/src/locales/ru.ts index aa59f70a7d..e5c216bee1 100644 --- a/packages/i18n/src/locales/ru.ts +++ b/packages/i18n/src/locales/ru.ts @@ -135,6 +135,13 @@ const ru = { createSuccess: "{{object}} создан", updateSuccess: "{{object}} обновлён", deleteSuccess: "{{object}} удалён", + fullscreen: { + title: "Изменить текст", + description: "Измените полное текстовое значение, затем сохраните или отмените изменения.", + done: "Готово", + toggle: "Редактировать {{label}} в полноэкранном режиме", + textFallback: "текст", + }, }, fields: { image: { diff --git a/packages/i18n/src/locales/zh.ts b/packages/i18n/src/locales/zh.ts index 22df664918..f04dd99d61 100644 --- a/packages/i18n/src/locales/zh.ts +++ b/packages/i18n/src/locales/zh.ts @@ -140,6 +140,13 @@ const zh = { createSuccess: '{{object}}创建成功', updateSuccess: '{{object}}更新成功', deleteSuccess: '{{object}}删除成功', + fullscreen: { + title: '编辑文本', + description: '编辑完整的文本内容,然后保存或取消更改。', + done: '完成', + toggle: '全屏编辑{{label}}', + textFallback: '文本', + }, }, fields: { relativeDate: {