From e6948c68300bd3f4c8290e8ce2227a8e5823c51f Mon Sep 17 00:00:00 2001 From: Espen Hovlandsdal Date: Wed, 27 Sep 2023 17:58:58 +0100 Subject: [PATCH 1/8] feat(i18n): add `useIntlListFormat` hook Makes it easier to get hold of a list formatter outside of locale resources, which you sometimes need, while ensuring it is using the correct (current) locale, and does not recreate the formatter on every render. --- .../src/core/i18n/hooks/useIntlListFormat.ts | 44 +++++++++++++++++++ packages/sanity/src/core/i18n/index.ts | 1 + 2 files changed, 45 insertions(+) create mode 100644 packages/sanity/src/core/i18n/hooks/useIntlListFormat.ts diff --git a/packages/sanity/src/core/i18n/hooks/useIntlListFormat.ts b/packages/sanity/src/core/i18n/hooks/useIntlListFormat.ts new file mode 100644 index 00000000000..cdce2bc214e --- /dev/null +++ b/packages/sanity/src/core/i18n/hooks/useIntlListFormat.ts @@ -0,0 +1,44 @@ +import {useMemo} from 'react' +import {useCurrentLocale} from './useLocale' + +/** + * Options for the `useIntlListFormat` hook + * + * @public + */ +export interface UseIntlListFormatOptions { + /** + * The format of output message. + * - `conjunction` (read: "and") - default + * - `disjunction` (read: "or") + * - `unit` (just a list) + */ + type?: Intl.ListFormatType | undefined + + /** + * The length of the internationalized message. + * This obviously varies based on language, but in US English this maps to: + * - `long`: "a, b and c" - default + * - `short`: "a, b & c" + * - `narrow`: `a, b, c` + */ + style?: Intl.ListFormatStyle | undefined +} + +/** + * Returns an instance of `Intl.ListFormat` that uses the currently selected locale, + * and enables language-sensitive list formatting. + * + * @param options - Optional options for the list formatter + * @returns Instance of `Intl.ListFormat` + */ +export function useIntlListFormat(options?: UseIntlListFormatOptions): Intl.ListFormat { + const currentLocale = useCurrentLocale() + + // @todo Consider memoizing this "globally", since these can be a little costly to create, + // and the limited set of options makes them highly reusable + return useMemo( + () => new Intl.ListFormat(currentLocale, {style: options?.style, type: options?.type}), + [currentLocale, options?.style, options?.type], + ) +} diff --git a/packages/sanity/src/core/i18n/index.ts b/packages/sanity/src/core/i18n/index.ts index 4fa4a2a03b5..4af203c1302 100644 --- a/packages/sanity/src/core/i18n/index.ts +++ b/packages/sanity/src/core/i18n/index.ts @@ -1,5 +1,6 @@ export * from './hooks/useTranslation' export * from './hooks/useLocale' +export * from './hooks/useIntlListFormat' export * from './components/LocaleProvider' export * from './locales' export * from './Translate' From 03936bfa33325016eed679b7fc10fa2f0e3074c6 Mon Sep 17 00:00:00 2001 From: Espen Hovlandsdal Date: Thu, 28 Sep 2023 15:36:27 -0700 Subject: [PATCH 2/8] feat(i18n): localize reference input --- .../plugins/locale-no-nb/bundles/studio.ts | 64 ++++++++++++++ .../inputs/ReferenceInput/CreateButton.tsx | 16 +++- .../inputs/ReferenceInput/OptionPreview.tsx | 21 ++--- .../ReferenceInput/PreviewReferenceValue.tsx | 88 +++++++++++-------- .../ReferenceInput/ReferenceAutocomplete.tsx | 14 +-- .../inputs/ReferenceInput/ReferenceField.tsx | 22 +++-- .../inputs/ReferenceInput/ReferenceInput.tsx | 21 +++-- .../inputs/ReferenceInput/ReferenceItem.tsx | 42 ++++++--- .../ReferenceInput/useReferenceInput.tsx | 2 +- .../sanity/src/core/i18n/bundles/studio.ts | 68 +++++++++++++- 10 files changed, 275 insertions(+), 83 deletions(-) diff --git a/dev/test-studio/plugins/locale-no-nb/bundles/studio.ts b/dev/test-studio/plugins/locale-no-nb/bundles/studio.ts index c83c1e412eb..6c879afe397 100644 --- a/dev/test-studio/plugins/locale-no-nb/bundles/studio.ts +++ b/dev/test-studio/plugins/locale-no-nb/bundles/studio.ts @@ -101,6 +101,70 @@ const studioResources: Record = { /** Label for selecting a hour preset. Receives a `time` param as a string on hh:mm format and a `date` param as a Date instance denoting the preset date */ 'inputs.datetime.calendar.action.set-to-time-preset': '{{time}} on {{date, datetime}}', + + /** --- Reference (and Cross-Dataset Reference) Input --- */ + + /** Error title for when the search for a reference failed. Note that the message sent by the backend may not be localized. */ + 'inputs.reference.error.search-failed-title': `Referansesøk mislyktes`, + + /** Error title for when the current reference value points to a document that does not exist (on weak references) */ + 'inputs.reference.error.nonexistent-document-title': 'Ikke funnet', + + /** Error description for when the current reference value points to a document that does not exist (on weak references) */ + 'inputs.reference.error.nonexistent-document-description': `Det refererte dokumentet eksisterer ikke (ID: {{documentId}}). Du kan enten fjerne referansen eller erstatte den med et annet dokument.`, + + /** Error title for when the referenced document failed to be loaded */ + 'inputs.reference.error.failed-to-load-document-title': 'Kunne ikke laste referert dokument', + + /** Error title for when the user does not have permissions to read the referenced document */ + 'inputs.reference.error.missing-read-permissions-title': 'Manglende tillatelser', + + /** Error description for when the user does not have permissions to read the referenced document */ + 'inputs.reference.error.missing-read-permissions-description': + 'Det refererte dokumentet kunne ikke åpnes på grunn av manglende tillatelser', + + /** Error title for when the document is unavailable (for any possible reason) */ + 'inputs.reference.error.document-unavailable-title': 'Dokument ikke tilgjengelig', + + /** Error title for when the reference search returned a document that is not an allowed type for the field */ + 'inputs.reference.error.invalid-search-result-type-title': `Søket returnerte en type som ikke er gyldig for denne referansen: "{{returnedType}}"`, + + /** Error title for when the document referenced is not one of the types declared as allowed target types in schema */ + 'inputs.reference.error.invalid-type-title': 'Dokument av ugyldig type', + + /** Error description for when the document referenced is not one of the types declared as allowed target types in schema */ + 'inputs.reference.error.invalid-type-description': `Referert dokument ({{documentId}}) er av type {{actualType}}. Ifølge skjemaet kan refererte dokumenter bare være av type .`, + + /** Placeholder shown in a reference input with no current value */ + 'inputs.reference.search-placeholder': 'Skriv for å søke', + + /** Message shown when no documents were found that matched the given search string */ + 'inputs.reference.no-results-for-query': + 'Ingen resultater for «{{searchString}}»', + + /** Label for action to create a new document from the reference input */ + 'inputs.reference.action.create-new-document': 'Opprett ny', + + /** Label for action to create a new document from the reference input, when there are multiple templates or document types to choose from */ + 'inputs.reference.action-create-new-document-select': 'Opprett ny…', + + /** Label for action to clear the current value of the reference field */ + 'inputs.reference.action.clear': 'Tøm', + + /** Label for action to replace the current value of the field */ + 'inputs.reference.action.replace': 'Erstatt', + + /** Label for action to remove the reference from an array */ + 'inputs.reference.action.remove': 'Fjern', + + /** Label for action to duplicate the current item to a new item (used within arrays) */ + 'inputs.reference.action.duplicate': 'Dupliser', + + /** Label for action to cancel a previously initiated replace action */ + 'inputs.reference.action.replace-cancel': 'Avbryt erstatning', + + /** Label for action that opens the referenced document in a new tab */ + 'inputs.reference.action.open-in-new-tab': 'Åpne i ny fane', } export default studioResources diff --git a/packages/sanity/src/core/form/inputs/ReferenceInput/CreateButton.tsx b/packages/sanity/src/core/form/inputs/ReferenceInput/CreateButton.tsx index 7ce2eb3a16e..8c532b71250 100644 --- a/packages/sanity/src/core/form/inputs/ReferenceInput/CreateButton.tsx +++ b/packages/sanity/src/core/form/inputs/ReferenceInput/CreateButton.tsx @@ -1,8 +1,9 @@ import React, {ComponentProps} from 'react' import {AddIcon} from '@sanity/icons' import {Box, Button, Menu, MenuButton, MenuButtonProps, MenuItem, Tooltip} from '@sanity/ui' +import {useTranslation} from '../../../i18n' import {InsufficientPermissionsMessage} from '../../../components' -import {CreateReferenceOption} from './types' +import type {CreateReferenceOption} from './types' interface Props extends ComponentProps { id: string @@ -27,6 +28,7 @@ const POPOVER_PROPS: MenuButtonProps['popover'] = { export function CreateButton(props: Props) { const {createOptions, onCreate, id, ...rest} = props + const {t} = useTranslation() const canCreateAny = createOptions.some((option) => option.permission.granted) if (!canCreateAny) { return ( @@ -39,7 +41,13 @@ export function CreateButton(props: Props) { > {/* this wrapper div is needed because disabled button doesn't trigger mouse events */}
-
) @@ -51,7 +59,7 @@ export function CreateButton(props: Props) {