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
@@ -26,6 +26,7 @@ import type { ToolPopupContent, DiffViewMode } from './types';
import { DiffViewToggle } from './DiffViewToggle';
import { VirtualizedCodeBlock, type CodeLine } from './parts/VirtualizedCodeBlock';
import { JsonTreeView } from '@/components/ui/JsonTreeView';
import { useI18n } from '@/lib/i18n';
interface ToolOutputDialogProps {
popup: ToolPopupContent;
@@ -302,6 +303,7 @@ const ImagePreviewDialog: React.FC<{
onOpenChange: (open: boolean) => void;
isMobile: boolean;
}> = ({ popup, onOpenChange, isMobile }) => {
const { t } = useI18n();
const gallery = React.useMemo(() => {
const baseImage = popup.image;
if (!baseImage) return [] as Array<{ url: string; mimeType?: string; filename?: string; size?: number }>;
@@ -434,7 +436,7 @@ const ImagePreviewDialog: React.FC<{
onMouseDown={(event) => event.stopPropagation()}
onClick={showPrevious}
className="absolute left-3 top-1/2 -translate-y-1/2 z-10 h-10 w-10 flex items-center justify-center rounded-full bg-black/40 text-foreground/90 hover:bg-black/55 focus:outline-none focus:ring-2 focus:ring-primary/60"
aria-label="Previous image"
aria-label={t('chat.toolOutputDialog.image.previousAria')}
>
<RiArrowLeftSLine className="h-6 w-6" />
</button>
@@ -443,7 +445,7 @@ const ImagePreviewDialog: React.FC<{
onMouseDown={(event) => event.stopPropagation()}
onClick={showNext}
className="absolute right-3 top-1/2 -translate-y-1/2 z-10 h-10 w-10 flex items-center justify-center rounded-full bg-black/40 text-foreground/90 hover:bg-black/55 focus:outline-none focus:ring-2 focus:ring-primary/60"
aria-label="Next image"
aria-label={t('chat.toolOutputDialog.image.nextAria')}
>
<RiArrowRightSLine className="h-6 w-6" />
</button>
@@ -472,7 +474,7 @@ const ImagePreviewDialog: React.FC<{
type="button"
className="h-8 w-8 flex items-center justify-center rounded-lg text-muted-foreground/80 hover:text-foreground focus:outline-none focus:ring-2 focus:ring-primary/60"
onClick={() => onOpenChange(false)}
aria-label="Close image preview"
aria-label={t('chat.toolOutputDialog.image.closeAria')}
>
<RiCloseLine className="h-4 w-4" />
</button>
@@ -632,6 +634,7 @@ const MermaidPreviewDialog: React.FC<{
onOpenChange: (open: boolean) => void;
isMobile: boolean;
}> = ({ popup, onOpenChange, isMobile }) => {
const { t } = useI18n();
const [source, setSource] = React.useState<string>(popup.mermaid?.source || '');
const [status, setStatus] = React.useState<'idle' | 'loading' | 'ready' | 'error'>(popup.mermaid?.source ? 'ready' : 'idle');
const [errorMessage, setErrorMessage] = React.useState<string>('');
@@ -707,7 +710,7 @@ const MermaidPreviewDialog: React.FC<{
const target = popup.mermaid;
if (!target?.url) {
setStatus('error');
setErrorMessage('Missing Mermaid source URL.');
setErrorMessage(t('chat.toolOutputDialog.mermaid.missingSource'));
return;
}
@@ -773,7 +776,7 @@ const MermaidPreviewDialog: React.FC<{
return;
}
setStatus('error');
setErrorMessage(error instanceof Error ? error.message : 'Unable to load Mermaid diagram.');
setErrorMessage(error instanceof Error ? error.message : t('chat.toolOutputDialog.mermaid.loadFailed'));
});
}, [decodeDataUrl, normalizeFilePath, popup.mermaid]);
@@ -918,7 +921,7 @@ const MermaidPreviewDialog: React.FC<{
type="button"
className="h-8 w-8 flex items-center justify-center rounded-lg text-muted-foreground/80 hover:text-foreground focus:outline-none focus:ring-2 focus:ring-primary/60"
onClick={() => onOpenChange(false)}
aria-label="Close diagram preview"
aria-label={t('chat.toolOutputDialog.mermaid.closeAria')}
>
<RiCloseLine className="h-4 w-4" />
</button>
@@ -931,14 +934,14 @@ const MermaidPreviewDialog: React.FC<{
{status === 'loading' && (
<div className="h-full min-h-28 flex items-center justify-center gap-2 text-muted-foreground typography-meta">
<RiLoader4Line className="h-4 w-4 animate-spin" />
<span>Loading diagram...</span>
<span>{t('chat.toolOutputDialog.mermaid.loading')}</span>
</div>
)}
{status === 'error' && (
<div className="rounded-xl border border-border/30 bg-muted/20 p-3 space-y-3">
<p className="typography-markdown" style={{ color: 'var(--status-error)' }}>
{errorMessage || 'Unable to render Mermaid diagram.'}
{errorMessage || t('chat.toolOutputDialog.mermaid.renderFailed')}
</p>
<button
type="button"
@@ -951,7 +954,7 @@ const MermaidPreviewDialog: React.FC<{
color: 'var(--surface-foreground)',
}}
>
Retry
{t('chat.toolOutputDialog.mermaid.retry')}
</button>
</div>
)}
@@ -983,6 +986,7 @@ const MermaidPreviewDialog: React.FC<{
};
const ToolOutputDialog: React.FC<ToolOutputDialogProps> = ({ popup, onOpenChange, syntaxTheme, isMobile }) => {
const { t } = useI18n();
const [diffViewMode, setDiffViewMode] = React.useState<DiffViewMode>('unified');
const pierreThemeConfig = usePierreThemeConfig();
@@ -1112,7 +1116,13 @@ const ToolOutputDialog: React.FC<ToolOutputDialogProps> = ({ popup, onOpenChange
if (tool === 'todowrite' || tool === 'todoread') {
return (
renderTodoOutput(popup.content) || (
renderTodoOutput(popup.content, {
total: t('chat.todo.total'),
inProgress: t('chat.todo.inProgress'),
pending: t('chat.todo.pending'),
completed: t('chat.todo.completed'),
cancelled: t('chat.todo.cancelled'),
}) || (
<SyntaxHighlighter
style={syntaxTheme}
language="json"
@@ -1214,8 +1224,8 @@ const ToolOutputDialog: React.FC<ToolOutputDialogProps> = ({ popup, onOpenChange
</div>
) : (
<div className="p-8 text-muted-foreground typography-ui-header">
<div className="mb-2">Command completed successfully</div>
<div className="typography-meta">No output was produced</div>
<div className="mb-2">{t('chat.toolOutputDialog.commandCompleted')}</div>
<div className="typography-meta">{t('chat.toolOutputDialog.noOutputProduced')}</div>
</div>
)}
</div>