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:
Bohdan Triapitsyn
2026-04-26 14:03:39 +03:00
committed by GitHub
parent 87db2ea210
commit 7d7285655d
198 changed files with 24173 additions and 4365 deletions
@@ -16,6 +16,7 @@ import type { DesktopSettings } from '@/lib/desktop';
import { updateDesktopSettings } from '@/lib/persistence';
import { useFileSystemAccess } from '@/hooks/useFileSystemAccess';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { useI18n } from '@/lib/i18n';
interface DirectoryItem {
name: string;
@@ -53,6 +54,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
isRootReady,
alwaysShowActions = false,
}) => {
const { t } = useI18n();
const { isMobile } = useDeviceInfo();
const [directories, setDirectories] = React.useState<DirectoryItem[]>([]);
const [expandedPaths, setExpandedPaths] = React.useState<Set<string>>(new Set());
@@ -678,7 +680,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
isMobile ? "p-1.5" : "p-1",
alwaysShowActions ? "opacity-60" : "opacity-0 group-hover:opacity-100"
)}
title="Create new directory"
title={t('directoryTree.actions.createNewDirectory')}
>
<RiAddLine className={cn("text-muted-foreground", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
</button>
@@ -693,7 +695,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
isMobile ? "p-1.5" : "p-1",
alwaysShowActions ? "opacity-60" : "opacity-0 group-hover:opacity-100"
)}
title={isPinned ? "Unpin directory" : "Pin directory"}
title={isPinned ? t('directoryTree.actions.unpinDirectory') : t('directoryTree.actions.pinDirectory')}
>
{isPinned ? (
<RiPushpin2Line className={cn("text-primary", isMobile ? "h-3.5 w-3.5" : "h-3 w-3")} />
@@ -745,7 +747,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
}}
onBlur={createDirectory}
className="h-6 typography-meta flex-1 selection:bg-interactive-selection selection:text-interactive-selection-foreground"
placeholder="new_directory"
placeholder={t('directoryTree.field.newDirectoryPlaceholder')}
/>
<button
onClick={(e) => {
@@ -754,7 +756,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
createDirectory();
}}
className="p-1 hover:bg-interactive-hover rounded"
title="Create directory"
title={t('directoryTree.actions.createDirectory')}
>
<RiCheckLine className="h-3 w-3 text-green-600" />
</button>
@@ -765,7 +767,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
cancelCreatingDirectory();
}}
className="p-1 hover:bg-interactive-hover rounded"
title="Cancel"
title={t('directoryTree.actions.cancel')}
>
<RiCloseLine className="h-3 w-3 text-muted-foreground" />
</button>
@@ -834,7 +836,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
}}
onBlur={createDirectory}
className="h-6 typography-meta flex-1 selection:bg-interactive-selection selection:text-interactive-selection-foreground"
placeholder="new_directory"
placeholder={t('directoryTree.field.newDirectoryPlaceholder')}
/>
<button
onClick={(e) => {
@@ -843,7 +845,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
createDirectory();
}}
className="p-1 hover:bg-interactive-hover rounded"
title="Create directory"
title={t('directoryTree.actions.createDirectory')}
>
<RiCheckLine className="h-3 w-3 text-green-600" />
</button>
@@ -854,7 +856,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
cancelCreatingDirectory();
}}
className="p-1 hover:bg-interactive-hover rounded"
title="Cancel"
title={t('directoryTree.actions.cancel')}
>
<RiCloseLine className="h-3 w-3 text-muted-foreground" />
</button>
@@ -919,7 +921,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
"hover:bg-interactive-hover rounded-md transition-opacity",
isMobile ? "p-1.5 opacity-60" : "p-1 opacity-0 group-hover:opacity-100"
)}
title="Unpin directory"
title={t('directoryTree.actions.unpinDirectory')}
>
<RiPushpin2Line className={cn("text-primary", isMobile ? "h-3.5 w-3.5" : "h-3.5 w-3.5")} />
</button>
@@ -956,7 +958,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
togglePin(path);
}}
className="p-1 opacity-0 group-hover:opacity-100 hover:bg-interactive-hover rounded transition-opacity"
title="Unpin directory"
title={t('directoryTree.actions.unpinDirectory')}
>
<RiPushpin2Line className="h-3 w-3 text-primary" />
</button>
@@ -968,7 +970,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
<>
{!rootReady ? (
<div className="px-3 py-2 typography-ui-label text-muted-foreground">
Locating home directory...
{t('directoryTree.state.locatingHomeDirectory')}
</div>
) : (
<>
@@ -987,7 +989,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
) : (
<RiArrowRightSLine className={isMobile ? "h-3.5 w-3.5" : "h-3 w-3"} />
)}
<span>Pinned</span>
<span>{t('directoryTree.section.pinned')}</span>
<span className="ml-auto typography-micro text-muted-foreground/60 normal-case tracking-normal">
{pinnedDirectories.length}
</span>
@@ -1004,12 +1006,12 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
"typography-meta font-medium text-muted-foreground/80 flex items-center gap-1.5 uppercase tracking-wide",
isMobile ? "px-1.5 py-1" : "px-2 py-1.5"
)}>
Browse
{t('directoryTree.section.browse')}
</div>
{isLoading ? (
<div className="px-3 py-2 typography-ui-label text-muted-foreground">
Loading...
{t('directoryTree.state.loading')}
</div>
) : (
directories.map((item) => renderTreeItem(item))
@@ -1017,7 +1019,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
{!isLoading && directories.length === 0 && (
<div className="px-3 py-2 typography-ui-label text-muted-foreground">
No directories found
{t('directoryTree.state.noDirectoriesFound')}
</div>
)}
</>
@@ -1044,7 +1046,7 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
'w-full h-8 px-2.5 justify-between items-center rounded-lg border border-transparent bg-sidebar-accent/40 text-foreground/90 hover:bg-sidebar-accent/60 typography-meta',
triggerClassName
)}
aria-label="Select working directory"
aria-label={t('directoryTree.actions.selectWorkingDirectoryAria')}
>
<span className="flex items-center gap-1.5 min-w-0 flex-1">
<RiFolder6Line className="h-3 w-3 flex-shrink-0 text-muted-foreground" />