Add i18n foundation and translations (#1027)
* feat: add i18n foundation * feat: localize sessions sidebar * Localize multirun/scheduled tasks and fix dialog dropdown interactions * localize git sidebar surface and add zh-CN keys * feat(ui): localize context panel, diff/plan views, and context sidebar content * fix(config): resolve user config home via fs/home before embedded home * localize header/chat UI and complete model/worktree panel strings * localize worktree + github issue/pr dialog flows * localize settings sections and split settings i18n dictionaries * localize additional settings sections and sidebars * localize more settings pages and dialogs * fix settings select trigger localization * localize tunnel settings ui surface * localize additional settings sections * localize keyboard shortcuts labels in settings * localize terminal and utility dialogs surfaces * feat(i18n): localize remaining UI strings * Add Ukrainian locale * Add Spanish locale * Add Brazilian Portuguese locale * Polish locale translations
This commit is contained in:
committed by
GitHub
parent
87db2ea210
commit
7d7285655d
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { RiCheckboxBlankLine, RiCheckboxLine } from '@remixicon/react';
|
||||
import type { Session } from '@opencode-ai/sdk/v2';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
export type DeleteSessionConfirmState = {
|
||||
session: Session;
|
||||
@@ -16,21 +17,45 @@ export function SessionDeleteConfirmDialog(props: {
|
||||
setShowDeletionDialog: (next: boolean) => void;
|
||||
onConfirm: () => Promise<void> | void;
|
||||
}): React.ReactNode {
|
||||
const { t } = useI18n();
|
||||
const { value, setValue, showDeletionDialog, setShowDeletionDialog, onConfirm } = props;
|
||||
const untitledSession = t('sessions.sidebar.session.untitled');
|
||||
|
||||
return (
|
||||
<Dialog open={Boolean(value)} onOpenChange={(open) => { if (!open) setValue(null); }}>
|
||||
<DialogContent showCloseButton={false} className="max-w-sm gap-5">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{value?.archivedBucket ? 'Delete session?' : 'Archive session?'}</DialogTitle>
|
||||
<DialogTitle>{value?.archivedBucket
|
||||
? t('sessions.sidebar.dialogs.deleteSession.title')
|
||||
: t('sessions.sidebar.dialogs.archiveSession.title')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{value && value.descendantCount > 0
|
||||
? value.archivedBucket
|
||||
? `"${value.session.title || 'Untitled Session'}" and its ${value.descendantCount} sub-task${value.descendantCount === 1 ? '' : 's'} will be permanently deleted.`
|
||||
: `"${value.session.title || 'Untitled Session'}" and its ${value.descendantCount} sub-task${value.descendantCount === 1 ? '' : 's'} will be archived.`
|
||||
? value.descendantCount === 1
|
||||
? t('sessions.sidebar.dialogs.deleteSession.withOneSubtask', {
|
||||
sessionTitle: value.session.title || untitledSession,
|
||||
count: value.descendantCount,
|
||||
})
|
||||
: t('sessions.sidebar.dialogs.deleteSession.withManySubtasks', {
|
||||
sessionTitle: value.session.title || untitledSession,
|
||||
count: value.descendantCount,
|
||||
})
|
||||
: value.descendantCount === 1
|
||||
? t('sessions.sidebar.dialogs.archiveSession.withOneSubtask', {
|
||||
sessionTitle: value.session.title || untitledSession,
|
||||
count: value.descendantCount,
|
||||
})
|
||||
: t('sessions.sidebar.dialogs.archiveSession.withManySubtasks', {
|
||||
sessionTitle: value.session.title || untitledSession,
|
||||
count: value.descendantCount,
|
||||
})
|
||||
: value?.archivedBucket
|
||||
? `"${value?.session.title || 'Untitled Session'}" will be permanently deleted.`
|
||||
: `"${value?.session.title || 'Untitled Session'}" will be archived.`}
|
||||
? t('sessions.sidebar.dialogs.deleteSession.single', {
|
||||
sessionTitle: value?.session.title || untitledSession,
|
||||
})
|
||||
: t('sessions.sidebar.dialogs.archiveSession.single', {
|
||||
sessionTitle: value?.session.title || untitledSession,
|
||||
})}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="w-full sm:items-center sm:justify-between">
|
||||
@@ -41,7 +66,7 @@ export function SessionDeleteConfirmDialog(props: {
|
||||
aria-pressed={!showDeletionDialog}
|
||||
>
|
||||
{!showDeletionDialog ? <RiCheckboxLine className="h-4 w-4 text-primary" /> : <RiCheckboxBlankLine className="h-4 w-4" />}
|
||||
Never ask
|
||||
{t('sessions.sidebar.dialogs.neverAsk')}
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
@@ -49,14 +74,14 @@ export function SessionDeleteConfirmDialog(props: {
|
||||
onClick={() => setValue(null)}
|
||||
className="inline-flex h-8 items-center justify-center rounded-md border border-border px-3 typography-ui-label text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
|
||||
>
|
||||
Cancel
|
||||
{t('sessions.sidebar.dialogs.cancel')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void onConfirm()}
|
||||
className="inline-flex h-8 items-center justify-center rounded-md bg-destructive px-3 typography-ui-label text-destructive-foreground hover:bg-destructive/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50"
|
||||
>
|
||||
{value?.archivedBucket ? 'Delete' : 'Archive'}
|
||||
{value?.archivedBucket ? t('sessions.sidebar.bulkActions.delete') : t('sessions.sidebar.bulkActions.archive')}
|
||||
</button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
@@ -77,14 +102,24 @@ export function BulkSessionDeleteConfirmDialog(props: {
|
||||
setShowDeletionDialog: (next: boolean) => void;
|
||||
onConfirm: () => Promise<void> | void;
|
||||
}): React.ReactNode {
|
||||
const { t } = useI18n();
|
||||
const { value, setValue, showDeletionDialog, setShowDeletionDialog, onConfirm } = props;
|
||||
const archived = value?.archivedBucket === true;
|
||||
const n = value?.sessionCount ?? 0;
|
||||
const plural = n === 1 ? '' : 's';
|
||||
const title = archived ? 'Delete sessions?' : 'Archive sessions?';
|
||||
const title = archived
|
||||
? (n === 1
|
||||
? t('sessions.sidebar.dialogs.deleteSession.title')
|
||||
: t('sessions.sidebar.dialogs.deleteSessions.title'))
|
||||
: (n === 1
|
||||
? t('sessions.sidebar.dialogs.archiveSession.title')
|
||||
: t('sessions.sidebar.dialogs.archiveSessions.title'));
|
||||
const description = archived
|
||||
? `${n} session${plural} will be permanently deleted.`
|
||||
: `${n} session${plural} will be archived.`;
|
||||
? (n === 1
|
||||
? t('sessions.sidebar.dialogs.deleteSessions.singleDescription', { count: n })
|
||||
: t('sessions.sidebar.dialogs.deleteSessions.pluralDescription', { count: n }))
|
||||
: (n === 1
|
||||
? t('sessions.sidebar.dialogs.archiveSessions.singleDescription', { count: n })
|
||||
: t('sessions.sidebar.dialogs.archiveSessions.pluralDescription', { count: n }));
|
||||
|
||||
return (
|
||||
<Dialog open={Boolean(value)} onOpenChange={(open) => { if (!open) setValue(null); }}>
|
||||
@@ -101,7 +136,7 @@ export function BulkSessionDeleteConfirmDialog(props: {
|
||||
aria-pressed={!showDeletionDialog}
|
||||
>
|
||||
{!showDeletionDialog ? <RiCheckboxLine className="h-4 w-4 text-primary" /> : <RiCheckboxBlankLine className="h-4 w-4" />}
|
||||
Never ask
|
||||
{t('sessions.sidebar.dialogs.neverAsk')}
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
@@ -109,14 +144,14 @@ export function BulkSessionDeleteConfirmDialog(props: {
|
||||
onClick={() => setValue(null)}
|
||||
className="inline-flex h-8 items-center justify-center rounded-md border border-border px-3 typography-ui-label text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
|
||||
>
|
||||
Cancel
|
||||
{t('sessions.sidebar.dialogs.cancel')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void onConfirm()}
|
||||
className="inline-flex h-8 items-center justify-center rounded-md bg-destructive px-3 typography-ui-label text-destructive-foreground hover:bg-destructive/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50"
|
||||
>
|
||||
{archived ? 'Delete' : 'Archive'}
|
||||
{archived ? t('sessions.sidebar.bulkActions.delete') : t('sessions.sidebar.bulkActions.archive')}
|
||||
</button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
@@ -138,17 +173,32 @@ export function FolderDeleteConfirmDialog(props: {
|
||||
setValue: (next: DeleteFolderConfirmState) => void;
|
||||
onConfirm: () => void;
|
||||
}): React.ReactNode {
|
||||
const { t } = useI18n();
|
||||
const { value, setValue, onConfirm } = props;
|
||||
|
||||
return (
|
||||
<Dialog open={Boolean(value)} onOpenChange={(open) => { if (!open) setValue(null); }}>
|
||||
<DialogContent showCloseButton={false} className="max-w-sm gap-5">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete folder?</DialogTitle>
|
||||
<DialogTitle>{t('sessions.sidebar.dialogs.deleteFolder.title')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{value && (value.subFolderCount > 0 || value.sessionCount > 0)
|
||||
? `"${value.folderName}" will be deleted${value.subFolderCount > 0 ? ` along with ${value.subFolderCount} sub-folder${value.subFolderCount === 1 ? '' : 's'}` : ''}. Sessions inside will not be deleted.`
|
||||
: `"${value?.folderName}" will be permanently deleted.`}
|
||||
? value.subFolderCount > 0
|
||||
? value.subFolderCount === 1
|
||||
? t('sessions.sidebar.dialogs.deleteFolder.withOneSubfolder', {
|
||||
folderName: value.folderName,
|
||||
count: value.subFolderCount,
|
||||
})
|
||||
: t('sessions.sidebar.dialogs.deleteFolder.withManySubfolders', {
|
||||
folderName: value.folderName,
|
||||
count: value.subFolderCount,
|
||||
})
|
||||
: t('sessions.sidebar.dialogs.deleteFolder.withContentsNoSubfolders', {
|
||||
folderName: value.folderName,
|
||||
})
|
||||
: t('sessions.sidebar.dialogs.deleteFolder.single', {
|
||||
folderName: value?.folderName ?? '',
|
||||
})}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
@@ -157,14 +207,14 @@ export function FolderDeleteConfirmDialog(props: {
|
||||
onClick={() => setValue(null)}
|
||||
className="inline-flex h-8 items-center justify-center rounded-md border border-border px-3 typography-ui-label text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
|
||||
>
|
||||
Cancel
|
||||
{t('sessions.sidebar.dialogs.cancel')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onConfirm}
|
||||
className="inline-flex h-8 items-center justify-center rounded-md bg-destructive px-3 typography-ui-label text-destructive-foreground hover:bg-destructive/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50"
|
||||
>
|
||||
Delete
|
||||
{t('sessions.sidebar.bulkActions.delete')}
|
||||
</button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
Reference in New Issue
Block a user