Skip to content

Commit 06d812c

Browse files
authoredMar 19, 2024
feat(tasks): Localize Task feature (#6017)
* feat(tasks): Localize Task feature * feat(tasks): updated i18n keys to not have tasks prefix * feat(tasks): updated missing string to be localized * feat(task): i18n updates after rebase * feat(task): added i18n attribute exception
1 parent 2afbc3b commit 06d812c

25 files changed

+292
-83
lines changed
 

‎.eslintrc.cjs

+2-9
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,14 @@ const config = {
7676
attributes: [
7777
'animate',
7878
'closed',
79+
'documentType',
7980
'exit',
8081
'fill',
8182
'full',
8283
'initial',
8384
'size',
8485
'sortOrder',
86+
'status',
8587
'group',
8688
],
8789
},
@@ -280,15 +282,6 @@ const config = {
280282
'@sanity/i18n/no-attribute-template-literals': 'off',
281283
},
282284
},
283-
// Ignore i18n in Tasks files for now. This will need to be removed before task is completed.
284-
{
285-
files: ['**/*/Tasks*.{js,ts,tsx}', '**/*/tasks/**/*'],
286-
rules: {
287-
'i18next/no-literal-string': 'off',
288-
'@sanity/i18n/no-attribute-string-literals': 'off',
289-
'@sanity/i18n/no-attribute-template-literals': 'off',
290-
},
291-
},
292285

