From dc2b4c4d295fba25f09ab493aabcd95c1c09bc8e Mon Sep 17 00:00:00 2001 From: Espen Hovlandsdal Date: Wed, 1 Nov 2023 18:22:10 -0700 Subject: [PATCH] feat(i18n): localize global search filters and results --- .../plugins/locale-no-nb/bundles/studio.ts | 419 ++++++++++++++++-- .../src/core/form/inputs/DateInputs/utils.ts | 55 ++- .../sanity/src/core/i18n/bundles/studio.ts | 415 +++++++++++++++-- .../__workshop__/CommonDateRangeStory.tsx | 4 +- .../navbar/search/components/SortMenu.tsx | 7 +- .../search/components/common/FilterLabel.tsx | 98 +++- .../components/filters/common/ButtonValue.tsx | 80 +--- .../filters/filter/OperatorsMenuButton.tsx | 7 +- .../filters/filter/inputs/asset/Asset.tsx | 4 +- .../filter/inputs/date/CommonDateRange.tsx | 42 +- .../filters/filter/inputs/date/DateLast.tsx | 27 +- .../inputs/date/ParsedDateTextInput.tsx | 21 +- .../DateIncludeTimeFooter.tsx | 8 +- .../datePicker/calendar/CalendarHeader.tsx | 26 +- .../datePicker/calendar/CalendarMonth.tsx | 10 +- .../date/datePicker/calendar/constants.ts | 37 +- .../filter/inputs/number/NumberRange.tsx | 32 +- .../recentSearches/item/RecentSearchItem.tsx | 1 + .../search/contexts/search/reducer.test.ts | 2 +- .../operators/arrayOperators.test.ts | 6 +- .../definitions/operators/arrayOperators.ts | 67 ++- .../definitions/operators/assetOperators.ts | 14 +- .../definitions/operators/booleanOperators.ts | 13 +- .../search/definitions/operators/common.ts | 4 +- .../operators/dateOperators.test.ts | 26 +- .../definitions/operators/dateOperators.ts | 105 +++-- .../definitions/operators/definedOperators.ts | 11 +- .../operators/numberOperators.test.ts | 6 +- .../definitions/operators/numberOperators.ts | 46 +- .../definitions/operators/operatorTypes.ts | 31 +- .../operators/portableTextOperators.ts | 23 +- .../operators/referenceOperators.ts | 20 +- .../definitions/operators/slugOperators.ts | 22 +- .../definitions/operators/stringOperators.ts | 33 +- .../navbar/search/definitions/orderings.ts | 10 +- .../studio/components/navbar/search/types.ts | 16 +- 36 files changed, 1239 insertions(+), 509 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 83890d2e876..898466e6f76 100644 --- a/dev/test-studio/plugins/locale-no-nb/bundles/studio.ts +++ b/dev/test-studio/plugins/locale-no-nb/bundles/studio.ts @@ -4,6 +4,61 @@ const studioResources: Record = { /* Relative time, just now */ 'relative-time.just-now': 'akkurat nå', + /** --- Calendar (date input, search filters...) --- */ + + /** Action message for navigating to next month */ + 'calendar.action.go-to-next-month': 'Gå til neste måned', + /** Action message for navigating to previous month */ + 'calendar.action.go-to-previous-month': 'Gå til forrige måned', + /** Action message for navigating to next year */ + 'calendar.action.go-to-next-year': 'Gå til neste år', + /** Action message for navigating to previous year */ + 'calendar.action.go-to-previous-year': 'Gå til forrige år', + /** Action message for setting to the current time */ + 'calendar.action.set-to-current-time': 'Sett til nå', + /** Action message for selecting the hour */ + 'calendar.action.select-hour': 'Velg time', + /** Action message for selecting the minute */ + 'calendar.action.select-minute': 'Velg minutt', + + /** Month names */ + 'calendar.month-names.january': 'Januar', + 'calendar.month-names.february': 'Februar', + 'calendar.month-names.march': 'Mars', + 'calendar.month-names.april': 'April', + 'calendar.month-names.may': 'Mai', + 'calendar.month-names.june': 'Juni', + 'calendar.month-names.july': 'Juli', + 'calendar.month-names.august': 'August', + 'calendar.month-names.september': 'September', + 'calendar.month-names.october': 'Oktober', + 'calendar.month-names.november': 'November', + 'calendar.month-names.december': 'Desember', + + /** Short weekday names */ + 'calendar.weekday-names.short.monday': 'Man', + 'calendar.weekday-names.short.tuesday': 'Tir', + 'calendar.weekday-names.short.wednesday': 'Ons', + 'calendar.weekday-names.short.thursday': 'Tor', + 'calendar.weekday-names.short.friday': 'Fre', + 'calendar.weekday-names.short.saturday': 'Lør', + 'calendar.weekday-names.short.sunday': 'Søn', + + /* Label for navigating the calendar to "today", without _selecting_ today. Short form, eg `Today`, not `Go to today` */ + 'calendar.action.go-to-today': 'I dag', + + /* Accessibility label for navigating the calendar to "today", without _selecting_ today */ + 'calendar.action.go-to-today-aria-label': 'Gå til i dag', + + /** 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 */ + 'calendar.action.set-to-time-preset': '{{time}} on {{date, datetime}}', + + /** Label for switch that controls whether or not to include time in given timestamp */ + 'calendar.action.include-time-label': 'Med klokkeslett', + + /** Error message displayed in calendar when entered date is not the correct format */ + 'calendar.error.must-be-in-format': 'Må være i formatet {{exampleDate}}', + /** --- Review Changes --- */ /** Title for the Review Changes pane */ @@ -112,49 +167,6 @@ const studioResources: Record = { /** Action message for generating the slug */ 'inputs.slug.action.generate': `Generer`, - /** --- DateTime (and Date) Input --- */ - - /** Action message for navigating to next month */ - 'inputs.datetime.calendar.action.go-to-next-month': 'Gå til forrige måned', - /** Action message for navigating to previous month */ - 'inputs.datetime.calendar.action.go-to-previous-month': 'Gå til neste måned', - /** Action message for navigating to next year */ - 'inputs.datetime.calendar.action.go-to-next-year': 'Gå til neste år', - /** Action message for navigating to previous year */ - 'inputs.datetime.calendar.action.go-to-previous-year': 'Gå til forrige år', - /** Action message for setting to the current time */ - 'inputs.datetime.calendar.action.set-to-current-time': 'Sett til nå', - /** Action message for selecting the hour */ - 'inputs.datetime.calendar.action.select-hour': 'Velg time', - /** Action message for selecting the minute */ - 'inputs.datetime.calendar.action.select-minute': 'Velg minutt', - - /** Month names */ - 'inputs.datetime.calendar.month-names.january': 'Januar', - 'inputs.datetime.calendar.month-names.february': 'Februar', - 'inputs.datetime.calendar.month-names.march': 'Mars', - 'inputs.datetime.calendar.month-names.april': 'April', - 'inputs.datetime.calendar.month-names.may': 'Mai', - 'inputs.datetime.calendar.month-names.june': 'Juni', - 'inputs.datetime.calendar.month-names.july': 'Juli', - 'inputs.datetime.calendar.month-names.august': 'August', - 'inputs.datetime.calendar.month-names.september': 'September', - 'inputs.datetime.calendar.month-names.october': 'Oktober', - 'inputs.datetime.calendar.month-names.november': 'November', - 'inputs.datetime.calendar.month-names.december': 'Desember', - - /** Short weekday names */ - 'inputs.datetime.calendar.weekday-names.short.monday': 'Man', - 'inputs.datetime.calendar.weekday-names.short.tuesday': 'Tir', - 'inputs.datetime.calendar.weekday-names.short.wednesday': 'Ons', - 'inputs.datetime.calendar.weekday-names.short.thursday': 'Tor', - 'inputs.datetime.calendar.weekday-names.short.friday': 'Fre', - 'inputs.datetime.calendar.weekday-names.short.saturday': 'Lør', - 'inputs.datetime.calendar.weekday-names.short.sunday': 'Søn', - - /** 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}}', - /** --- File (Image, File and ImageTool) Inputs --- */ /** Open image edit dialog */ @@ -532,6 +544,21 @@ const studioResources: Record = { /** Label for when no document types matching the filter are found */ 'search.document-types-no-matches-found': 'Ingen dokumenttyper funnet', + /** Label for the "Best match" search ordering type */ + 'search.ordering.best-match-label': 'Beste treff', + + /** Label for the "Created: Oldest first" search ordering type */ + 'search.ordering.created-ascending-label': 'Opprettet: Eldste først', + + /** Label for the "Created: Newest first" search ordering type */ + 'search.ordering.created-descending-label': 'Opprettet: Nyeste først', + + /** Label for the "Updated: Oldest first" search ordering type */ + 'search.ordering.updated-ascending-label': 'Oppdatert: Eldste først', + + /** Label for the "Updated: Newest first" search ordering type */ + 'search.ordering.updated-descending-label': 'Oppdatert: Nyeste først', + /** Accessibility label for action to clear all currently applied document type filters */ 'search.action.clear-type-filters-aria-label': 'Fjern valgte filtre', @@ -551,7 +578,7 @@ const studioResources: Record = { 'search.filter-field-tooltip-name': 'Feltnavn', /** Label for "field description" shown in tooltip when navigating list of possible fields to filter */ - 'search.filter-field-tooltip-description': 'Feltopplysninger', + 'search.filter-field-tooltip-description': 'Feltbeskrivelse', /** Label for "Used in document types", a list of the document types a field appears in. Shown in tooltip when navigating list of possible fields to filter */ 'search.filter-field-tooltip-used-in-document-types': 'Brukt i dokumenttyper', @@ -575,10 +602,10 @@ const studioResources: Record = { 'search.filter-number-value-placeholder': 'Verdi', /** Placeholder value for minimum numeric value filter */ - 'search.filter-number-min-value-placeholder': 'Min verdi', + 'search.filter-number-min-value-placeholder': 'Minimumverdi', /** Placeholder value for maximum numeric value filter */ - 'search.filter-number-max-value-placeholder': 'Maks verdi', + 'search.filter-number-max-value-placeholder': 'Maksverdi', /** Label/placeholder prompting user to select one of the predefined, allowed values for a string field */ 'search.filter-string-value-select-predefined-value': 'Velg…', @@ -586,12 +613,306 @@ const studioResources: Record = { /** Label for the action of clearing the currently selected asset in an image/file filter */ 'search.filter-asset-clear': 'Fjern', + /** Label for the action of changing from one image to a different image in asset search filter */ + 'search.filter-asset-change_image': 'Bytt bilde', + + /** Label for the action of changing from one file to a different file in asset search filter */ + 'search.filter-asset-change_file': 'Bytt fil', + + /** Label for the action of selecting an image in asset search filter */ + 'search.filter-asset-select_image': 'Velg bilde', + + /** Label for the action of selecting a file in asset search filter */ + 'search.filter-asset-select_file': 'Velg fil', + /** Label for the action of clearing the currently selected document in a reference filter */ 'search.filter-reference-clear': 'Fjern', - /** Label for search value in a range of numbers */ - // @todo Part of `arrayOperators` - needs `` refactoring - 'search.filter-number-items-range': `{{min}} → {{max}} elementer`, + /** Accessibility label for selecting start date on the date range search filter */ + 'search.filter-date-range-start-date-aria-label': 'Fra dato', + + /** Accessibility label for selecting end date on the date range search filter */ + 'search.filter-date-range-end-date-aria-label': 'Til dato', + + /** Accessibility label for the input value (days/months/years) when adding "X days ago" search filter */ + 'search.filter-date-value-aria-label': 'Enhetsverdi', + + /** Accessibility label for selecting the unit (day/month/year) when adding "X days ago" search filter */ + 'search.filter-date-unit-aria-label': 'Velg enhet', + + /** + * Label for "Days"/"Months"/"Years" when selecting it as unit in "X days ago" search filter. + * Capitalized, as it would be listed in a dropdown. + */ + 'search.filter-date-unit_days': 'Dager', + 'search.filter-date-unit_months': 'Måneder', + 'search.filter-date-unit_years': 'År', + + /** + * Individual search operators. + * + * The `name` variant is the form we use when the user is building a query, and selecting from a + * list of available operators for a field. Keep in mind that since the user knows what the field + * represents, we do not need to contextualize too much, and that the user may not be a developer + * eg prefer "quantity is" over "array has length". Additionally, (if applicable in language) use + * lowercased names. + * + * The `description` variant is the form shown once the filter has enough information to apply, + * and is shown in the list of applied filters. It is passed components that _should_ be used to + * compose the filter string, and to format them correctly: + * + * `` - eg "Bird species", "Category", "Date of birth" + * `operator text` - eg "has ≤", "includes", "is" + * `{{value}}` - eg "Hawk", "Sparrow", "Eagle" + * + * Where applicable, a `count` is passed, allowing you to pluralize where needed, by using + * suffixes such as `_zero`, `_one`, `_other` etc. + * + * Prefer (reasonable) brevity since many filters may be applied. For instance: + * ` has ≤ ` may be better than + * ` has less than eller lik ` + **/ + /* Array should have a count the given filter value */ + 'search.operator.array-count-equal.name': 'antall er', + 'search.operator.array-count-equal.description_one': + ' har {{count}} element', + 'search.operator.array-count-equal.description_other': + ' har {{count}} elementer', + /* Array should have a count greater than given filter value */ + 'search.operator.array-count-gt.name': 'antall større enn', + 'search.operator.array-count-gt.description_one': + ' har > {{count}} element', + 'search.operator.array-count-gt.description_other': + ' har > {{count}} elementer', + /* Array should have a count greater than or the given filter value */ + 'search.operator.array-count-gte.name': 'antall større enn eller lik', + 'search.operator.array-count-gte.description_one': + ' har ≥ {{count}} element', + 'search.operator.array-count-gte.description_other': + ' har ≥ {{count}} elementer', + /* Array should have a count less than given filter value */ + 'search.operator.array-count-lt.name': 'antall mindre enn', + 'search.operator.array-count-lt.description_one': + ' har < {{count}} element', + 'search.operator.array-count-lt.description_other': + ' har < {{count}} elementer', + /* Array should have a count less than or the given filter value */ + 'search.operator.array-count-lte.name': 'antall mindre enn eller lik', + 'search.operator.array-count-lte.description_one': + ' har ≤ {{count}} element', + 'search.operator.array-count-lte.description_other': + ' har ≤ {{count}} elementer', + /* Array should have a count not the given filter value */ + 'search.operator.array-count-not-equal.name': 'antall er ikke', + 'search.operator.array-count-not-equal.description_one': + ' antall er ikke {{count}} element', + 'search.operator.array-count-not-equal.description_other': + ' antall er ikke {{count}} elementer', + /** + * Array should have a count within the range of given filter values. + * Gets passed `{{from}}` and `{{to}}` values. + **/ + 'search.operator.array-count-range.name': 'antall er mellom', + 'search.operator.array-count-range.description': + ' har mellom {{from}} → {{to}} elementer', + /* Array should include the given value */ + 'search.operator.array-list-includes.name': 'inneholder', + 'search.operator.array-list-includes.description': + ' inneholder {{value}}', + /* Array should not include the given value */ + 'search.operator.array-list-not-includes.name': 'inneholder ikke', + 'search.operator.array-list-not-includes.description': + ' inneholder ikke {{value}}', + /* Array should include the given reference */ + 'search.operator.array-reference-includes.name': 'inneholder', + 'search.operator.array-reference-includes.description': + ' inneholder {{value}}', + /* Array should not include the given reference */ + 'search.operator.array-reference-not-includes.name': 'inneholder ikke', + 'search.operator.array-reference-not-includes.description': + ' inneholder ikke {{value}}', + /* Asset (file) should be the selected asset */ + 'search.operator.asset-file-equal.name': 'er', + 'search.operator.asset-file-equal.description': + ' er {{value}}', + /* Asset (file) should not be the selected asset */ + 'search.operator.asset-file-not-equal.name': 'er ikke', + 'search.operator.asset-file-not-equal.description': + ' er ikke {{value}}', + /* Asset (image) should be the selected asset */ + 'search.operator.asset-image-equal.name': 'er', + 'search.operator.asset-image-equal.description': + ' er {{value}}', + /* Asset (image) should not be the selected asset */ + 'search.operator.asset-image-not-equal.name': 'er ikke', + 'search.operator.asset-image-not-equal.description': + ' er ikke {{value}}', + /** + * Boolean value should be the given filter value (true/false). + * Context passed is `true` and `false`, allowing for more specific translations: + * - `search.operator.boolean-equal.description_true` + * - `search.operator.boolean-equal.description_false` + */ + 'search.operator.boolean-equal.name': 'er', + 'search.operator.boolean-equal.description': + ' er {{value}}', + /* Date should be after (later than) given filter value */ + 'search.operator.date-after.name': 'etter', + 'search.operator.date-after.description': + ' er etter {{value}}', + /* Date should be before (earlier than) given filter value */ + 'search.operator.date-before.name': 'før', + 'search.operator.date-before.description': + ' er før {{value}}', + /* Date should be the given filter value */ + 'search.operator.date-equal.name': 'er', + 'search.operator.date-equal.description': + ' er {{value}}', + /* Date should be within the given filter value range (eg "within the last X days") */ + 'search.operator.date-last.name': 'siste', + 'search.operator.date-last.description': + ' er innenfor siste {{value}}', + /* Date should not be the given filter value */ + 'search.operator.date-not-equal.name': 'er ikke', + 'search.operator.date-not-equal.description': + ' er ikke {{value}}', + /* Date should be within the range of given filter values */ + 'search.operator.date-range.name': 'er mellom', + 'search.operator.date-range.description': ' er mellom ', + /* Date and time should be after (later than) given filter value */ + 'search.operator.date-time-after.name': 'etter', + 'search.operator.date-time-after.description': + ' er etter {{value}}', + /* Date and time should be before (earlier than) given filter value */ + 'search.operator.date-time-before.name': 'før', + 'search.operator.date-time-before.description': + ' er før {{value}}', + /* Date and time should be the given filter value */ + 'search.operator.date-time-equal.name': 'er', + 'search.operator.date-time-equal.description': + ' er {{value}}', + /* Date and time should be within the given filter value range (eg "within the last X days") */ + 'search.operator.date-time-last.name': 'siste', + 'search.operator.date-time-last.description': + ' er innenfor siste {{value}}', + /* Date and time should not be the given filter value */ + 'search.operator.date-time-not-equal.name': 'er ikke', + 'search.operator.date-time-not-equal.description': + ' er ikke {{value}}', + /* Date and time should be within the range of given filter values */ + 'search.operator.date-time-range.name': 'er mellom', + 'search.operator.date-time-range.description': ' er mellom ', + /* Value should be defined */ + 'search.operator.defined.name': 'er definert', + 'search.operator.defined.description': ' er definert', + /* Value should not be defined */ + 'search.operator.not-defined.name': 'ikke definert', + 'search.operator.not-defined.description': + ' er ikke definert', + /* Number should be the given filter value */ + 'search.operator.number-equal.name': 'er', + 'search.operator.number-equal.description': + ' er {{value}}', + /* Number should be greater than given filter value */ + 'search.operator.number-gt.name': 'større enn', + 'search.operator.number-gt.description': + ' > {{value}}', + /* Number should be greater than or the given filter value */ + 'search.operator.number-gte.name': 'større enn eller lik', + 'search.operator.number-gte.description': + ' {{value}}', + /* Number should be less than given filter value */ + 'search.operator.number-lt.name': 'mindre enn', + 'search.operator.number-lt.description': + ' < {{value}}', + /* Number should be less than or the given filter value */ + 'search.operator.number-lte.name': 'mindre enn eller lik', + 'search.operator.number-lte.description': + ' {{value}}', + /* Number should not be the given filter value */ + 'search.operator.number-not-equal.name': 'er ikke', + 'search.operator.number-not-equal.description': + ' er ikke {{value}}', + /* Number should be within the range of given filter values */ + 'search.operator.number-range.name': 'er mellom', + 'search.operator.number-range.description': + ' er mellom {{from}} → {{to}}', + /* Portable Text should be the given filter value */ + 'search.operator.portable-text-equal.name': 'er', + 'search.operator.portable-text-equal.description': + ' er {{value}}', + /* Portable Text should contain the given filter value */ + 'search.operator.portable-text-contains.name': 'inneholder', + 'search.operator.portable-text-contains.description': + ' inneholder {{value}}', + /* Portable Text should not be the given filter value */ + 'search.operator.portable-text-not-equal.name': 'er ikke', + 'search.operator.portable-text-not-equal.description': + ' er ikke {{value}}', + /* Portable Text should not contain the given filter value */ + 'search.operator.portable-text-not-contains.name': 'inneholder ikke', + 'search.operator.portable-text-not-contains.description': + ' inneholder ikke {{value}}', + /* Reference should be the given document */ + 'search.operator.reference-equal.name': 'er', + 'search.operator.reference-equal.description': + ' er {{value}}', + /* Reference should not be the given document */ + 'search.operator.reference-not-equal.name': 'er ikke', + 'search.operator.reference-not-equal.description': + ' er ikke {{value}}', + /* References the given asset (file) */ + 'search.operator.reference-asset-file.name': 'fil', + 'search.operator.reference-asset-file.description': + ' {{value}}', + /* References the given asset (image) */ + 'search.operator.reference-asset-image.name': 'bilde', + 'search.operator.reference-asset-image.description': + ' {{value}}', + /* References the given document */ + 'search.operator.reference-document.name': 'dokument', + 'search.operator.reference-document.description': + ' {{value}}', + /* Slug equals the given filter value */ + 'search.operator.slug-equal.name': 'er', + 'search.operator.slug-equal.description': + ' er {{value}}', + /* Slug contains the given value */ + 'search.operator.slug-contains.name': 'inneholder', + 'search.operator.slug-contains.description': + ' inneholder {{value}}', + /* Slug does not equal the given filter value */ + 'search.operator.slug-not-equal.name': 'er ikke', + 'search.operator.slug-not-equal.description': + ' er ikke {{value}}', + /* Slug does not contain the given value */ + 'search.operator.slug-not-contains.name': 'inneholder ikke', + 'search.operator.slug-not-contains.description': + ' inneholder ikke {{value}}', + /* String equals the given filter value */ + 'search.operator.string-equal.name': 'er', + 'search.operator.string-equal.description': + ' er {{value}}', + /* String equals one of the predefined allowed values */ + 'search.operator.string-list-equal.name': 'er', + 'search.operator.string-list-equal.description': + ' er {{value}}', + /* String does not equal one of the predefined allowed values */ + 'search.operator.string-list-not-equal.name': 'er ikke', + 'search.operator.string-list-not-equal.description': + ' er ikke {{value}}', + /* String contains the given filter value */ + 'search.operator.string-contains.name': 'inneholder', + 'search.operator.string-contains.description': + ' inneholder {{value}}', + /* String does not equal the given filter value */ + 'search.operator.string-not-equal.name': 'er ikke', + 'search.operator.string-not-equal.description': + ' er ikke {{value}}', + /* String does not contain the given filter value */ + 'search.operator.string-not-contains.name': 'inneholder ikke', + 'search.operator.string-not-contains.description': + ' inneholder ikke {{value}}', /** Title label for when no search results are found */ 'search.no-results-title': 'Ingen resultater funnet', diff --git a/packages/sanity/src/core/form/inputs/DateInputs/utils.ts b/packages/sanity/src/core/form/inputs/DateInputs/utils.ts index 0708d975ad5..4ae38bb8bda 100644 --- a/packages/sanity/src/core/form/inputs/DateInputs/utils.ts +++ b/packages/sanity/src/core/form/inputs/DateInputs/utils.ts @@ -8,37 +8,36 @@ export function getCalendarLabels( t: (key: string, values?: Record) => string, ): CalendarLabels { return { - goToNextMonth: t('inputs.datetime.calendar.action.go-to-next-month'), - goToPreviousMonth: t('inputs.datetime.calendar.action.go-to-previous-month'), - goToNextYear: t('inputs.datetime.calendar.action.go-to-next-year'), - goToPreviousYear: t('inputs.datetime.calendar.action.go-to-previous-year'), - setToCurrentTime: t('inputs.datetime.calendar.action.set-to-current-time'), - selectHour: t('inputs.datetime.calendar.action.select-hour'), - selectMinute: t('inputs.datetime.calendar.action.select-minute'), + goToNextMonth: t('calendar.action.go-to-next-month'), + goToPreviousMonth: t('calendar.action.go-to-previous-month'), + goToNextYear: t('calendar.action.go-to-next-year'), + goToPreviousYear: t('calendar.action.go-to-previous-year'), + setToCurrentTime: t('calendar.action.set-to-current-time'), + selectHour: t('calendar.action.select-hour'), + selectMinute: t('calendar.action.select-minute'), monthNames: [ - t('inputs.datetime.calendar.month-names.january'), - t('inputs.datetime.calendar.month-names.february'), - t('inputs.datetime.calendar.month-names.march'), - t('inputs.datetime.calendar.month-names.april'), - t('inputs.datetime.calendar.month-names.may'), - t('inputs.datetime.calendar.month-names.june'), - t('inputs.datetime.calendar.month-names.july'), - t('inputs.datetime.calendar.month-names.august'), - t('inputs.datetime.calendar.month-names.september'), - t('inputs.datetime.calendar.month-names.october'), - t('inputs.datetime.calendar.month-names.november'), - t('inputs.datetime.calendar.month-names.december'), + t('calendar.month-names.january'), + t('calendar.month-names.february'), + t('calendar.month-names.march'), + t('calendar.month-names.april'), + t('calendar.month-names.may'), + t('calendar.month-names.june'), + t('calendar.month-names.july'), + t('calendar.month-names.august'), + t('calendar.month-names.september'), + t('calendar.month-names.october'), + t('calendar.month-names.november'), + t('calendar.month-names.december'), ], weekDayNamesShort: [ - t('inputs.datetime.calendar.weekday-names.short.monday'), - t('inputs.datetime.calendar.weekday-names.short.tuesday'), - t('inputs.datetime.calendar.weekday-names.short.wednesday'), - t('inputs.datetime.calendar.weekday-names.short.thursday'), - t('inputs.datetime.calendar.weekday-names.short.friday'), - t('inputs.datetime.calendar.weekday-names.short.saturday'), - t('inputs.datetime.calendar.weekday-names.short.sunday'), + t('calendar.weekday-names.short.monday'), + t('calendar.weekday-names.short.tuesday'), + t('calendar.weekday-names.short.wednesday'), + t('calendar.weekday-names.short.thursday'), + t('calendar.weekday-names.short.friday'), + t('calendar.weekday-names.short.saturday'), + t('calendar.weekday-names.short.sunday'), ], - setToTimePreset: (time, date) => - t('inputs.datetime.calendar.action.set-to-time-preset', {time, date}), + setToTimePreset: (time, date) => t('calendar.action.set-to-time-preset', {time, date}), } } diff --git a/packages/sanity/src/core/i18n/bundles/studio.ts b/packages/sanity/src/core/i18n/bundles/studio.ts index 623d558d287..759baaacab7 100644 --- a/packages/sanity/src/core/i18n/bundles/studio.ts +++ b/packages/sanity/src/core/i18n/bundles/studio.ts @@ -10,6 +10,61 @@ export const studioLocaleStrings = { /* Relative time, just now */ 'relative-time.just-now': 'just now', + /** --- Calendar (date input, search filters...) --- */ + + /** Action message for navigating to next month */ + 'calendar.action.go-to-next-month': 'Go to next month', + /** Action message for navigating to previous month */ + 'calendar.action.go-to-previous-month': 'Go to previous month', + /** Action message for navigating to next year */ + 'calendar.action.go-to-next-year': 'Go to next year', + /** Action message for navigating to previous year */ + 'calendar.action.go-to-previous-year': 'Go to previous year', + /** Action message for setting to the current time */ + 'calendar.action.set-to-current-time': 'Set to current time', + /** Action message for selecting the hour */ + 'calendar.action.select-hour': 'Select hour', + /** Action message for selecting the minute */ + 'calendar.action.select-minute': 'Select minute', + + /** Month names */ + 'calendar.month-names.january': 'January', + 'calendar.month-names.february': 'February', + 'calendar.month-names.march': 'March', + 'calendar.month-names.april': 'April', + 'calendar.month-names.may': 'May', + 'calendar.month-names.june': 'June', + 'calendar.month-names.july': 'July', + 'calendar.month-names.august': 'August', + 'calendar.month-names.september': 'September', + 'calendar.month-names.october': 'October', + 'calendar.month-names.november': 'November', + 'calendar.month-names.december': 'December', + + /** Short weekday names */ + 'calendar.weekday-names.short.monday': 'Mon', + 'calendar.weekday-names.short.tuesday': 'Tue', + 'calendar.weekday-names.short.wednesday': 'Wed', + 'calendar.weekday-names.short.thursday': 'Thu', + 'calendar.weekday-names.short.friday': 'Fri', + 'calendar.weekday-names.short.saturday': 'Sat', + 'calendar.weekday-names.short.sunday': 'Sun', + + /* Label for navigating the calendar to "today", without _selecting_ today. Short form, eg `Today`, not `Go to today` */ + 'calendar.action.go-to-today': 'Today', + + /* Accessibility label for navigating the calendar to "today", without _selecting_ today */ + 'calendar.action.go-to-today-aria-label': 'Go to today', + + /** Label for selecting an 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 */ + 'calendar.action.set-to-time-preset': '{{time}} on {{date, datetime}}', + + /** Label for switch that controls whether or not to include time in given timestamp */ + 'calendar.action.include-time-label': 'Include time', + + /** Error message displayed in calendar when entered date is not the correct format */ + 'calendar.error.must-be-in-format': 'Must be in the format {{exampleDate}}', + /** --- Review Changes --- */ /** Title for the Review Changes pane */ @@ -121,49 +176,6 @@ export const studioLocaleStrings = { /** Action message for generating the slug */ 'inputs.slug.action.generate': `Generate`, - /** --- DateTime (and Date) Input --- */ - - /** Action message for navigating to next month */ - 'inputs.datetime.calendar.action.go-to-next-month': 'Go to next month', - /** Action message for navigating to previous month */ - 'inputs.datetime.calendar.action.go-to-previous-month': 'Go to previous month', - /** Action message for navigating to next year */ - 'inputs.datetime.calendar.action.go-to-next-year': 'Go to next year', - /** Action message for navigating to previous year */ - 'inputs.datetime.calendar.action.go-to-previous-year': 'Go to previous year', - /** Action message for setting to the current time */ - 'inputs.datetime.calendar.action.set-to-current-time': 'Set to current time', - /** Action message for selecting the hour */ - 'inputs.datetime.calendar.action.select-hour': 'Select hour', - /** Action message for selecting the minute */ - 'inputs.datetime.calendar.action.select-minute': 'Select minute', - - /** Month names */ - 'inputs.datetime.calendar.month-names.january': 'January', - 'inputs.datetime.calendar.month-names.february': 'February', - 'inputs.datetime.calendar.month-names.march': 'March', - 'inputs.datetime.calendar.month-names.april': 'April', - 'inputs.datetime.calendar.month-names.may': 'May', - 'inputs.datetime.calendar.month-names.june': 'June', - 'inputs.datetime.calendar.month-names.july': 'July', - 'inputs.datetime.calendar.month-names.august': 'August', - 'inputs.datetime.calendar.month-names.september': 'September', - 'inputs.datetime.calendar.month-names.october': 'October', - 'inputs.datetime.calendar.month-names.november': 'November', - 'inputs.datetime.calendar.month-names.december': 'December', - - /** Short weekday names */ - 'inputs.datetime.calendar.weekday-names.short.monday': 'Mon', - 'inputs.datetime.calendar.weekday-names.short.tuesday': 'Tue', - 'inputs.datetime.calendar.weekday-names.short.wednesday': 'Wed', - 'inputs.datetime.calendar.weekday-names.short.thursday': 'Thu', - 'inputs.datetime.calendar.weekday-names.short.friday': 'Fri', - 'inputs.datetime.calendar.weekday-names.short.saturday': 'Sat', - 'inputs.datetime.calendar.weekday-names.short.sunday': 'Sun', - - /** 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}}', - /** --- File (Image, File and ImageTool) Inputs --- */ /** Open image edit dialog */ @@ -538,6 +550,21 @@ export const studioLocaleStrings = { /** Label for when no document types matching the filter are found */ 'search.document-types-no-matches-found': `No matches for {{filter}}`, + /** Label for the "Best match" search ordering type */ + 'search.ordering.best-match-label': 'Best match', + + /** Label for the "Created: Oldest first" search ordering type */ + 'search.ordering.created-ascending-label': 'Created: Oldest first', + + /** Label for the "Created: Newest first" search ordering type */ + 'search.ordering.created-descending-label': 'Created: Newest first', + + /** Label for the "Updated: Oldest first" search ordering type */ + 'search.ordering.updated-ascending-label': 'Updated: Oldest first', + + /** Label for the "Updated: Newest first" search ordering type */ + 'search.ordering.updated-descending-label': 'Updated: Newest first', + /** Accessibility label for action to clear all currently applied document type filters */ 'search.action.clear-type-filters-aria-label': 'Clear checked filters', @@ -600,12 +627,308 @@ export const studioLocaleStrings = { /** Label for the action of clearing the currently selected asset in an image/file filter */ 'search.filter-asset-clear': 'Clear', + /** Label for the action of changing from one image to a different image in asset search filter */ + 'search.filter-asset-change_image': 'Change image', + + /** Label for the action of changing from one file to a different file in asset search filter */ + 'search.filter-asset-change_file': 'Change file', + + /** Label for the action of selecting an image in asset search filter */ + 'search.filter-asset-select_image': 'Select image', + + /** Label for the action of selecting a file in asset search filter */ + 'search.filter-asset-select_file': 'Select file', + /** Label for the action of clearing the currently selected document in a reference filter */ 'search.filter-reference-clear': 'Clear', - /** Label for search value in a range of numbers */ - // @todo Part of `arrayOperators` - needs `` refactoring - 'search.filter-number-items-range': `{{min}} → {{max}} items`, + /** Accessibility label for selecting start date on the date range search filter */ + 'search.filter-date-range-start-date-aria-label': 'Start date', + + /** Accessibility label for selecting end date on the date range search filter */ + 'search.filter-date-range-end-date-aria-label': 'End date', + + /** Accessibility label for the input value (days/months/years) when adding "X days ago" search filter */ + 'search.filter-date-value-aria-label': 'Unit value', + + /** Accessibility label for selecting the unit (day/month/year) when adding "X days ago" search filter */ + 'search.filter-date-unit-aria-label': 'Select unit', + + /** + * Label for "Days"/"Months"/"Years" when selecting it as unit in "X days ago" search filter. + * Capitalized, as it would be listed in a dropdown. + */ + 'search.filter-date-unit_days': 'Days', + 'search.filter-date-unit_months': 'Months', + 'search.filter-date-unit_years': 'Years', + + /** + * Individual search operators. + * + * The `name` variant is the form we use when the user is building a query, and selecting from a + * list of available operators for a field. Keep in mind that since the user knows what the field + * represents, we do not need to contextualize too much, and that the user may not be a developer + * eg prefer "quantity is" over "array has length". Additionally, (if applicable in language) use + * lowercased names. + * + * The `description` variant is the form shown once the filter has enough information to apply, + * and is shown in the list of applied filters. It is passed components that _should_ be used to + * compose the filter string, and to format them correctly: + * + * `` - eg "Bird species", "Category", "Date of birth" + * `operator text` - eg "has ≤", "includes", "is" + * `{{value}}` - eg "Hawk", "Sparrow", "Eagle" + * + * Where applicable, a `count` is passed, allowing you to pluralize where needed, by using + * suffixes such as `_zero`, `_one`, `_other` etc. + * + * Prefer (reasonable) brevity since many filters may be applied. For instance: + * ` has ≤ ` may be better than + * ` has less than or equal to ` + **/ + /* Array should have a count the given filter value */ + 'search.operator.array-count-equal.name': 'quantity is', + 'search.operator.array-count-equal.description_one': + ' has {{count}} item', + 'search.operator.array-count-equal.description_other': + ' has {{count}} items', + /* Array should have a count greater than given filter value */ + 'search.operator.array-count-gt.name': 'quantity greater than', + 'search.operator.array-count-gt.description_one': + ' has > {{count}} item', + 'search.operator.array-count-gt.description_other': + ' has > {{count}} items', + /* Array should have a count greater than or the given filter value */ + 'search.operator.array-count-gte.name': 'quantity greater than or equal to', + 'search.operator.array-count-gte.description_one': + ' has ≥ {{count}} item', + 'search.operator.array-count-gte.description_other': + ' has ≥ {{count}} items', + /* Array should have a count less than given filter value */ + 'search.operator.array-count-lt.name': 'quantity less than', + 'search.operator.array-count-lt.description_one': + ' has < {{count}} item', + 'search.operator.array-count-lt.description_other': + ' has < {{count}} items', + /* Array should have a count less than or the given filter value */ + 'search.operator.array-count-lte.name': 'quantity less than or equal to', + 'search.operator.array-count-lte.description_one': + ' has ≤ {{count}} item', + 'search.operator.array-count-lte.description_other': + ' has ≤ {{count}} items', + /* Array should have a count not the given filter value */ + 'search.operator.array-count-not-equal.name': 'quantity is not', + 'search.operator.array-count-not-equal.description_one': + ' does not have {{count}} item', + 'search.operator.array-count-not-equal.description_other': + ' does not have {{count}} items', + /** + * Array should have a count within the range of given filter values. + * Gets passed `{{from}}` and `{{to}}` values. + **/ + 'search.operator.array-count-range.name': 'quantity is between', + 'search.operator.array-count-range.description': + ' has between {{from}} → {{to}} items', + /* Array should include the given value */ + 'search.operator.array-list-includes.name': 'includes', + 'search.operator.array-list-includes.description': + ' includes {{value}}', + /* Array should not include the given value */ + 'search.operator.array-list-not-includes.name': 'does not include', + 'search.operator.array-list-not-includes.description': + ' does not include {{value}}', + /* Array should include the given reference */ + 'search.operator.array-reference-includes.name': 'includes', + 'search.operator.array-reference-includes.description': + ' includes {{value}}', + /* Array should not include the given reference */ + 'search.operator.array-reference-not-includes.name': 'does not include', + 'search.operator.array-reference-not-includes.description': + ' does not include {{value}}', + /* Asset (file) should be the selected asset */ + 'search.operator.asset-file-equal.name': 'is', + 'search.operator.asset-file-equal.description': + ' is {{value}}', + /* Asset (file) should not be the selected asset */ + 'search.operator.asset-file-not-equal.name': 'is not', + 'search.operator.asset-file-not-equal.description': + ' is not {{value}}', + /* Asset (image) should be the selected asset */ + 'search.operator.asset-image-equal.name': 'is', + 'search.operator.asset-image-equal.description': + ' is {{value}}', + /* Asset (image) should not be the selected asset */ + 'search.operator.asset-image-not-equal.name': 'is not', + 'search.operator.asset-image-not-equal.description': + ' is not {{value}}', + /** + * Boolean value should be the given filter value (true/false). + * Context passed is `true` and `false`, allowing for more specific translations: + * - `search.operator.boolean-equal.description_true` + * - `search.operator.boolean-equal.description_false` + */ + 'search.operator.boolean-equal.name': 'is', + 'search.operator.boolean-equal.description': + ' is {{value}}', + /* Date should be after (later than) given filter value */ + 'search.operator.date-after.name': 'after', + 'search.operator.date-after.description': + ' is after {{value}}', + /* Date should be before (earlier than) given filter value */ + 'search.operator.date-before.name': 'before', + 'search.operator.date-before.description': + ' is before {{value}}', + /* Date should be the given filter value */ + 'search.operator.date-equal.name': 'is', + 'search.operator.date-equal.description': + ' is {{value}}', + /* Date should be within the given filter value range (eg "within the last X days") */ + 'search.operator.date-last.name': 'last', + 'search.operator.date-last.description': + ' is in the last {{value}}', + /* Date should not be the given filter value */ + 'search.operator.date-not-equal.name': 'is not', + 'search.operator.date-not-equal.description': + ' is not {{value}}', + /* Date should be within the range of given filter values */ + 'search.operator.date-range.name': 'is between', + 'search.operator.date-range.description': ' is between ', + /* Date and time should be after (later than) given filter value */ + 'search.operator.date-time-after.name': 'after', + 'search.operator.date-time-after.description': + ' is after {{value}}', + /* Date and time should be before (earlier than) given filter value */ + 'search.operator.date-time-before.name': 'before', + 'search.operator.date-time-before.description': + ' is before {{value}}', + /* Date and time should be the given filter value */ + 'search.operator.date-time-equal.name': 'is', + 'search.operator.date-time-equal.description': + ' is {{value}}', + /* Date and time should be within the given filter value range (eg "within the last X days") */ + 'search.operator.date-time-last.name': 'last', + 'search.operator.date-time-last.description': + ' is in the last {{value}}', + /* Date and time should not be the given filter value */ + 'search.operator.date-time-not-equal.name': 'is not', + 'search.operator.date-time-not-equal.description': + ' is not {{value}}', + /* Date and time should be within the range of given filter values */ + 'search.operator.date-time-range.name': 'is between', + 'search.operator.date-time-range.description': + ' is between ', + /* Value should be defined */ + 'search.operator.defined.name': 'not empty', + 'search.operator.defined.description': + ' is not empty', + /* Value should not be defined */ + 'search.operator.not-defined.name': 'empty', + 'search.operator.not-defined.description': + ' is empty', + /* Number should be the given filter value */ + 'search.operator.number-equal.name': 'is', + 'search.operator.number-equal.description': + ' is {{value}}', + /* Number should be greater than given filter value */ + 'search.operator.number-gt.name': 'greater than', + 'search.operator.number-gt.description': + ' > {{value}}', + /* Number should be greater than or the given filter value */ + 'search.operator.number-gte.name': 'greater than or equal to', + 'search.operator.number-gte.description': + ' {{value}}', + /* Number should be less than given filter value */ + 'search.operator.number-lt.name': 'less than', + 'search.operator.number-lt.description': + ' < {{value}}', + /* Number should be less than or the given filter value */ + 'search.operator.number-lte.name': 'less than or equal to', + 'search.operator.number-lte.description': + ' {{value}}', + /* Number should not be the given filter value */ + 'search.operator.number-not-equal.name': 'is not', + 'search.operator.number-not-equal.description': + ' is not {{value}}', + /* Number should be within the range of given filter values */ + 'search.operator.number-range.name': 'is between', + 'search.operator.number-range.description': + ' is between {{from}} → {{to}}', + /* Portable Text should be the given filter value */ + 'search.operator.portable-text-equal.name': 'is', + 'search.operator.portable-text-equal.description': + ' is {{value}}', + /* Portable Text should contain the given filter value */ + 'search.operator.portable-text-contains.name': 'contains', + 'search.operator.portable-text-contains.description': + ' contains {{value}}', + /* Portable Text should not be the given filter value */ + 'search.operator.portable-text-not-equal.name': 'is not', + 'search.operator.portable-text-not-equal.description': + ' is not {{value}}', + /* Portable Text should not contain the given filter value */ + 'search.operator.portable-text-not-contains.name': 'does not contain', + 'search.operator.portable-text-not-contains.description': + ' does not contain {{value}}', + /* Reference should be the given document */ + 'search.operator.reference-equal.name': 'is', + 'search.operator.reference-equal.description': + ' is {{value}}', + /* Reference should not be the given document */ + 'search.operator.reference-not-equal.name': 'is not', + 'search.operator.reference-not-equal.description': + ' is not {{value}}', + /* References the given asset (file) */ + 'search.operator.reference-asset-file.name': 'file', + 'search.operator.reference-asset-file.description': + ' {{value}}', + /* References the given asset (image) */ + 'search.operator.reference-asset-image.name': 'image', + 'search.operator.reference-asset-image.description': + ' {{value}}', + /* References the given document */ + 'search.operator.reference-document.name': 'document', + 'search.operator.reference-document.description': + ' {{value}}', + /* Slug equals the given filter value */ + 'search.operator.slug-equal.name': 'is', + 'search.operator.slug-equal.description': + ' is {{value}}', + /* Slug contains the given value */ + 'search.operator.slug-contains.name': 'contains', + 'search.operator.slug-contains.description': + ' contains {{value}}', + /* Slug does not equal the given filter value */ + 'search.operator.slug-not-equal.name': 'is not', + 'search.operator.slug-not-equal.description': + ' is not {{value}}', + /* Slug does not contain the given value */ + 'search.operator.slug-not-contains.name': 'does not contain', + 'search.operator.slug-not-contains.description': + ' does not contain {{value}}', + /* String equals the given filter value */ + 'search.operator.string-equal.name': 'is', + 'search.operator.string-equal.description': + ' is {{value}}', + /* String equals one of the predefined allowed values */ + 'search.operator.string-list-equal.name': 'is', + 'search.operator.string-list-equal.description': + ' is {{value}}', + /* String does not equal one of the predefined allowed values */ + 'search.operator.string-list-not-equal.name': 'is not', + 'search.operator.string-list-not-equal.description': + ' is not {{value}}', + /* String contains the given filter value */ + 'search.operator.string-contains.name': 'contains', + 'search.operator.string-contains.description': + ' contains {{value}}', + /* String does not equal the given filter value */ + 'search.operator.string-not-equal.name': 'is not', + 'search.operator.string-not-equal.description': + ' is not {{value}}', + /* String does not contain the given filter value */ + 'search.operator.string-not-contains.name': 'does not contain', + 'search.operator.string-not-contains.description': + ' does not contain {{value}}', /** Title label for when no search results are found */ 'search.no-results-title': 'No results found', diff --git a/packages/sanity/src/core/studio/components/navbar/search/__workshop__/CommonDateRangeStory.tsx b/packages/sanity/src/core/studio/components/navbar/search/__workshop__/CommonDateRangeStory.tsx index 4bcfdbcf61a..f6a6c7ff13e 100644 --- a/packages/sanity/src/core/studio/components/navbar/search/__workshop__/CommonDateRangeStory.tsx +++ b/packages/sanity/src/core/studio/components/navbar/search/__workshop__/CommonDateRangeStory.tsx @@ -26,7 +26,7 @@ export default function CommonDateRangeStory() { Start: - {value?.dateMin ? value.dateMin : Empty} + {value?.from ? value.from : Empty} @@ -34,7 +34,7 @@ export default function CommonDateRangeStory() { End: - {value?.dateMax ? value.dateMax : Empty} + {value?.to ? value.to : Empty} diff --git a/packages/sanity/src/core/studio/components/navbar/search/components/SortMenu.tsx b/packages/sanity/src/core/studio/components/navbar/search/components/SortMenu.tsx index 0b8b844ce58..eb4f431d3cd 100644 --- a/packages/sanity/src/core/studio/components/navbar/search/components/SortMenu.tsx +++ b/packages/sanity/src/core/studio/components/navbar/search/components/SortMenu.tsx @@ -14,6 +14,7 @@ import { import isEqual from 'lodash/isEqual' import React, {useCallback, useId, useMemo} from 'react' import styled from 'styled-components' +import {useTranslation} from '../../../../../i18n' import {ORDERINGS} from '../definitions/orderings' import {useSearchState} from '../contexts/search/useSearchState' import type {SearchOrdering} from '../types' @@ -41,6 +42,7 @@ function isSearchDivider(item: SearchDivider | SearchOrdering): item is SearchDi } function CustomMenuItem({ordering}: {ordering: SearchOrdering}) { + const {t} = useTranslation() const { dispatch, state: {ordering: currentOrdering}, @@ -56,7 +58,7 @@ function CustomMenuItem({ordering}: {ordering: SearchOrdering}) { - {ordering.title} + {t(ordering.titleKey)} @@ -64,6 +66,7 @@ function CustomMenuItem({ordering}: {ordering: SearchOrdering}) { } export function SortMenu() { + const {t} = useTranslation() const { state: {ordering}, } = useSearchState() @@ -92,7 +95,7 @@ export function SortMenu() { - {currentMenuItem.title} + {t(currentMenuItem.titleKey)} diff --git a/packages/sanity/src/core/studio/components/navbar/search/components/common/FilterLabel.tsx b/packages/sanity/src/core/studio/components/navbar/search/components/common/FilterLabel.tsx index 2515934c687..dc5e20ee981 100644 --- a/packages/sanity/src/core/studio/components/navbar/search/components/common/FilterLabel.tsx +++ b/packages/sanity/src/core/studio/components/navbar/search/components/common/FilterLabel.tsx @@ -1,10 +1,13 @@ import {Box, Flex} from '@sanity/ui' -import React from 'react' +import React, {useMemo} from 'react' import styled from 'styled-components' import {TextWithTone} from '../../../../../../components' +import {TranslateComponentMap, Translate} from '../../../../../../i18n/Translate' +import {isRecord} from '../../../../../../util' +import {useTranslation} from '../../../../../../i18n' import {useSearchState} from '../../contexts/search/useSearchState' import {getOperatorDefinition} from '../../definitions/operators' -import type {SearchFilter} from '../../types' +import type {SearchFilter, SearchFilterValues} from '../../types' import {FilterTitle} from './FilterTitle' interface FilterLabelProps { @@ -18,6 +21,7 @@ const CustomBox = styled(Box)<{$flexShrink?: number}>` ` export function FilterLabel({filter, fontSize = 1, showContent = true}: FilterLabelProps) { + const {t} = useTranslation() const { state: {definitions, fullscreen}, } = useSearchState() @@ -25,31 +29,79 @@ export function FilterLabel({filter, fontSize = 1, showContent = true}: FilterLa const operator = getOperatorDefinition(definitions.operators, filter.operatorType) const ButtonValue = operator?.buttonValueComponent + const filterValue = filter.value - return ( - - {/* Title */} - - - - - - {/* Operator */} - {showContent && operator?.buttonLabel && ( - - - {operator.buttonLabel} - - - )} - {/* Value */} - {showContent && ButtonValue && ( - + const components: TranslateComponentMap = useMemo( + () => ({ + Field: () => ( + - + - )} + ), + Operator: ({children}) => + showContent && ( + + + {children} + + + ), + Value: ({children}) => + showContent && ( + + + {ButtonValue ? : children} + + + ), + }), + [filter, fontSize, fullscreen, showContent, ButtonValue, filterValue], + ) + + if (!operator?.descriptionKey) { + console.warn('Missing `descriptionKey` for operator `%s`', filter.operatorType) + } + + if (!showContent || !operator?.descriptionKey) { + const Field = components.Field + return ( + + + + ) + } + + return ( + + ) } + +function getFilterValues(filter: SearchFilter): SearchFilterValues { + const values: SearchFilterValues = {} + if (typeof filter.value === 'number') { + values.count = filter.value + } + if (isStringOrNumber(filter.value)) { + values.value = filter.value + } + if (isRecord(filter.value) && 'from' in filter.value && isStringOrNumber(filter.value.from)) { + values.from = filter.value.from + } + if (isRecord(filter.value) && 'to' in filter.value && isStringOrNumber(filter.value.to)) { + values.to = filter.value.to + } + return values +} + +function isStringOrNumber(value: unknown): value is string | number { + return typeof value === 'string' || typeof value === 'number' +} diff --git a/packages/sanity/src/core/studio/components/navbar/search/components/filters/common/ButtonValue.tsx b/packages/sanity/src/core/studio/components/navbar/search/components/filters/common/ButtonValue.tsx index 72dc2a47b86..1cdde133c1e 100644 --- a/packages/sanity/src/core/studio/components/navbar/search/components/filters/common/ButtonValue.tsx +++ b/packages/sanity/src/core/studio/components/navbar/search/components/filters/common/ButtonValue.tsx @@ -1,11 +1,10 @@ import type {Reference} from '@sanity/types' -import {format, isValid} from 'date-fns' -import pluralize from 'pluralize-esm' +import {isValid} from 'date-fns' import React from 'react' import {useSchema} from '../../../../../../../hooks' -import type {OperatorNumberRangeValue} from '../../../definitions/operators/common' +import {useUnitFormatter} from '../../../../../../../hooks/useUnitFormatter' +import {useIntlDateTimeFormat} from '../../../../../../../i18n/hooks/useIntlDateTimeFormat' import type { - OperatorDateDirectionValue, OperatorDateEqualValue, OperatorDateLastValue, OperatorDateRangeValue, @@ -24,75 +23,40 @@ export function SearchButtonValueBoolean({value}: OperatorButtonValueComponentPr export function SearchButtonValueDate({ value, }: OperatorButtonValueComponentProps) { + const dateFormat = useIntlDateTimeFormat({ + dateStyle: 'medium', + timeStyle: value.includeTime ? 'short' : undefined, + }) const date = value?.date ? new Date(value.date) : null if (!date || !isValid(date)) { return null } - return <>{format(date, DEFAULT_DATE_FORMAT)} -} - -export function SearchButtonValueDateDirection({ - value, -}: OperatorButtonValueComponentProps) { - const date = value?.date ? new Date(value.date) : null - if (!date || !isValid(date)) { - return null - } - return <>{format(date, DEFAULT_DATE_FORMAT)} + return <>{dateFormat.format(date)} } export function SearchButtonValueDateLast({ value, }: OperatorButtonValueComponentProps) { - return ( - <> - {Math.floor(value?.unitValue ?? 0)} {value.unit} - - ) + const formatUnit = useUnitFormatter() + return <>{formatUnit(Math.floor(value?.unitValue ?? 0), value.unit)} } export function SearchButtonValueDateRange({ value, }: OperatorButtonValueComponentProps) { - const startDate = value?.dateMin ? new Date(value.dateMin) : null - const endDate = value?.dateMax ? new Date(value.dateMax) : null + const dateFormat = useIntlDateTimeFormat({ + dateStyle: 'medium', + timeStyle: value.includeTime ? 'short' : undefined, + }) + const startDate = value?.from ? new Date(value.from) : null + const endDate = value?.to ? new Date(value.to) : null if (!endDate || !startDate || !isValid(endDate) || !isValid(startDate)) { return null } - return ( - <> - {format(startDate, DEFAULT_DATE_FORMAT)} → {format(endDate, DEFAULT_DATE_FORMAT)} - - ) -} - -export function SearchButtonValueNumber({value}: OperatorButtonValueComponentProps) { - return <>{value} -} -export function SearchButtonValueNumberCount({value}: OperatorButtonValueComponentProps) { - return ( - <> - {value} {pluralize('item', value)} - - ) -} - -export function SearchButtonValueNumberRange({ - value, -}: OperatorButtonValueComponentProps) { - return ( - <> - {value.min} → {value.max} - - ) -} - -export function SearchButtonValueNumberCountRange({ - value, -}: OperatorButtonValueComponentProps) { - const {t} = useTranslation() - return <>{t('search.number-items-range', {min: value.min, max: value.max})} + const from = dateFormat.format(startDate) + const to = dateFormat.format(endDate) + return <>{`${from} → ${to}`} } export function SearchButtonValueReference({value}: OperatorButtonValueComponentProps) { @@ -104,9 +68,3 @@ export function SearchButtonValueReference({value}: OperatorButtonValueComponent } return } - -export function SearchButtonValueString({ - value, -}: OperatorButtonValueComponentProps) { - return <>{value} -} diff --git a/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/OperatorsMenuButton.tsx b/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/OperatorsMenuButton.tsx index 588fd80c362..29258c30fb4 100644 --- a/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/OperatorsMenuButton.tsx +++ b/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/OperatorsMenuButton.tsx @@ -6,6 +6,7 @@ import {getFilterDefinition} from '../../../definitions/filters' import {getOperatorDefinition, SearchOperatorDefinition} from '../../../definitions/operators' import type {SearchFilter} from '../../../types' import {getFilterKey} from '../../../utils/filterUtils' +import {useTranslation} from '../../../../../../../i18n' interface OperatorsMenuButtonProps { filter: SearchFilter @@ -22,13 +23,14 @@ function CustomMenuItem({ selected: boolean }) { const handleClick = useCallback(() => onClick(operator.type), [onClick, operator.type]) + const {t} = useTranslation() return ( - {operator.label} + {t(operator.nameKey)} {operator?.icon && ( @@ -44,6 +46,7 @@ function CustomMenuItem({ export function OperatorsMenuButton({filter, operator}: OperatorsMenuButtonProps) { const menuButtonId = useId() + const {t} = useTranslation() const {dispatch, state} = useSearchState() const operatorItems = getFilterDefinition(state.definitions.filters, filter.filterName)?.operators @@ -69,7 +72,7 @@ export function OperatorsMenuButton({filter, operator}: OperatorsMenuButtonProps