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')
+})