From d887e8d78f4307d16a3e74d6290c554bfea4dac4 Mon Sep 17 00:00:00 2001 From: Espen Hovlandsdal Date: Tue, 9 Jul 2024 14:30:39 -0700 Subject: [PATCH 1/4] fix(i18n): allow translating field group titles --- .../src/schema/definition/type/common.ts | 3 +- packages/@sanity/types/src/schema/types.ts | 3 +- .../fieldGroups/FieldGroupTabs.tsx | 52 +++++++++++-------- .../sanity/src/core/form/store/constants.ts | 8 +++ .../sanity/src/core/form/store/formState.ts | 1 + .../src/core/form/store/types/fieldGroup.ts | 2 + .../sanity/src/core/i18n/bundles/studio.ts | 4 +- 7 files changed, 49 insertions(+), 24 deletions(-) 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': From 2d4d7b78df15baf56433cfad6d1f484d612147a4 Mon Sep 17 00:00:00 2001 From: Espen Hovlandsdal Date: Tue, 9 Jul 2024 14:48:44 -0700 Subject: [PATCH 2/4] chore(test-studio): add field group translation debug --- dev/test-studio/locales/index.ts | 18 +++++++++++- .../schema/debug/fieldGroupsWithI18n.ts | 29 +++++++++++++++++++ dev/test-studio/schema/index.ts | 2 ++ dev/test-studio/structure/constants.ts | 1 + 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 dev/test-studio/schema/debug/fieldGroupsWithI18n.ts 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..6f8abde9e9b --- /dev/null +++ b/dev/test-studio/schema/debug/fieldGroupsWithI18n.ts @@ -0,0 +1,29 @@ +import {defineType} from 'sanity' + +import {testStudioLocaleNamespace} from '../../locales' + +export default defineType({ + name: 'fieldGroupsWithI18n', + title: 'With i18n', + type: 'document', + groups: [ + { + name: 'group1', + title: 'I18N-MISSING (1)', + i18n: {title: {key: 'field-groups.group-1', ns: testStudioLocaleNamespace}}, + icon: () => '🌎', + }, + { + name: 'group2', + title: 'I18N-MISSING (2)', + i18n: {title: {key: 'field-groups.group-2', ns: testStudioLocaleNamespace}}, + icon: () => '🌍', + }, + ], + fields: [ + {name: 'field1', type: 'string', group: 'group1'}, + {name: 'field2', type: 'string', group: 'group2'}, + {name: 'field3', type: 'string', group: 'group1'}, + {name: 'field4', type: 'string', group: ['group1', 'group2']}, + ], +}) 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', From 53d2cd3304ea7d2676e44a576ea68e122f99aa4a Mon Sep 17 00:00:00 2001 From: Espen Hovlandsdal Date: Tue, 9 Jul 2024 15:01:38 -0700 Subject: [PATCH 3/4] chore(e2e): simplify e2e studio config --- dev/studio-e2e-testing/sanity.config.ts | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/dev/studio-e2e-testing/sanity.config.ts b/dev/studio-e2e-testing/sanity.config.ts index 507334524f9..ab57cd16f40 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' @@ -18,8 +18,15 @@ import {defaultDocumentNode, newDocumentOptions, structure} from 'sanity-test-st import {customComponents} from './components-api' 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, @@ -90,14 +97,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', -}) From 6ab6c18645526c951a49d0158e8d398c9ac8251b Mon Sep 17 00:00:00 2001 From: Espen Hovlandsdal Date: Tue, 9 Jul 2024 15:23:23 -0700 Subject: [PATCH 4/4] test(i18n): add test for field group translations --- dev/studio-e2e-testing/i18n/bundles.ts | 12 +++++++++++ dev/studio-e2e-testing/sanity.config.ts | 5 +++++ .../schema/debug/fieldGroupsWithI18n.ts | 21 +++++++++++-------- test/e2e/tests/inputs/object.spec.ts | 15 +++++++++++++ 4 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 dev/studio-e2e-testing/i18n/bundles.ts create mode 100644 test/e2e/tests/inputs/object.spec.ts 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 ab57cd16f40..74542540feb 100644 --- a/dev/studio-e2e-testing/sanity.config.ts +++ b/dev/studio-e2e-testing/sanity.config.ts @@ -16,6 +16,7 @@ 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' export default defineConfig({ @@ -37,6 +38,10 @@ export default defineConfig({ }, }, + i18n: { + bundles: e2eI18nBundles, + }, + document: { actions: documentActions, inspectors: (prev, ctx) => { diff --git a/dev/test-studio/schema/debug/fieldGroupsWithI18n.ts b/dev/test-studio/schema/debug/fieldGroupsWithI18n.ts index 6f8abde9e9b..21bf0586bfa 100644 --- a/dev/test-studio/schema/debug/fieldGroupsWithI18n.ts +++ b/dev/test-studio/schema/debug/fieldGroupsWithI18n.ts @@ -8,22 +8,25 @@ export default defineType({ type: 'document', groups: [ { - name: 'group1', + name: 'i18n-group1', title: 'I18N-MISSING (1)', i18n: {title: {key: 'field-groups.group-1', ns: testStudioLocaleNamespace}}, - icon: () => '🌎', }, { - name: 'group2', + name: 'i18n-group2', title: 'I18N-MISSING (2)', - i18n: {title: {key: 'field-groups.group-2', ns: testStudioLocaleNamespace}}, - icon: () => '🌍', + i18n: {title: {key: 'intentionally-missing-key', ns: testStudioLocaleNamespace}}, + }, + { + name: 'non-i18n-group3', + title: '🌐 Non-i18n group', }, ], fields: [ - {name: 'field1', type: 'string', group: 'group1'}, - {name: 'field2', type: 'string', group: 'group2'}, - {name: 'field3', type: 'string', group: 'group1'}, - {name: 'field4', type: 'string', group: ['group1', 'group2']}, + {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/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') +})