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
@@ -3,6 +3,7 @@ import type { Session } from '@opencode-ai/sdk/v2';
|
||||
import { RiLayoutLeftLine } from '@remixicon/react';
|
||||
import { toast } from '@/components/ui';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { isDesktopLocalOriginActive, isDesktopShell, isTauriShell } from '@/lib/desktop';
|
||||
import { isDesktopWindowFullscreen as getDesktopWindowFullscreen, onDesktopWindowResized, startDesktopWindowDrag } from '@/lib/desktopNative';
|
||||
import { sessionEvents } from '@/lib/sessionEvents';
|
||||
@@ -125,6 +126,7 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
||||
hideDirectoryControls = false,
|
||||
showOnlyMainWorkspace = false,
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
const [isSessionSearchOpen, setIsSessionSearchOpen] = React.useState(false);
|
||||
const [sessionSearchQuery, setSessionSearchQuery] = React.useState('');
|
||||
const sessionSearchContainerRef = React.useRef<HTMLDivElement | null>(null);
|
||||
@@ -542,8 +544,8 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
||||
|
||||
const emptyState = (
|
||||
<div className="py-6 text-center text-muted-foreground">
|
||||
<p className="typography-ui-label font-semibold">No sessions yet</p>
|
||||
<p className="typography-meta mt-1">Create your first session to start coding.</p>
|
||||
<p className="typography-ui-label font-semibold">{t('sessions.sidebar.empty.noSessions.title')}</p>
|
||||
<p className="typography-meta mt-1">{t('sessions.sidebar.empty.noSessions.description')}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -574,16 +576,16 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
||||
void updateStore.checkForUpdates().then(() => {
|
||||
const { available, error } = useUpdateStore.getState();
|
||||
if (error) {
|
||||
toast.error('Failed to check for updates', { description: error });
|
||||
toast.error(t('sessions.sidebar.updateCheck.errorTitle'), { description: error });
|
||||
return;
|
||||
}
|
||||
if (!available) {
|
||||
toast.success('You are on the latest version');
|
||||
toast.success(t('sessions.sidebar.updateCheck.latestVersion'));
|
||||
return;
|
||||
}
|
||||
setUpdateDialogOpen(true);
|
||||
});
|
||||
}, [updateStore]);
|
||||
}, [t, updateStore]);
|
||||
|
||||
const handleOpenSettings = React.useCallback(() => {
|
||||
if (mobileVariant) {
|
||||
@@ -664,21 +666,21 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
||||
if (result.success && result.path) {
|
||||
const added = addProject(result.path, { id: result.projectId });
|
||||
if (!added) {
|
||||
toast.error('Failed to add project', {
|
||||
description: 'Please select a valid directory.',
|
||||
toast.error(t('sessions.sidebar.directory.errorAddProjectTitle'), {
|
||||
description: t('sessions.sidebar.directory.errorAddProjectDescription'),
|
||||
});
|
||||
}
|
||||
} else if (result.error && result.error !== 'Directory selection cancelled') {
|
||||
toast.error('Failed to select directory', {
|
||||
toast.error(t('sessions.sidebar.directory.errorSelectDirectoryTitle'), {
|
||||
description: result.error,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Desktop: Error selecting directory:', error);
|
||||
toast.error('Failed to select directory');
|
||||
toast.error(t('sessions.sidebar.directory.errorSelectDirectoryTitle'));
|
||||
});
|
||||
}, [addProject, tauriIpcAvailable]);
|
||||
}, [addProject, t, tauriIpcAvailable]);
|
||||
|
||||
// Auto-expand parent session when navigating to a subagent (child) session
|
||||
React.useEffect(() => {
|
||||
@@ -722,12 +724,12 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
||||
toggleFolderCollapse(parentId);
|
||||
}
|
||||
|
||||
const newFolder = createFolder(scopeKey, 'New folder', parentId);
|
||||
const newFolder = createFolder(scopeKey, t('sessions.sidebar.folder.newFolderName'), parentId);
|
||||
setRenamingFolderId(newFolder.id);
|
||||
setRenameFolderDraft(newFolder.name);
|
||||
return newFolder;
|
||||
},
|
||||
[collapsedFolderIds, toggleFolderCollapse, createFolder],
|
||||
[collapsedFolderIds, toggleFolderCollapse, createFolder, t],
|
||||
);
|
||||
|
||||
const toggleGroupSessionLimit = React.useCallback((groupId: string) => {
|
||||
@@ -890,8 +892,8 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
||||
|
||||
const searchEmptyState = (
|
||||
<div className="py-6 text-center text-muted-foreground">
|
||||
<p className="typography-ui-label font-semibold">No matching sessions</p>
|
||||
<p className="typography-meta mt-1">Try a different title, branch, folder, or path.</p>
|
||||
<p className="typography-ui-label font-semibold">{t('sessions.sidebar.empty.noMatches.title')}</p>
|
||||
<p className="typography-meta mt-1">{t('sessions.sidebar.empty.noMatches.description')}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1037,9 +1039,9 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
||||
};
|
||||
|
||||
return [
|
||||
{ key: 'active-now' as const, title: 'recent', items: activeNowSessions.map(toItem) },
|
||||
{ key: 'active-now' as const, title: t('sessions.sidebar.activity.recentTitle'), items: activeNowSessions.map(toItem) },
|
||||
];
|
||||
}, [activeNowSessions, sessionSidebarMetaById]);
|
||||
}, [activeNowSessions, sessionSidebarMetaById, t]);
|
||||
|
||||
const recentSessionIds = React.useMemo(() => {
|
||||
return new Set(activitySections.flatMap((section) => section.items.map((item) => item.node.session.id)));
|
||||
@@ -1468,15 +1470,31 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
||||
if (ids.length === 0) return;
|
||||
if (bulkScopeIsArchived) {
|
||||
const { deletedIds, failedIds } = await deleteSessions(ids);
|
||||
if (deletedIds.length > 0) toast.success(`Deleted ${deletedIds.length} session${deletedIds.length === 1 ? '' : 's'}`);
|
||||
if (failedIds.length > 0) toast.error(`Failed to delete ${failedIds.length} session${failedIds.length === 1 ? '' : 's'}`);
|
||||
if (deletedIds.length > 0) {
|
||||
toast.success(deletedIds.length === 1
|
||||
? t('sessions.sidebar.bulkActions.deletedSingle', { count: deletedIds.length })
|
||||
: t('sessions.sidebar.bulkActions.deletedPlural', { count: deletedIds.length }));
|
||||
}
|
||||
if (failedIds.length > 0) {
|
||||
toast.error(failedIds.length === 1
|
||||
? t('sessions.sidebar.bulkActions.failedDeleteSingle', { count: failedIds.length })
|
||||
: t('sessions.sidebar.bulkActions.failedDeletePlural', { count: failedIds.length }));
|
||||
}
|
||||
} else {
|
||||
const { archivedIds, failedIds } = await archiveSessions(ids);
|
||||
if (archivedIds.length > 0) toast.success(`Archived ${archivedIds.length} session${archivedIds.length === 1 ? '' : 's'}`);
|
||||
if (failedIds.length > 0) toast.error(`Failed to archive ${failedIds.length} session${failedIds.length === 1 ? '' : 's'}`);
|
||||
if (archivedIds.length > 0) {
|
||||
toast.success(archivedIds.length === 1
|
||||
? t('sessions.sidebar.bulkActions.archivedSingle', { count: archivedIds.length })
|
||||
: t('sessions.sidebar.bulkActions.archivedPlural', { count: archivedIds.length }));
|
||||
}
|
||||
if (failedIds.length > 0) {
|
||||
toast.error(failedIds.length === 1
|
||||
? t('sessions.sidebar.bulkActions.failedArchiveSingle', { count: failedIds.length })
|
||||
: t('sessions.sidebar.bulkActions.failedArchivePlural', { count: failedIds.length }));
|
||||
}
|
||||
}
|
||||
useSessionMultiSelectStore.getState().clear();
|
||||
}, [archiveSessions, bulkScopeIsArchived, deleteSessions, selectedIds]);
|
||||
}, [archiveSessions, bulkScopeIsArchived, deleteSessions, selectedIds, t]);
|
||||
|
||||
const handleBulkDelete = React.useCallback(() => {
|
||||
const count = selectedIds.size;
|
||||
@@ -1576,13 +1594,13 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
||||
type="button"
|
||||
onClick={toggleSidebar}
|
||||
className={desktopSidebarToggleButtonClass}
|
||||
aria-label="Close sessions"
|
||||
aria-label={t('sessions.sidebar.header.actions.closeSessions')}
|
||||
>
|
||||
<RiLayoutLeftLine className="h-[18px] w-[18px]" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Close sessions</p>
|
||||
<p>{t('sessions.sidebar.header.actions.closeSessions')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user