293286
// Prefer local components vs certain @sanity/ui imports (in sanity package)
294287
{
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {defineLocaleResourceBundle} from 'sanity'
2+
3+
/**
4+
* The locale namespace for the task tool
5+
*
6+
* @public
7+
*/
8+
export const tasksLocaleNamespace = 'tasks' as const
9+
10+
/**
11+
* The default locale bundle for the task tool, which is US English.
12+
*
13+
* @internal
14+
*/
15+
export const tasksUsEnglishLocaleBundle = defineLocaleResourceBundle({
16+
locale: 'en-US',
17+
namespace: tasksLocaleNamespace,
18+
resources: () => import('./resources'),
19+
})
20+
21+
/**
22+
* The locale resource keys for the task tool.
23+
*
24+
* @alpha
25+
* @hidden
26+
*/
27+
export type {TasksLocaleResourceKeys} from './resources'
+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/* eslint sort-keys: "error" */
2+
import {defineLocalesResources} from 'sanity'
3+
4+
/**
5+
* Defined locale strings for the task tool, in US English.
6+
*
7+
* @internal
8+
*/
9+
const tasksLocaleStrings = defineLocalesResources('tasks', {
10+
/** The label for the create task action */
11+
'actions.create.text': 'Create new task',
12+
/** The label for the open tasks panel action */
13+
'actions.open.text': 'Tasks',
14+
/** The label for the button to create a new task */
15+
'buttons.create.text': 'Create Task',
16+
/** The label for the button to discard changes */
17+
'buttons.discard.text': 'Discard',
18+
/** The label for the button to open the draft */
19+
'buttons.draft.text': 'Draft',
20+
/** The label for the button to create a new task */
21+
'buttons.new.text': 'New task',
22+
/** The label for the button that will navigate to the next task */
23+
'buttons.next.tooltip': 'Go to next task',
24+
/** The label for the button that will previous to the next task */
25+
'buttons.previous.tooltip': 'Go to previous task',
26+
/** Text for the remove task dialog asking for confirmation of deletion */
27+
'dialog.remove-task.body': 'Are you sure you want to delete this task?',
28+
/** Text for the remove task dialog clarifying that deletion is permanent */
29+
'dialog.remove-task.body2': 'Once deleted, it cannot be restored.',
30+
/** The label for the cancel button on the remove task dialog */
31+
'dialog.remove-task.buttons.cancel.text': 'Cancel',
32+
/** The label for the confirmation button on the remove task dialog */
33+
'dialog.remove-task.buttons.confirm.text': 'Remove',
34+
/** The title for the remove task dialog */
35+
'dialog.remove-task.title': 'Remove task',
36+
/** The text used as a placeholder for the footer action in a document with a single task */
37+
'document.footer.open-tasks.placeholder_one': 'Open task',
38+
/** The text used as a placeholder for the footer action in a document with multiple tasks */
39+
'document.footer.open-tasks.placeholder_other': 'Open tasks',
40+
/** The label used in the button in the footer action in a document with a single task */
41+
'document.footer.open-tasks.text_one': '{{count}} open task',
42+
/** The label used in the button in the footer action in a document with multiple tasks */
43+
'document.footer.open-tasks.text_other': '{{count}} open tasks',
44+
/** Text used in the assignee input when there is no user assigned */
45+
'form.input.assignee.no-user-assigned.text': 'Not assigned',
46+
/** Text used in the assignee input when searching and no users are found */
47+
'form.input.assignee.search.no-users.text': 'No users found',
48+
/** Placeholder text used in the search box in the assignee input */
49+
'form.input.assignee.search.placeholder': 'Search username',
50+
/** Text used in the assignee input when user is not authorized */
51+
'form.input.assignee.unauthorized.text': 'Unauthorized',
52+
/** Text used in the assignee input when user is not found */
53+
'form.input.assignee.user-not-found.text': 'User not found',
54+
/** The label used in the create more toggle */
55+
'form.input.create-more.text': 'Create more',
56+
/** The label used in the date input to remove the current value */
57+
'form.input.date.buttons.remove.text': 'Remove',
58+
/** Placeholder text used in the description input */
59+
'form.input.description.placeholder': 'Optional additional description',
60+
/** The label used in the target input to remove the current value */
61+
'form.input.target.buttons.remove.text': 'Remove target content',
62+
/** The text used in the target input when encountering a schema error */
63+
'form.input.target.error.schema-not-found': 'Schema not found',
64+
/** The placeholder text used in the target input for the search component */
65+
'form.input.target.search.placeholder': 'Search document',
66+
/** The placeholder text for the title input */
67+
'form.input.title.placeholder': 'Task title',
68+
/** The status error message presented when the user does not supply a title */
69+
'form.status.error.title-required': 'Title is required',
70+
/** The status message upon successful creation of a task */
71+
'form.status.success': 'Task created',
72+
/** The text displayed when no tasks are found */
73+
'list.empty.text': 'No tasks',
74+
/** The label for the copy link menu item */
75+
'menuitem.copylink.text': 'Copy link to task',
76+
/** The label for the delete task menu item */
77+
'menuitem.delete.text': 'Delete task',
78+
/** The label for the duplicate task menu item */
79+
'menuitem.duplicate.text': 'Duplicate task',
80+
/** Fragment used to construct the first entry in the activity log */
81+
'panel.activity.created-fragment': 'created this task',
82+
/** The title of the activity section of the task */
83+
'panel.activity.title': 'Activity',
84+
/** The text used in the activity log when unable to find the user */
85+
'panel.activity.unknown-user': 'Unknown user',
86+
/** The tooltip for the close button for the task panel */
87+
'panel.close.tooltip': 'Close sidebar',
88+
/** The placeholder text for the comment text box */
89+
'panel.comment.placeholder': 'Add a comment...',
90+
/** The title used in the task panel when showing the create task form */
91+
'panel.create.title': 'Create',
92+
/** The title used in the drafts pulldown */
93+
'panel.drafts.title': 'Drafts',
94+
/** The tooltip for the task navigation component */
95+
'panel.navigation.tooltip': 'Open tasks',
96+
/** Title of the Tasks panel */
97+
'panel.title': 'Tasks',
98+
})
99+
100+
/**
101+
* @alpha
102+
*/
103+
export type TasksLocaleResourceKeys = keyof typeof tasksLocaleStrings
104+
105+
export default tasksLocaleStrings

‎packages/sanity/src/tasks/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
export {tasksLocaleNamespace, type TasksLocaleResourceKeys} from './i18n'
12
export * from './plugin'
23
export * from './src/tasks'

‎packages/sanity/src/tasks/plugin/TaskCreateAction.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {TaskIcon} from '@sanity/icons'
22
import {useCallback} from 'react'
3-
import {type DocumentActionDescription} from 'sanity'
3+
import {type DocumentActionDescription, useTranslation} from 'sanity'
44

5+
import {tasksLocaleNamespace} from '../i18n'
56
import {useTasksEnabled, useTasksNavigation} from '../src'
67

78
export function TaskCreateAction(): DocumentActionDescription | null {
@@ -13,12 +14,14 @@ export function TaskCreateAction(): DocumentActionDescription | null {
1314
setViewMode({type: 'create'})
1415
}, [handleOpenTasks, setViewMode])
1516

17+
const {t} = useTranslation(tasksLocaleNamespace)
18+
1619
if (!enabled) return null
1720

1821
return {
1922
icon: TaskIcon,
20-
label: 'Create new task',
21-
title: 'Create new task',
23+
label: t('actions.create.text'),
24+
title: t('actions.create.text'),
2225
group: ['paneActions'],
2326
onHandle: handleCreateTaskFromDocument,
2427
}

‎packages/sanity/src/tasks/plugin/TasksFooterOpenTasks.tsx

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import {TaskIcon} from '@sanity/icons'
22
import {Badge, useMediaIndex} from '@sanity/ui'
33
import {useCallback, useMemo} from 'react'
4+
import {useTranslation} from 'sanity'
45
import styled from 'styled-components'
56

67
import {Button} from '../../ui-components'
8+
import {tasksLocaleNamespace} from '../i18n'
79
import {useTasks, useTasksEnabled, useTasksNavigation} from '../src'
810

911
const ButtonContainer = styled.div`
@@ -44,9 +46,10 @@ export function TasksFooterOpenTasks() {
4446
setActiveTab('document')
4547
}, [handleOpenTasks, setActiveTab])
4648

49+
const {t} = useTranslation(tasksLocaleNamespace)
50+
4751
if (pendingTasks.length === 0 || !enabled) return null
4852

49-
const pluralizedTask = `task${pendingTasks.length > 1 ? 's' : ''}`
5053
if (mediaIndex < 3) {
5154
return (
5255
<ButtonContainer>
@@ -56,7 +59,9 @@ export function TasksFooterOpenTasks() {
5659
size={'large'}
5760
onClick={handleOnClick}
5861
tooltipProps={{
59-
content: `Open ${pluralizedTask}`,
62+
content: t('document.footer.open-tasks.placeholder', {
63+
count: pendingTasks.length,
64+
}),
6065
}}
6166
/>
6267
<Badge tone="primary" fontSize={0}>
@@ -68,8 +73,12 @@ export function TasksFooterOpenTasks() {
6873
return (
6974
<Button
7075
mode="bleed"
71-
tooltipProps={{content: `Open ${pluralizedTask}`}}
72-
text={`${pendingTasks.length} open ${pluralizedTask}`}
76+
tooltipProps={{
77+
content: t('document.footer.open-tasks.placeholder', {
78+
count: pendingTasks.length,
79+
}),
80+
}}
81+
text={t('document.footer.open-tasks.text', {count: pendingTasks.length})}
7382
onClick={handleOnClick}
7483
/>
7584
)

‎packages/sanity/src/tasks/plugin/TasksStudioNavbar.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {PanelRightIcon, TaskIcon} from '@sanity/icons'
22
import {useCallback, useMemo} from 'react'
3-
import {type NavbarProps} from 'sanity'
3+
import {type NavbarProps, useTranslation} from 'sanity'
44

5+
import {tasksLocaleNamespace} from '../i18n'
56
import {useTasksEnabled, useTasksNavigation} from '../src'
67

78
const EMPTY_ARRAY: [] = []
@@ -21,6 +22,8 @@ function TasksStudioNavbarInner(props: NavbarProps) {
2122
}
2223
}, [handleCloseTasks, handleOpenTasks, isOpen])
2324

25+
const {t} = useTranslation(tasksLocaleNamespace)
26+
2427
const actions = useMemo((): NavbarProps['__internal_actions'] => {
2528
return [
2629
...(props?.__internal_actions || EMPTY_ARRAY),
@@ -30,18 +33,18 @@ function TasksStudioNavbarInner(props: NavbarProps) {
3033
name: 'tasks-topbar',
3134
onAction: handleAction,
3235
selected: isOpen,
33-
title: 'Tasks',
36+
title: t('actions.open.text'),
3437
},
3538
{
3639
icon: TaskIcon,
3740
location: 'sidebar',
3841
name: 'tasks-sidebar',
3942
onAction: handleAction,
4043
selected: isOpen,
41-
title: 'Tasks',
44+
title: t('actions.open.text'),
4245
},
4346
]
44-
}, [handleAction, isOpen, props?.__internal_actions])
47+
}, [handleAction, isOpen, props?.__internal_actions, t])
4548

4649
return props.renderDefault({
4750
...props,

‎packages/sanity/src/tasks/plugin/index.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import {definePlugin, type ObjectInputProps} from 'sanity'
22

3+
import {tasksUsEnglishLocaleBundle} from '../i18n'
34
import {TaskCreateAction} from './TaskCreateAction'
45
import {TasksDocumentInputLayout} from './TasksDocumentInputLayout'
56
import {TasksFooterOpenTasks} from './TasksFooterOpenTasks'
67
import {TasksStudioActiveToolLayout} from './TasksStudioActiveToolLayout'
78
import {TasksStudioLayout} from './TasksStudioLayout'
89
import {TasksStudioNavbar} from './TasksStudioNavbar'
9-
1010
/**
1111
* @internal
1212
* @beta
@@ -40,4 +40,7 @@ export const tasks = definePlugin({
4040
},
4141
},
4242
},
43+
i18n: {
44+
bundles: [tasksUsEnglishLocaleBundle],
45+
},
4346
})

‎packages/sanity/src/tasks/src/tasks/components/activity/TasksActivityCommentInput.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import {Card} from '@sanity/ui'
22
import {useCallback, useMemo, useRef, useState} from 'react'
3+
import {useTranslation} from 'sanity'
34

45
import {
56
CommentInput,
67
type CommentInputHandle,
78
type CommentInputProps,
89
hasCommentMessageValue,
910
} from '../../../../../structure/comments'
11+
import {tasksLocaleNamespace} from '../../../../i18n'
1012
import {ActivityItem} from './TasksActivityItem'
1113

1214
interface TasksCommentActivityInputProps {
@@ -58,6 +60,7 @@ export function TasksActivityCommentInput(props: TasksCommentActivityInputProps)
5860
},
5961
[hasValue],
6062
)
63+
const {t} = useTranslation(tasksLocaleNamespace)
6164

6265
return (
6366
<ActivityItem userId={currentUser.id}>
@@ -72,7 +75,7 @@ export function TasksActivityCommentInput(props: TasksCommentActivityInputProps)
7275
onDiscardCancel={handleDiscardCancel}
7376
onKeyDown={handleKeyDown}
7477
onSubmit={handleSubmit}
75-
placeholder="Add a comment..."
78+
placeholder={t('panel.comment.placeholder')}
7679
ref={editorRef}
7780
value={value}
7881
/>

‎packages/sanity/src/tasks/src/tasks/components/activity/TasksActivityCreatedAt.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import {Flex, Text, TextSkeleton} from '@sanity/ui'
22
import {memo} from 'react'
3-
import {useUser} from 'sanity'
3+
import {useTranslation, useUser} from 'sanity'
44
import styled from 'styled-components'
55

66
import {Tooltip} from '../../../../../ui-components'
7+
import {tasksLocaleNamespace} from '../../../../i18n'
78
import {UpdatedTimeAgo} from './helpers'
89
import {ActivityItem} from './TasksActivityItem'
910

@@ -22,15 +23,15 @@ export const TasksActivityCreatedAt = memo(
2223
const {createdAt, authorId} = props
2324
const [user, loading] = useUser(authorId)
2425
const {timeAgo, formattedDate} = UpdatedTimeAgo(createdAt)
25-
26+
const {t} = useTranslation(tasksLocaleNamespace)
2627
return (
2728
<ActivityItem userId={authorId}>
2829
<Flex align="center" paddingTop={1}>
2930
<Text size={1} muted>
3031
<strong style={{fontWeight: 600}}>
31-
{loading ? <UserSkeleton /> : user?.displayName ?? 'Unknown user'}{' '}
32+
{loading ? <UserSkeleton /> : user?.displayName ?? t('panel.activity.unknown-user')}{' '}
3233
</strong>
33-
created this task{' '}
34+
{t('panel.activity.created-fragment')}{' '}
3435
<Tooltip content={formattedDate} placement="top-end">
3536
<time dateTime={createdAt}>{timeAgo}</time>
3637
</Tooltip>

‎packages/sanity/src/tasks/src/tasks/components/activity/TasksActivityLog.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
type TransactionLogEventWithEffects,
1313
useClient,
1414
useCurrentUser,
15+
useTranslation,
1516
useWorkspace,
1617
} from 'sanity'
1718
import styled from 'styled-components'
@@ -27,6 +28,7 @@ import {
2728
type CommentUpdatePayload,
2829
useComments,
2930
} from '../../../../../structure/comments'
31+
import {tasksLocaleNamespace} from '../../../../i18n'
3032
import {API_VERSION} from '../../constants/API_VERSION'
3133
import {type TaskDocument} from '../../types'
3234
import {CurrentWorkspaceProvider} from '../form/CurrentWorkspaceProvider'
@@ -287,6 +289,7 @@ export function TasksActivityLog(props: TasksActivityLogProps) {
287289
.concat(commentsActivity)
288290
.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime())
289291
}, [activityData, taskComments])
292+
const {t} = useTranslation(tasksLocaleNamespace)
290293

291294
const commentToDeleteIsParent = useMemo(() => {
292295
const parent = taskComments.find((c) => c.parentComment?._id === commentToDeleteId)
@@ -312,7 +315,7 @@ export function TasksActivityLog(props: TasksActivityLogProps) {
312315
<Flex align="center">
313316
<Box flex={1}>
314317
<Text size={2} weight="semibold">
315-
Activity
318+
{t('panel.activity.title')}
316319
</Text>
317320
</Box>
318321

‎packages/sanity/src/tasks/src/tasks/components/form/CurrentWorkspaceProvider.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {LoadingBlock, WorkspaceLoader} from 'sanity'
22

33
function ConfigErrorsScreen() {
4+
// eslint-disable-next-line i18next/no-literal-string
45
return <div>Config errors</div>
56
}
67

‎packages/sanity/src/tasks/src/tasks/components/form/RemoveTaskDialog.tsx

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,35 @@
11
import {Stack, Text} from '@sanity/ui'
2+
import {useTranslation} from 'sanity'
23

34
import {Dialog} from '../../../../../ui-components'
5+
import {tasksLocaleNamespace} from '../../../../i18n'
46
import {type useRemoveTask} from '../../hooks/useRemoveTask'
57

68
export function RemoveTaskDialog(props: ReturnType<typeof useRemoveTask>) {
79
const {handleCloseDialog, handleRemove, removeStatus, showDialog} = props
10+
const {t} = useTranslation(tasksLocaleNamespace)
811
if (showDialog) {
912
return (
1013
<Dialog
1114
id="remove-task"
12-
header="Remove task"
15+
header={t('dialog.remove-task.title')}
1316
onClose={handleCloseDialog}
1417
footer={{
1518
cancelButton: {
16-
text: 'Cancel',
19+
text: t('dialog.remove-task.buttons.cancel.text'),
1720
onClick: handleCloseDialog,
1821
},
1922
confirmButton: {
20-
text: 'Remove',
23+
text: t('dialog.remove-task.buttons.confirm.text'),
2124
tone: 'critical',
2225
onClick: handleRemove,
2326
loading: removeStatus === 'loading',
2427
},
2528
}}
2629
>
2730
<Stack space={3}>
28-
<Text as="p">Are you sure you want to delete this task?</Text>
29-
<Text as="p">Once deleted, it cannot be restored.</Text>
31+
<Text as="p">{t('dialog.remove-task.body')}</Text>
32+
<Text as="p">{t('dialog.remove-task.body2')}</Text>
3033
</Stack>
3134
</Dialog>
3235
)

‎packages/sanity/src/tasks/src/tasks/components/form/fields/DateEditFormField.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {type CalendarLabels} from '../../../../../../core/form/inputs/DateInputs
1818
import {DatePicker} from '../../../../../../core/form/inputs/DateInputs/base/DatePicker'
1919
import {getCalendarLabels} from '../../../../../../core/form/inputs/DateInputs/utils'
2020
import {Button, Popover} from '../../../../../../ui-components'
21+
import {tasksLocaleNamespace} from '../../../../../i18n'
2122

2223
const serialize = (date: Date) => format(date, DEFAULT_DATE_FORMAT)
2324
const deserialize = (value: string) => parse(value, DEFAULT_DATE_FORMAT)
@@ -28,7 +29,9 @@ export function DateEditFormField(props: {
2829
path: Path
2930
}) {
3031
const {value, onChange, path} = props
31-
const {t} = useTranslation()
32+
const {t: coreT} = useTranslation()
33+
const {t} = useTranslation(tasksLocaleNamespace)
34+
3235
const [pickerOpen, setPickerOpen] = useState(false)
3336
const [popoverRef, setPopoverRef] = useState<HTMLElement | null>(null)
3437
const buttonRef = useRef<HTMLButtonElement | null>(null)
@@ -48,7 +51,7 @@ export function DateEditFormField(props: {
4851
}
4952
}, [])
5053
const handleClick = useCallback(() => setPickerOpen((p) => !p), [])
51-
const calendarLabels: CalendarLabels = useMemo(() => getCalendarLabels(t), [t])
54+
const calendarLabels: CalendarLabels = useMemo(() => getCalendarLabels(coreT), [coreT])
5255
const handleChange = useCallback(
5356
(nextDate: Date | null) => {
5457
if (nextDate) {
@@ -88,7 +91,7 @@ export function DateEditFormField(props: {
8891
<Flex justify={'flex-start'} padding={3} paddingTop={0}>
8992
<Button
9093
mode="bleed"
91-
text="Remove"
94+
text={t('form.input.date.buttons.remove.text')}
9295
onClick={() => handleChange(null)}
9396
tone="critical"
9497
/>

‎packages/sanity/src/tasks/src/tasks/components/form/fields/DescriptionInput.tsx

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
// eslint-disable-next-line camelcase
22
import {getTheme_v2} from '@sanity/ui/theme'
33
import {useCallback, useEffect, useRef, useState} from 'react'
4-
import {type ArrayFieldProps, type PortableTextBlock, set, useCurrentUser} from 'sanity'
4+
import {
5+
type ArrayFieldProps,
6+
type PortableTextBlock,
7+
set,
8+
useCurrentUser,
9+
useTranslation,
10+
} from 'sanity'
511
import styled, {css} from 'styled-components'
612

713
import {CommentInput} from '../../../../../../structure/comments'
14+
import {tasksLocaleNamespace} from '../../../../../i18n'
815
import {useMentionUser} from '../../../context'
916
import {type FormMode} from '../../../types'
1017

@@ -59,6 +66,7 @@ export function DescriptionInput(props: ArrayFieldProps & {mode: FormMode}) {
5966
},
6067
[setTextboxHeight],
6168
)
69+
const {t} = useTranslation(tasksLocaleNamespace)
6270

6371
useEffect(() => {
6472
if (!rootRef.current) return
@@ -75,7 +83,7 @@ export function DescriptionInput(props: ArrayFieldProps & {mode: FormMode}) {
7583
onChange={handleChange}
7684
value={value ?? []}
7785
withAvatar={false}
78-
placeholder="Optional additional description"
86+
placeholder={t('form.input.description.placeholder')}
7987
// eslint-disable-next-line react/jsx-no-bind
8088
onDiscardConfirm={() => null}
8189
/>

‎packages/sanity/src/tasks/src/tasks/components/form/fields/TargetField.tsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ import {
1313
unset,
1414
useDocumentPresence,
1515
useSchema,
16+
useTranslation,
1617
useWorkspace,
1718
} from 'sanity'
1819
import {IntentLink} from 'sanity/router'
1920
import styled, {css} from 'styled-components'
2021

2122
import {Button} from '../../../../../../ui-components'
23+
import {tasksLocaleNamespace} from '../../../../../i18n'
2224
import {type FormMode, type TaskTarget} from '../../../types'
2325
import {CurrentWorkspaceProvider} from '../CurrentWorkspaceProvider'
2426
import {getTargetValue} from '../utils'
@@ -95,7 +97,7 @@ function Preview(props: {value: TaskTarget; handleRemove: () => void}) {
9597
const schema = useSchema()
9698
const schemaType = schema.get(value.documentType)
9799
const documentPresence = useDocumentPresence(documentId)
98-
100+
const {t} = useTranslation(tasksLocaleNamespace)
99101
const CardLink = useMemo(
100102
() =>
101103
forwardRef(function LinkComponent(linkProps, ref: ForwardedRef<HTMLAnchorElement>) {
@@ -111,7 +113,7 @@ function Preview(props: {value: TaskTarget; handleRemove: () => void}) {
111113
[documentId, documentType],
112114
)
113115
if (!schemaType) {
114-
return <Text>Schema not found</Text>
116+
return <Text>{t('form.input.target.error.schema-not-found')}</Text>
115117
}
116118

117119
return (
@@ -132,7 +134,7 @@ function Preview(props: {value: TaskTarget; handleRemove: () => void}) {
132134
icon={CloseIcon}
133135
mode="bleed"
134136
onClick={handleRemove}
135-
tooltipProps={{content: 'Remove target content'}}
137+
tooltipProps={{content: t('form.input.target.buttons.remove.text')}}
136138
/>
137139
</div>
138140
</Flex>
@@ -187,6 +189,8 @@ export function TargetField(
187189
if (event.key === 'Enter' || event.key === ' ') setOpen(true)
188190
}, [])
189191

192+
const {t} = useTranslation(tasksLocaleNamespace)
193+
190194
return (
191195
<Card borderBottom={mode === 'edit'} paddingBottom={mode === 'edit' ? 4 : 0}>
192196
<FieldWrapperRoot>
@@ -223,7 +227,7 @@ export function TargetField(
223227
<DocumentIcon />
224228
</Text>
225229
</Box>
226-
<Placeholder size={1}>Search document</Placeholder>
230+
<Placeholder size={1}>{t('form.input.target.search.placeholder')}</Placeholder>
227231
</Flex>
228232
</EmptyReferenceRoot>
229233
)}

‎packages/sanity/src/tasks/src/tasks/components/form/fields/assignee/AssigneeCreateFormField.tsx

+7-6
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import {Badge, Card, Flex, Text, TextSkeleton} from '@sanity/ui'
22
// eslint-disable-next-line camelcase
33
import {getTheme_v2} from '@sanity/ui/theme'
44
import {useCallback, useMemo} from 'react'
5-
import {set, type StringInputProps} from 'sanity'
5+
import {set, type StringInputProps, useTranslation} from 'sanity'
66
import styled, {css} from 'styled-components'
77

8+
import {tasksLocaleNamespace} from '../../../../../../i18n'
89
import {useMentionUser} from '../../../../context'
910
import {TasksUserAvatar} from '../../../TasksUserAvatar'
1011
import {AssigneeSelectionMenu} from './AssigneeSelectionMenu'
@@ -31,15 +32,15 @@ export function AssigneeCreateFormField(props: StringInputProps) {
3132
)
3233

3334
const onSelect = useCallback((userId: string) => onChange(set(userId)), [onChange])
34-
35+
const {t} = useTranslation(tasksLocaleNamespace)
3536
const displayText = useMemo(() => {
3637
if (value) {
3738
if (mentionOptions.loading) return <TextSkeleton animated style={{width: '10ch'}} />
3839
if (mentionedUser) return mentionedUser.displayName || mentionedUser.email
39-
if (!mentionedUser) return 'User not found'
40+
if (!mentionedUser) return t('form.input.assignee.user-not-found.text')
4041
}
41-
return 'Search username'
42-
}, [mentionOptions.loading, mentionedUser, value])
42+
return t('form.input.assignee.search.placeholder')
43+
}, [mentionOptions.loading, mentionedUser, value, t])
4344

4445
return (
4546
<AssigneeSelectionMenu
@@ -57,7 +58,7 @@ export function AssigneeCreateFormField(props: StringInputProps) {
5758

5859
{value && mentionedUser && !mentionedUser.granted && (
5960
<Badge fontSize={1} mode="outline">
60-
Unauthorized
61+
{t('form.input.assignee.unauthorized.text')}
6162
</Badge>
6263
)}
6364
</Flex>

‎packages/sanity/src/tasks/src/tasks/components/form/fields/assignee/AssigneeEditFormField.tsx

+7-6
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import {
88
TextSkeleton,
99
} from '@sanity/ui'
1010
import {useCallback, useMemo} from 'react'
11-
import {type FormPatch, type PatchEvent, type Path, set, useFormValue} from 'sanity'
11+
import {type FormPatch, type PatchEvent, type Path, set, useFormValue, useTranslation} from 'sanity'
1212
import styled from 'styled-components'
1313

14+
import {tasksLocaleNamespace} from '../../../../../../i18n'
1415
import {useMentionUser} from '../../../../context'
1516
import {TasksUserAvatar} from '../../../TasksUserAvatar'
1617
import {AssigneeSelectionMenu} from './AssigneeSelectionMenu'
@@ -31,7 +32,7 @@ export function AssigneeEditFormField(props: {
3132
() => mentionOptions.data?.find((u) => u.id === value),
3233
[mentionOptions.data, value],
3334
)
34-
35+
const {t} = useTranslation(tasksLocaleNamespace)
3536
const onSelect = useCallback(
3637
(userId: string) => {
3738
onChange(set(userId, path))
@@ -46,10 +47,10 @@ export function AssigneeEditFormField(props: {
4647
if (value) {
4748
if (mentionOptions.loading) return <TextSkeleton animated style={{width: '10ch'}} />
4849
if (mentionedUser) return mentionedUser.displayName || mentionedUser.email
49-
if (!mentionedUser) return 'User not found'
50+
if (!mentionedUser) return t('form.input.assignee.user-not-found.text')
5051
}
51-
return 'Not assigned'
52-
}, [mentionOptions.loading, mentionedUser, value])
52+
return t('form.input.assignee.no-user-assigned.text')
53+
}, [mentionOptions.loading, mentionedUser, value, t])
5354

5455
return (
5556
<AssigneeSelectionMenu
@@ -69,7 +70,7 @@ export function AssigneeEditFormField(props: {
6970

7071
{value && mentionedUser && !mentionedUser.granted && (
7172
<Badge fontSize={1} mode="outline">
72-
Unauthorized
73+
{t('form.input.assignee.unauthorized.text')}
7374
</Badge>
7475
)}
7576
</Flex>

‎packages/sanity/src/tasks/src/tasks/components/form/fields/assignee/AssigneeSelectionMenu.tsx

+8-5
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import {
1313
import {motion} from 'framer-motion'
1414
import {deburr} from 'lodash'
1515
import {type ChangeEvent, type KeyboardEvent, useCallback, useMemo, useRef, useState} from 'react'
16-
import {LoadingBlock, type UserWithPermission} from 'sanity'
16+
import {LoadingBlock, type UserWithPermission, useTranslation} from 'sanity'
1717
import styled from 'styled-components'
1818

1919
import {MenuButton} from '../../../../../../../ui-components'
20+
import {tasksLocaleNamespace} from '../../../../../../i18n'
2021
import {useMentionUser} from '../../../../context'
2122
import {TasksUserAvatar} from '../../../TasksUserAvatar'
2223

@@ -28,7 +29,7 @@ function MentionUserMenuItem(props: {
2829
pressed: boolean
2930
}) {
3031
const {user, onSelect, pressed} = props
31-
32+
const {t} = useTranslation(tasksLocaleNamespace)
3233
const handleSelect = useCallback(() => onSelect(user.id), [user, onSelect])
3334
return (
3435
<MenuItem onClick={handleSelect} padding={1} disabled={!user.granted} pressed={pressed}>
@@ -42,7 +43,7 @@ function MentionUserMenuItem(props: {
4243

4344
{!user.granted && (
4445
<Badge fontSize={1} mode="outline">
45-
Unauthorized
46+
{t('form.input.assignee.unauthorized.text')}
4647
</Badge>
4748
)}
4849
</Flex>
@@ -137,6 +138,8 @@ function MentionsMenu({onSelect, value = ''}: {onSelect: SelectItemHandler; valu
137138
}
138139
}, [])
139140

141+
const {t} = useTranslation(tasksLocaleNamespace)
142+
140143
if (mentionOptions.loading) {
141144
return (
142145
<Container width={0}>
@@ -148,7 +151,7 @@ function MentionsMenu({onSelect, value = ''}: {onSelect: SelectItemHandler; valu
148151
return (
149152
<div onKeyDown={handleKeyDown}>
150153
<TextInput
151-
placeholder="Search username"
154+
placeholder={t('form.input.assignee.search.placeholder')}
152155
autoFocus
153156
border={false}
154157
onChange={handleSearchChange}
@@ -162,7 +165,7 @@ function MentionsMenu({onSelect, value = ''}: {onSelect: SelectItemHandler; valu
162165
{filteredOptions.length === 0 ? (
163166
<Box padding={3}>
164167
<Text align="center" size={1} muted>
165-
No users found
168+
{t('form.input.assignee.search.no-users.text')}
166169
</Text>
167170
</Box>
168171
) : (

‎packages/sanity/src/tasks/src/tasks/components/form/tasksFormBuilder/FormCreate.tsx

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import {Box, Flex, Switch, Text, useToast} from '@sanity/ui'
22
import {useCallback, useState} from 'react'
3-
import {type ObjectInputProps, set} from 'sanity'
3+
import {type ObjectInputProps, set, useTranslation} from 'sanity'
44

55
import {Button} from '../../../../../../ui-components'
6+
import {tasksLocaleNamespace} from '../../../../../i18n'
67
import {useTasksNavigation} from '../../../context'
78
import {useRemoveTask} from '../../../hooks/useRemoveTask'
89
import {type TaskDocument} from '../../../types'
@@ -39,13 +40,13 @@ export function FormCreate(props: ObjectInputProps) {
3940
setViewMode({type: 'list'})
4041
}, [setViewMode])
4142
const {handleRemove, removeStatus} = useRemoveTask({id: value._id, onRemoved: onRemove})
42-
43+
const {t} = useTranslation(tasksLocaleNamespace)
4344
const handleCreate = useCallback(() => {
4445
if (!value?.title) {
4546
toast.push({
4647
closable: true,
4748
status: 'error',
48-
title: 'Title is required',
49+
title: t('form.status.error.title-required'),
4950
})
5051
return
5152
}
@@ -63,9 +64,9 @@ export function FormCreate(props: ObjectInputProps) {
6364
toast.push({
6465
closable: true,
6566
status: 'success',
66-
title: 'Task created',
67+
title: t('form.status.success'),
6768
})
68-
}, [setViewMode, setActiveTab, onChange, createMore, toast, value])
69+
}, [setViewMode, setActiveTab, onChange, createMore, toast, value, t])
6970

7071
return (
7172
<>
@@ -76,20 +77,20 @@ export function FormCreate(props: ObjectInputProps) {
7677
<Flex align="center" gap={2} style={{flexGrow: viewMode === 'draft' ? 1 : 0}}>
7778
<Switch onChange={handleCreateMore} checked={createMore} />
7879
<Text size={1} muted>
79-
Create more
80+
{t('form.input.create-more.text')}
8081
</Text>
8182
</Flex>
8283

8384
{viewMode === 'draft' && (
8485
<Button
85-
text="Discard"
86+
text={t('buttons.discard.text')}
8687
onClick={handleRemove}
8788
mode="bleed"
8889
disabled={removeStatus === 'loading'}
8990
loading={removeStatus === 'loading'}
9091
/>
9192
)}
92-
<Button text="Create Task" onClick={handleCreate} />
93+
<Button text={t('buttons.create.text')} onClick={handleCreate} />
9394
</Flex>
9495
</Box>
9596
</>

‎packages/sanity/src/tasks/src/tasks/components/form/tasksFormBuilder/FormEdit.tsx

+17-4
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ import {
1313
set,
1414
TransformPatches,
1515
useCurrentUser,
16+
useTranslation,
1617
} from 'sanity'
1718
import styled, {css} from 'styled-components'
1819

1920
import {CommentsProvider} from '../../../../../../structure/comments'
2021
import {MenuButton, MenuItem} from '../../../../../../ui-components'
22+
import {tasksLocaleNamespace} from '../../../../../i18n'
2123
import {useTasksNavigation} from '../../../context'
2224
import {useRemoveTask} from '../../../hooks/useRemoveTask'
2325
import {type TaskDocument} from '../../../types'
@@ -48,6 +50,8 @@ function FormActionsMenu({id, value}: {id: string; value: TaskDocument}) {
4850
setViewMode({type: 'duplicate', duplicateTaskValues: value})
4951
}, [setViewMode, value])
5052

53+
const {t} = useTranslation(tasksLocaleNamespace)
54+
5155
return (
5256
<>
5357
<Box paddingTop={3}>
@@ -60,11 +64,19 @@ function FormActionsMenu({id, value}: {id: string; value: TaskDocument}) {
6064
}}
6165
menu={
6266
<Menu>
63-
<MenuItem text="Duplicate task" icon={CopyIcon} onClick={duplicateTask} />
64-
<MenuItem text="Copy link to task" icon={LinkIcon} onClick={handleCopyLinkToTask} />
67+
<MenuItem
68+
text={t('menuitem.duplicate.text')}
69+
icon={CopyIcon}
70+
onClick={duplicateTask}
71+
/>
72+
<MenuItem
73+
text={t('menuitem.copylink.text')}
74+
icon={LinkIcon}
75+
onClick={handleCopyLinkToTask}
76+
/>
6577
<MenuDivider />
6678
<MenuItem
67-
text="Delete task"
79+
text={t('menuitem.delete.text')}
6880
icon={TrashIcon}
6981
onClick={removeTask.handleOpenDialog}
7082
tone="critical"
@@ -82,6 +94,7 @@ function FormEditInner(props: ObjectInputProps) {
8294
const statusField = props.schemaType.fields.find((f) => f.name === 'status')
8395
const value = props.value as TaskDocument
8496
const currentUser = useCurrentUser()
97+
const {t} = useTranslation(tasksLocaleNamespace)
8598
const handleChangeAndSubscribe = useCallback(
8699
(patch: FormPatch | PatchEvent | FormPatch[]) => {
87100
const subscribers = value.subscribers || []
@@ -108,7 +121,7 @@ function FormEditInner(props: ObjectInputProps) {
108121
onChange={handleChangeAndSubscribe}
109122
value={props.value?.title}
110123
path={['title']}
111-
placeholder="Task title"
124+
placeholder={t('form.input.title.placeholder')}
112125
/>
113126
</Stack>
114127
<FormActionsMenu id={props.value?._id} value={value} />

‎packages/sanity/src/tasks/src/tasks/components/list/TasksList.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import {ChevronDownIcon} from '@sanity/icons'
22
import {Box, Flex, MenuDivider, Stack, Text} from '@sanity/ui'
33
import {Fragment, useMemo} from 'react'
4+
import {useTranslation} from 'sanity'
45
import styled from 'styled-components'
56

7+
import {tasksLocaleNamespace} from '../../../../i18n'
68
import {TASK_STATUS} from '../../constants/TaskStatus'
79
import {type TaskDocument} from '../../types'
810
import {TasksListItem} from './TasksListItem'
@@ -104,12 +106,13 @@ export function TasksList(props: TasksListProps) {
104106

105107
const hasOpenTasks = tasksByStatus.open?.length > 0
106108
const hasClosedTasks = tasksByStatus.closed?.length > 0
109+
const {t} = useTranslation(tasksLocaleNamespace)
107110

108111
return (
109112
<Stack space={4}>
110113
{!hasOpenTasks && !hasClosedTasks ? (
111114
<Text as="p" size={1} muted>
112-
No tasks
115+
{t('list.empty.text')}
113116
</Text>
114117
) : (
115118
<>

‎packages/sanity/src/tasks/src/tasks/components/sidebar/TasksActiveTabNavigation.tsx

+7-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import {Box, Flex, Text} from '@sanity/ui'
33
// eslint-disable-next-line camelcase
44
import {getTheme_v2} from '@sanity/ui/theme'
55
import {useCallback} from 'react'
6+
import {useTranslation} from 'sanity'
67
import styled from 'styled-components'
78

89
import {Button, Tooltip, TooltipDelayGroupProvider} from '../../../../../ui-components'
10+
import {tasksLocaleNamespace} from '../../../../i18n'
911
import {useTasksNavigation} from '../../context'
1012
import {type TaskDocument} from '../../types'
1113

@@ -46,25 +48,27 @@ export function TasksActiveTabNavigation(props: TasksActiveTabNavigationProps) {
4648
setViewMode({type: 'edit', id: nextTaskId})
4749
}, [currentItemIndex, items, setViewMode])
4850

51+
const {t} = useTranslation(tasksLocaleNamespace)
52+
4953
if (!items.length) return null
5054
return (
5155
<TooltipDelayGroupProvider>
5256
<Flex gap={1} align="center">
5357
<Button
54-
tooltipProps={{content: 'Go to previous task'}}
58+
tooltipProps={{content: t('buttons.previous.tooltip')}}
5559
mode="bleed"
5660
icon={ChevronLeftIcon}
5761
onClick={goToPreviousTask}
5862
/>
59-
<Tooltip content={'Open tasks'}>
63+
<Tooltip content={t('panel.navigation.tooltip')}>
6064
<Box paddingY={2}>
6165
<Text size={1}>
6266
{currentItemIndex + 1} / {items.length}
6367
</Text>
6468
</Box>
6569
</Tooltip>
6670
<Button
67-
tooltipProps={{content: 'Go to next task'}}
71+
tooltipProps={{content: t('buttons.next.tooltip')}}
6872
mode="bleed"
6973
icon={ChevronRightIcon}
7074
onClick={goToNextTask}

‎packages/sanity/src/tasks/src/tasks/components/sidebar/TasksHeaderDraftsMenu.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import {CheckmarkIcon, ChevronDownIcon} from '@sanity/icons'
22
import {Box, Menu, MenuDivider, Text} from '@sanity/ui'
33
import {useCallback, useMemo} from 'react'
4-
import {useCurrentUser} from 'sanity'
4+
import {useCurrentUser, useTranslation} from 'sanity'
55
import styled from 'styled-components'
66

77
import {Button, MenuButton, type MenuButtonProps, MenuItem} from '../../../../../ui-components'
8+
import {tasksLocaleNamespace} from '../../../../i18n'
89
import {useTasks, useTasksNavigation} from '../../context'
910
import {type TaskDocument} from '../../types'
1011

@@ -72,17 +73,19 @@ export function TasksHeaderDraftsMenu() {
7273
[setViewMode],
7374
)
7475

76+
const {t} = useTranslation(tasksLocaleNamespace)
77+
7578
if (!draftTasks.length) return null
7679

7780
return (
7881
<MenuButton
79-
button={<Button text="Drafts" mode="ghost" iconRight={ChevronDownIcon} />}
82+
button={<Button text={t('buttons.draft.text')} mode="ghost" iconRight={ChevronDownIcon} />}
8083
id="edit-task-menu"
8184
menu={
8285
<StyledMenu>
8386
<Box padding={3}>
8487
<Text size={1} weight="semibold">
85-
Drafts
88+
{t('panel.drafts.title')}
8689
</Text>
8790
</Box>
8891

‎packages/sanity/src/tasks/src/tasks/components/sidebar/TasksSidebarHeader.tsx

+16-6
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import {
88
} from '@sanity/ui'
99
// eslint-disable-next-line camelcase
1010
import {useCallback} from 'react'
11-
import {BetaBadge} from 'sanity'
11+
import {BetaBadge, useTranslation} from 'sanity'
1212

1313
import {Button} from '../../../../../ui-components'
14+
import {tasksLocaleNamespace} from '../../../../i18n'
1415
import {useTasksNavigation} from '../../context'
1516
import {type TaskDocument} from '../../types'
1617
import {TasksActiveTabNavigation} from './TasksActiveTabNavigation'
@@ -36,24 +37,28 @@ export function TasksSidebarHeader(props: TasksSidebarHeaderProps) {
3637
setViewMode({type: 'list'})
3738
}, [setViewMode])
3839

40+
const {t} = useTranslation(tasksLocaleNamespace)
41+
3942
return (
4043
<Flex justify="space-between" align="center" gap={1}>
4144
<Flex align="center" flex={1}>
4245
{viewMode === 'list' ? (
4346
<Box padding={2}>
4447
<Text size={2} weight="semibold">
45-
Tasks
48+
{t('panel.title')}
4649
</Text>
4750
</Box>
4851
) : (
4952
<>
5053
<UIButton mode="bleed" space={2} padding={2} onClick={handleGoBack}>
51-
<Text size={1}>Tasks</Text>
54+
<Text size={1}>{t('panel.title')}</Text>
5255
</UIButton>
5356
<ChevronRightIcon />
5457
<Box paddingX={2}>
5558
<Text size={1} weight="semibold" style={{textTransform: 'capitalize'}}>
56-
{viewMode === 'create' || viewMode === 'draft' ? 'Create' : activeTabId}
59+
{viewMode === 'create' || viewMode === 'draft'
60+
? t('panel.create.title')
61+
: activeTabId}
5762
</Text>
5863
</Box>
5964
</>
@@ -64,12 +69,17 @@ export function TasksSidebarHeader(props: TasksSidebarHeaderProps) {
6469
{viewMode === 'edit' && <TasksActiveTabNavigation items={allItems} />}
6570
<Flex gap={1}>
6671
{viewMode === 'list' && (
67-
<Button icon={AddIcon} onClick={handleTaskCreate} mode="bleed" text="New task" />
72+
<Button
73+
icon={AddIcon}
74+
onClick={handleTaskCreate}
75+
mode="bleed"
76+
text={t('buttons.new.text')}
77+
/>
6878
)}
6979

7080
<Button
7181
tooltipProps={{
72-
content: 'Close sidebar',
82+
content: t('panel.close.tooltip'),
7383
}}
7484
iconRight={CloseIcon}
7585
mode="bleed"

0 commit comments

Comments
 (0)
Please sign in to comment.