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
@@ -18,6 +18,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { ProviderLogo } from '@/components/ui/ProviderLogo';
|
||||
import { UsageProgressBar } from '@/components/sections/usage/UsageProgressBar';
|
||||
import { PaceIndicator } from '@/components/sections/usage/PaceIndicator';
|
||||
@@ -54,6 +55,7 @@ const SESSIONS_SIDEBAR_MAX_WIDTH = 520;
|
||||
type VSCodeView = 'sessions' | 'chat' | 'settings';
|
||||
|
||||
export const VSCodeLayout: React.FC = () => {
|
||||
const { t } = useI18n();
|
||||
const runtimeApis = useRuntimeAPIs();
|
||||
|
||||
const viewMode = React.useMemo<'sidebar' | 'editor'>(() => {
|
||||
@@ -100,8 +102,8 @@ export const VSCodeLayout: React.FC = () => {
|
||||
if (!currentSessionId) {
|
||||
return null;
|
||||
}
|
||||
return sessions.find((session) => session.id === currentSessionId)?.title || 'Session';
|
||||
}, [currentSessionId, sessions]);
|
||||
return sessions.find((session) => session.id === currentSessionId)?.title || t('vscodeLayout.title.sessionFallback');
|
||||
}, [currentSessionId, sessions, t]);
|
||||
const newSessionDraftOpen = useSessionUIStore((state) => Boolean(state.newSessionDraft?.open));
|
||||
const isSyncingMessages = useViewportStore((state) => state.isSyncing);
|
||||
const hasActiveSessionWork = useDirectorySync((state) => {
|
||||
@@ -379,7 +381,7 @@ export const VSCodeLayout: React.FC = () => {
|
||||
// Editor mode: just chat, no sidebar
|
||||
<div className="flex flex-col h-full">
|
||||
<VSCodeHeader
|
||||
title={sessions.find((session) => session.id === currentSessionId)?.title || 'Chat'}
|
||||
title={sessions.find((session) => session.id === currentSessionId)?.title || t('vscodeLayout.title.chat')}
|
||||
showMcp
|
||||
showContextUsage
|
||||
/>
|
||||
@@ -422,15 +424,15 @@ export const VSCodeLayout: React.FC = () => {
|
||||
onPointerCancel={handleExpandedSidebarResizeEnd}
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-label="Resize sessions sidebar"
|
||||
aria-label={t('vscodeLayout.actions.resizeSessionsSidebarAria')}
|
||||
/>
|
||||
</div>
|
||||
{/* Chat content */}
|
||||
<div className="flex-1 flex flex-col min-w-0">
|
||||
<VSCodeHeader
|
||||
title={newSessionDraftOpen && !currentSessionId
|
||||
? 'New session'
|
||||
: sessions.find((session) => session.id === currentSessionId)?.title || 'Chat'}
|
||||
? t('vscodeLayout.title.newSession')
|
||||
: sessions.find((session) => session.id === currentSessionId)?.title || t('vscodeLayout.title.chat')}
|
||||
showMcp
|
||||
showContextUsage
|
||||
/>
|
||||
@@ -447,7 +449,7 @@ export const VSCodeLayout: React.FC = () => {
|
||||
{/* Sessions list view */}
|
||||
<div className={cn('flex flex-col h-full', currentView !== 'sessions' && 'hidden')}>
|
||||
<VSCodeHeader
|
||||
title="Sessions"
|
||||
title={t('vscodeLayout.title.sessions')}
|
||||
/>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<SessionSidebar
|
||||
@@ -463,8 +465,8 @@ export const VSCodeLayout: React.FC = () => {
|
||||
<div className={cn('flex flex-col h-full', currentView !== 'chat' && 'hidden')}>
|
||||
<VSCodeHeader
|
||||
title={newSessionDraftOpen && !currentSessionId
|
||||
? 'New session'
|
||||
: sessions.find((session) => session.id === currentSessionId)?.title || 'Chat'}
|
||||
? t('vscodeLayout.title.newSession')
|
||||
: sessions.find((session) => session.id === currentSessionId)?.title || t('vscodeLayout.title.chat')}
|
||||
showBack
|
||||
onBack={handleBackToSessions}
|
||||
showMcp
|
||||
@@ -496,6 +498,7 @@ interface VSCodeHeaderProps {
|
||||
}
|
||||
|
||||
const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, onNewSession, onSettings, onAgentManager, showMcp, showContextUsage, showRateLimits }) => {
|
||||
const { t } = useI18n();
|
||||
const getCurrentModel = useConfigStore((s) => s.getCurrentModel);
|
||||
const getContextUsage = useSessionUIStore((state) => state.getContextUsage);
|
||||
const quotaResults = useQuotaStore((state) => state.results);
|
||||
@@ -561,7 +564,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="inline-flex h-7 w-7 items-center justify-center text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
aria-label="Back to sessions"
|
||||
aria-label={t('vscodeLayout.actions.backToSessionsAria')}
|
||||
>
|
||||
<RiArrowLeftLine className="h-5 w-5" />
|
||||
</button>
|
||||
@@ -571,7 +574,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
<button
|
||||
onClick={onNewSession}
|
||||
className="inline-flex h-9 w-9 items-center justify-center p-2 text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
aria-label="New session"
|
||||
aria-label={t('vscodeLayout.actions.newSessionAria')}
|
||||
>
|
||||
<RiAddLine className="h-5 w-5" />
|
||||
</button>
|
||||
@@ -580,7 +583,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
<button
|
||||
onClick={onAgentManager}
|
||||
className="inline-flex h-9 w-9 items-center justify-center p-2 text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
aria-label="Open Agent Manager"
|
||||
aria-label={t('vscodeLayout.actions.openAgentManagerAria')}
|
||||
>
|
||||
<RiRobot2Line className="h-5 w-5" />
|
||||
</button>
|
||||
@@ -601,7 +604,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Rate limits"
|
||||
aria-label={t('vscodeLayout.quota.actions.rateLimitsAria')}
|
||||
className="inline-flex h-9 w-9 items-center justify-center p-2 text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
disabled={isQuotaLoading}
|
||||
>
|
||||
@@ -614,7 +617,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
>
|
||||
<div className="sticky top-0 z-20 bg-[var(--surface-elevated)]">
|
||||
<DropdownMenuLabel className="flex items-center justify-between gap-3 typography-ui-header font-semibold text-foreground">
|
||||
<span>Rate limits</span>
|
||||
<span>{t('vscodeLayout.quota.title')}</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex items-center rounded-md border border-[var(--interactive-border)] p-0.5">
|
||||
<button
|
||||
@@ -627,9 +630,9 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
}`
|
||||
}
|
||||
onClick={() => void handleDisplayModeChange('usage')}
|
||||
aria-label="Show used quota"
|
||||
aria-label={t('vscodeLayout.quota.actions.showUsedAria')}
|
||||
>
|
||||
Used
|
||||
{t('vscodeLayout.quota.mode.used')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -641,9 +644,9 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
}`
|
||||
}
|
||||
onClick={() => void handleDisplayModeChange('remaining')}
|
||||
aria-label="Show remaining quota"
|
||||
aria-label={t('vscodeLayout.quota.actions.showRemainingAria')}
|
||||
>
|
||||
Remaining
|
||||
{t('vscodeLayout.quota.mode.remaining')}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
@@ -651,7 +654,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
className="inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground hover:bg-interactive-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
onClick={() => fetchAllQuotas()}
|
||||
disabled={isQuotaLoading}
|
||||
aria-label="Refresh rate limits"
|
||||
aria-label={t('vscodeLayout.quota.actions.refreshAria')}
|
||||
>
|
||||
<RiRefreshLine className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -659,11 +662,11 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
</DropdownMenuLabel>
|
||||
</div>
|
||||
<div className="border-b border-[var(--interactive-border)] px-2 pb-2 typography-micro text-muted-foreground text-[10px]">
|
||||
Last updated {formatTime(quotaLastUpdated)}
|
||||
{t('vscodeLayout.quota.lastUpdated', { time: formatTime(quotaLastUpdated) })}
|
||||
</div>
|
||||
{!hasRateLimits && (
|
||||
<DropdownMenuItem className="cursor-default" closeOnClick={false}>
|
||||
<span className="typography-ui-label text-muted-foreground">No rate limits available.</span>
|
||||
<span className="typography-ui-label text-muted-foreground">{t('vscodeLayout.quota.noRateLimitsAvailable')}</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{rateLimitGroups.map((group, index) => (
|
||||
@@ -679,7 +682,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
closeOnClick={false}
|
||||
>
|
||||
<span className="typography-ui-label text-muted-foreground">
|
||||
{group.error ?? 'No rate limits reported.'}
|
||||
{group.error ?? t('vscodeLayout.quota.noRateLimitsReported')}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
) : (
|
||||
@@ -735,7 +738,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
<button
|
||||
onClick={onSettings}
|
||||
className="inline-flex h-9 w-9 items-center justify-center p-2 text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
aria-label="Settings"
|
||||
aria-label={t('vscodeLayout.actions.settingsAria')}
|
||||
>
|
||||
<RiSettings3Line className="h-5 w-5" />
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user