diff --git a/dev/studio-e2e-testing/i18n/bundles.ts b/dev/studio-e2e-testing/i18n/bundles.ts new file mode 100644 index 00000000000..50290cd84b4 --- /dev/null +++ b/dev/studio-e2e-testing/i18n/bundles.ts @@ -0,0 +1,12 @@ +import {defineLocaleResourceBundle} from 'sanity' +import {testStudioLocaleNamespace} from 'sanity-test-studio/locales' + +export const e2eI18nBundles = [ + defineLocaleResourceBundle({ + locale: 'en-US', + namespace: testStudioLocaleNamespace, + resources: { + 'field-groups.group-1': '🇺🇸 Group 1', + }, + }), +] diff --git a/dev/studio-e2e-testing/sanity.config.ts b/dev/studio-e2e-testing/sanity.config.ts index 507334524f9..74542540feb 100644 --- a/dev/studio-e2e-testing/sanity.config.ts +++ b/dev/studio-e2e-testing/sanity.config.ts @@ -1,7 +1,7 @@ import {googleMapsInput} from '@sanity/google-maps-input' import {BookIcon} from '@sanity/icons' import {visionTool} from '@sanity/vision' -import {defineConfig, definePlugin} from 'sanity' +import {defineConfig} from 'sanity' import {structureTool} from 'sanity/structure' import {muxInput} from 'sanity-plugin-mux-input' import {imageAssetSource} from 'sanity-test-studio/assetSources' @@ -16,10 +16,18 @@ import {presenceTool} from 'sanity-test-studio/plugins/presence' import {defaultDocumentNode, newDocumentOptions, structure} from 'sanity-test-studio/structure' import {customComponents} from './components-api' +import {e2eI18nBundles} from './i18n/bundles' import {schemaTypes} from './schemaTypes' -const sharedSettings = definePlugin({ - name: 'sharedSettings', +export default defineConfig({ + name: 'default', + title: 'studio-e2e-testing', + + projectId: process.env.SANITY_E2E_PROJECT_ID!, + dataset: process.env.SANITY_E2E_DATASET!, + + basePath: '/test', + schema: { types: schemaTypes, templates: resolveInitialValueTemplates, @@ -30,6 +38,10 @@ const sharedSettings = definePlugin({ }, }, + i18n: { + bundles: e2eI18nBundles, + }, + document: { actions: documentActions, inspectors: (prev, ctx) => { @@ -90,14 +102,3 @@ const sharedSettings = definePlugin({ }, }, }) - -export default defineConfig({ - name: 'default', - title: 'studio-e2e-testing', - - projectId: process.env.SANITY_E2E_PROJECT_ID!, - dataset: process.env.SANITY_E2E_DATASET!, - - plugins: [sharedSettings()], - basePath: '/test', -}) diff --git a/dev/test-studio/locales/index.ts b/dev/test-studio/locales/index.ts index 0a60e5d9b73..6f24f3d8c69 100644 --- a/dev/test-studio/locales/index.ts +++ b/dev/test-studio/locales/index.ts @@ -11,6 +11,10 @@ const enUSStrings = { 'translate.with-formatter': 'This value has a list-formatter: {{countries, list}}', 'use-translation.with-html': 'Apparently, code is an HTML element?', 'use-translation.interpolation-example': 'This has {{ spaces }} around it, this one {{doesNot}}', + + // Used by `fieldGroupsWithI18n` debug schema type + 'field-groups.group-1': '🇺🇸 Group 1', + 'field-groups.group-2': '🇺🇸 Group 2', } const enUS = defineLocaleResourceBundle({ @@ -29,6 +33,10 @@ const nbNO = defineLocaleResourceBundle({ 'translate.with-xml-in-value': 'Denne verdien har XML i en interpolert verdi: {{value}}', 'use-translation.with-html': 'Faktisk er code et HTML-element?', + + // Used by `fieldGroupsWithI18n` debug schema type + 'field-groups.group-1': '🇳🇴 Gruppe 1', + 'field-groups.group-2': '🇳🇴 Gruppe 2', }, }) @@ -40,6 +48,14 @@ const nbNOBStructureOverrides = defineLocaleResourceBundle({ }, }) +const enUSStudioOverrides = defineLocaleResourceBundle({ + locale: 'en-US', + namespace: 'studio', + resources: { + //'inputs.object.field-group-tabs.all-fields-title': 'அனைத்து துறைகள்', + }, +}) + export type TestStudioLocaleResourceKeys = keyof typeof enUSStrings -export const testStudioLocaleBundles = [enUS, nbNO, nbNOBStructureOverrides] +export const testStudioLocaleBundles = [enUS, nbNO, nbNOBStructureOverrides, enUSStudioOverrides] diff --git a/dev/test-studio/schema/debug/fieldGroupsWithI18n.ts b/dev/test-studio/schema/debug/fieldGroupsWithI18n.ts new file mode 100644 index 00000000000..21bf0586bfa --- /dev/null +++ b/dev/test-studio/schema/debug/fieldGroupsWithI18n.ts @@ -0,0 +1,32 @@ +import {defineType} from 'sanity' + +import {testStudioLocaleNamespace} from '../../locales' + +export default defineType({ + name: 'fieldGroupsWithI18n', + title: 'With i18n', + type: 'document', + groups: [ + { + name: 'i18n-group1', + title: 'I18N-MISSING (1)', + i18n: {title: {key: 'field-groups.group-1', ns: testStudioLocaleNamespace}}, + }, + { + name: 'i18n-group2', + title: 'I18N-MISSING (2)', + i18n: {title: {key: 'intentionally-missing-key', ns: testStudioLocaleNamespace}}, + }, + { + name: 'non-i18n-group3', + title: '🌐 Non-i18n group', + }, + ], + fields: [ + {name: 'field1', type: 'string', group: 'i18n-group1'}, + {name: 'field2', type: 'string', group: 'i18n-group2'}, + {name: 'field3', type: 'string', group: 'i18n-group1'}, + {name: 'field4', type: 'string', group: ['i18n-group1', 'i18n-group2']}, + {name: 'field5', type: 'string', group: 'non-i18n-group3'}, + ], +}) diff --git a/dev/test-studio/schema/index.ts b/dev/test-studio/schema/index.ts index 6645b8947ab..7edf1e4a58f 100644 --- a/dev/test-studio/schema/index.ts +++ b/dev/test-studio/schema/index.ts @@ -32,6 +32,7 @@ import fieldGroupsDefault from './debug/fieldGroupsDefault' import fieldGroupsMany from './debug/fieldGroupsMany' import fieldGroupsWithFieldsets from './debug/fieldGroupsWithFieldsets' import fieldGroupsWithFieldsetsAndValidation from './debug/fieldGroupsWithFieldsetsAndValidation' +import fieldGroupsWithI18n from './debug/fieldGroupsWithI18n' import fieldGroupsWithValidation from './debug/fieldGroupsWithValidation' import fieldsets from './debug/fieldsets' import { @@ -249,6 +250,7 @@ export const schemaTypes = [ fieldGroups, fieldGroupsDefault, fieldGroupsMany, + fieldGroupsWithI18n, fieldGroupsWithValidation, fieldGroupsWithFieldsetsAndValidation, virtualizationInObject, diff --git a/dev/test-studio/structure/constants.ts b/dev/test-studio/structure/constants.ts index a4e68bc8963..fbbc480ed0d 100644 --- a/dev/test-studio/structure/constants.ts +++ b/dev/test-studio/structure/constants.ts @@ -94,6 +94,7 @@ export const DEBUG_FIELD_GROUP_TYPES = [ 'fieldGroups', 'fieldGroupsDefault', 'fieldGroupsMany', + 'fieldGroupsWithI18n', 'fieldGroupsWithValidation', 'fieldGroupsWithFieldsets', 'fieldGroupsWithFieldsetsAndValidation', diff --git a/packages/@sanity/types/src/schema/definition/type/common.ts b/packages/@sanity/types/src/schema/definition/type/common.ts index 66d06e4f6db..91e1c638965 100644 --- a/packages/@sanity/types/src/schema/definition/type/common.ts +++ b/packages/@sanity/types/src/schema/definition/type/common.ts @@ -1,6 +1,6 @@ import {type ComponentType, type ReactElement, type ReactNode} from 'react' -import {type ConditionalProperty, type DeprecatedProperty} from '../../types' +import {type ConditionalProperty, type DeprecatedProperty, type I18nTextRecord} from '../../types' import {type ObjectOptions} from './object' /** @public */ @@ -21,6 +21,7 @@ export type FieldGroupDefinition = { hidden?: ConditionalProperty icon?: ComponentType default?: boolean + i18n?: I18nTextRecord<'title'> } /** @public */ diff --git a/packages/@sanity/types/src/schema/types.ts b/packages/@sanity/types/src/schema/types.ts index 6c80c216593..9c1982af651 100644 --- a/packages/@sanity/types/src/schema/types.ts +++ b/packages/@sanity/types/src/schema/types.ts @@ -105,7 +105,7 @@ export interface SortOrderingItem { direction: 'asc' | 'desc' } -/** @beta */ +/** @public */ export type I18nTextRecord = {[P in K]?: {key: string; ns: string}} /** @beta */ @@ -388,6 +388,7 @@ export interface FieldGroup { icon?: ComponentType title?: string description?: string + i18n?: I18nTextRecord<'title'> hidden?: ConditionalProperty default?: boolean fields?: ObjectField[] diff --git a/packages/sanity/src/core/form/inputs/ObjectInput/fieldGroups/FieldGroupTabs.tsx b/packages/sanity/src/core/form/inputs/ObjectInput/fieldGroups/FieldGroupTabs.tsx index 2e50f64b8d4..9970d6a78e6 100644 --- a/packages/sanity/src/core/form/inputs/ObjectInput/fieldGroups/FieldGroupTabs.tsx +++ b/packages/sanity/src/core/form/inputs/ObjectInput/fieldGroups/FieldGroupTabs.tsx @@ -38,25 +38,32 @@ const GroupTabs = ({ onClick, shouldAutoFocus = true, disabled, -}: FieldGroupTabsProps) => ( - - {groups.map((group) => { - return ( - - ) - })} - -) +}: FieldGroupTabsProps) => { + const {t} = useTranslation() + return ( + + {groups.map((group) => { + const title = group.i18n?.title + ? t(group.i18n.title.key, {ns: group.i18n.title.ns}) + : group.title || group.name + + return ( + + ) + })} + + ) +} /* For small screens, use Select from Sanity UI */ const GroupSelect = ({ @@ -87,7 +94,10 @@ const GroupSelect = ({ value={groups.find((g) => g.selected)?.name} > {groups.map((group) => { - // Separate hidden in order to resolve it to a boolean type + const title = group.i18n?.title + ? t(group.i18n.title.key, {ns: group.i18n.title.ns}) + : group.title || group.name + return ( ) })} diff --git a/packages/sanity/src/core/form/store/constants.ts b/packages/sanity/src/core/form/store/constants.ts index dc82ac710e5..44826d6d40a 100644 --- a/packages/sanity/src/core/form/store/constants.ts +++ b/packages/sanity/src/core/form/store/constants.ts @@ -1,5 +1,7 @@ import {type FieldGroup} from '@sanity/types' +import {studioLocaleNamespace} from '../../i18n/localeNamespaces' + /** * Max supported field depth. Fields deeper than this will be considered hidden. */ @@ -14,4 +16,10 @@ export const ALL_FIELDS_GROUP: FieldGroup = { name: 'all-fields', title: 'All fields', hidden: false, + i18n: { + title: { + key: 'inputs.object.field-group-tabs.all-fields-title', + ns: studioLocaleNamespace, + }, + }, } diff --git a/packages/sanity/src/core/form/store/formState.ts b/packages/sanity/src/core/form/store/formState.ts index b6df396b4ee..6862208ddcd 100644 --- a/packages/sanity/src/core/form/store/formState.ts +++ b/packages/sanity/src/core/form/store/formState.ts @@ -575,6 +575,7 @@ function prepareObjectInputState( name: group.name, selected, title: group.title, + i18n: group.i18n, }, ] }) diff --git a/packages/sanity/src/core/form/store/types/fieldGroup.ts b/packages/sanity/src/core/form/store/types/fieldGroup.ts index 2a2ae67985e..0b5133d88af 100644 --- a/packages/sanity/src/core/form/store/types/fieldGroup.ts +++ b/packages/sanity/src/core/form/store/types/fieldGroup.ts @@ -1,3 +1,4 @@ +import {type I18nTextRecord} from '@sanity/types' import {type ComponentType} from 'react' /** @@ -8,5 +9,6 @@ export interface FormFieldGroup { selected?: boolean disabled?: boolean title?: string + i18n?: I18nTextRecord<'title'> icon?: ComponentType } diff --git a/packages/sanity/src/core/i18n/bundles/studio.ts b/packages/sanity/src/core/i18n/bundles/studio.ts index dcb3626a4d4..5560f1988fb 100644 --- a/packages/sanity/src/core/i18n/bundles/studio.ts +++ b/packages/sanity/src/core/i18n/bundles/studio.ts @@ -660,7 +660,9 @@ export const studioLocaleStrings = defineLocalesResources('studio', { 'inputs.invalid-value.reset-button.text': 'Reset value', /** Invalid property value */ 'inputs.invalid-value.title': 'Invalid property value', - /** Field groups */ + /** Title for the "All fields" field group */ + 'inputs.object.field-group-tabs.all-fields-title': 'All fields', + /** Aria label for the "Field groups" select control on smaller screens */ 'inputs.object.field-group-tabs.aria-label': 'Field groups', /** Read-only field description */ 'inputs.object.unknown-fields.read-only.description': diff --git a/test/e2e/tests/inputs/object.spec.ts b/test/e2e/tests/inputs/object.spec.ts new file mode 100644 index 00000000000..9af6a6e237f --- /dev/null +++ b/test/e2e/tests/inputs/object.spec.ts @@ -0,0 +1,15 @@ +import {expect} from '@playwright/test' +import {test} from '@sanity/test' + +test('fields groups can use/not use i18n titles', async ({page, createDraftDocument}) => { + await createDraftDocument('/test/content/input-debug;field-groups;fieldGroupsWithI18n') + + await expect(await page.getByTestId(`group-tab-i18n-group1`)).toBeVisible() + + // Should be translated (see e2e studio `i18n/bundles`) + await expect(page.getByTestId('group-tab-i18n-group1')).toHaveText('🇺🇸 Group 1') + // Should intentionally not be translated, eg show the missing key + await expect(page.getByTestId('group-tab-i18n-group2')).toHaveText('intentionally-missing-key') + // Should show defined title if no `i18n` key is defined + await expect(page.getByTestId('group-tab-non-i18n-group3')).toHaveText('🌐 Non-i18n group') +})