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
@@ -24,6 +24,7 @@ import { useUIStore } from '@/stores/useUIStore';
import { useGitHubAuthStore } from '@/stores/useGitHubAuthStore';
import { renderMagicPrompt } from '@/lib/magicPrompts';
import type { GitHubPullRequestContextResult, GitHubPullRequestSummary, GitHubPullRequestsListResult } from '@/lib/api/types';
import { useI18n } from '@/lib/i18n';
const parsePrNumber = (value: string): number | null => {
const trimmed = value.trim();
@@ -67,6 +68,7 @@ export function GitHubPrPickerDialog({
author?: { login: string; avatarUrl?: string };
}) => void;
}) {
const { t } = useI18n();
const { github } = useRuntimeAPIs();
const githubAuthStatus = useGitHubAuthStore((state) => state.status);
const githubAuthChecked = useGitHubAuthStore((state) => state.hasChecked);
@@ -91,7 +93,7 @@ export function GitHubPrPickerDialog({
const refresh = React.useCallback(async () => {
if (!projectDirectory) {
setResult(null);
setError('No active project');
setError(t('session.githubPrPicker.error.noActiveProject'));
return;
}
if (githubAuthChecked && githubAuthStatus?.connected === false) {
@@ -104,7 +106,7 @@ export function GitHubPrPickerDialog({
}
if (!github?.prsList) {
setResult(null);
setError('GitHub runtime API unavailable');
setError(t('session.githubPrPicker.error.runtimeUnavailable'));
return;
}
@@ -142,11 +144,11 @@ export function GitHubPrPickerDialog({
setHasMore(Boolean(next.hasMore));
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
toast.error('Failed to load more pull requests', { description: message });
toast.error(t('session.githubPrPicker.toast.loadMoreFailed'), { description: message });
} finally {
setIsLoadingMore(false);
}
}, [github, hasMore, isLoading, isLoadingMore, page, projectDirectory]);
}, [github, hasMore, isLoading, isLoadingMore, page, projectDirectory, t]);
React.useEffect(() => {
if (!open) {
@@ -195,11 +197,11 @@ export function GitHubPrPickerDialog({
const attachPr = React.useCallback(async (prNumber: number) => {
if (!projectDirectory) {
toast.error('No active project');
toast.error(t('session.githubPrPicker.error.noActiveProject'));
return;
}
if (!github?.prContext) {
toast.error('GitHub runtime API unavailable');
toast.error(t('session.githubPrPicker.error.runtimeUnavailable'));
return;
}
if (loadingPrNumber) return;
@@ -212,18 +214,18 @@ export function GitHubPrPickerDialog({
});
if (context.connected === false) {
toast.error('GitHub not connected');
toast.error(t('session.githubPrPicker.error.notConnected'));
return;
}
if (!context.pr) {
toast.error('Pull request not found');
toast.error(t('session.githubPrPicker.error.prNotFound'));
return;
}
if (!context.repo) {
toast.error('Repo not resolvable', {
description: 'origin remote must be a GitHub URL',
toast.error(t('session.githubPrPicker.error.repoNotResolvable'), {
description: t('session.githubPrPicker.error.repoMustBeGithub'),
});
return;
}
@@ -250,14 +252,14 @@ export function GitHubPrPickerDialog({
onOpenChange(false);
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
toast.error('Failed to load pull request details', { description: message });
toast.error(t('session.githubPrPicker.toast.loadDetailsFailed'), { description: message });
} finally {
setLoadingPrNumber(null);
}
}, [github, includeDiff, loadingPrNumber, onOpenChange, onSelect, projectDirectory]);
}, [github, includeDiff, loadingPrNumber, onOpenChange, onSelect, projectDirectory, t]);
const title = 'Link GitHub Pull Request';
const description = 'Select a pull request to attach review context to this message.';
const title = t('session.githubPrPicker.title');
const description = t('session.githubPrPicker.description');
const content = (
<>
@@ -265,7 +267,7 @@ export function GitHubPrPickerDialog({
<div className="relative flex-1 min-w-0">
<RiSearchLine className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search by title or #123, or paste pull request URL"
placeholder={t('session.githubPrPicker.searchPlaceholder')}
value={query}
onChange={(e) => setQuery(e.target.value)}
className="pl-9 w-full"
@@ -276,41 +278,41 @@ export function GitHubPrPickerDialog({
onClick={() => setIncludeDiff((prev) => !prev)}
className="h-9 shrink-0 flex items-center gap-2 text-left"
aria-pressed={includeDiff}
aria-label="Include PR diff in attached context"
aria-label={t('session.githubPrPicker.includeDiffAria')}
>
<span onClick={(e) => e.stopPropagation()}>
<Checkbox
checked={includeDiff}
onChange={(checked) => setIncludeDiff(checked)}
ariaLabel="Include PR diff in attached context"
ariaLabel={t('session.githubPrPicker.includeDiffAria')}
/>
</span>
<span className="typography-small text-muted-foreground whitespace-nowrap">Include PR diff</span>
<span className="typography-small text-muted-foreground whitespace-nowrap">{t('session.githubPrPicker.includeDiff')}</span>
</button>
</div>
<div className={cn(isMobile ? 'min-h-0' : 'flex-1 overflow-y-auto')}>
{!projectDirectory ? (
<div className="text-center text-muted-foreground py-8">No active project selected.</div>
<div className="text-center text-muted-foreground py-8">{t('session.githubPrPicker.empty.noActiveProject')}</div>
) : null}
{!github ? (
<div className="text-center text-muted-foreground py-8">GitHub runtime API unavailable.</div>
<div className="text-center text-muted-foreground py-8">{t('session.githubPrPicker.empty.runtimeUnavailable')}</div>
) : null}
{isLoading ? (
<div className="text-center text-muted-foreground py-8 flex items-center justify-center gap-2">
<RiLoader4Line className="h-4 w-4 animate-spin" />
Loading pull requests...
{t('session.githubPrPicker.loading.pullRequests')}
</div>
) : null}
{connected === false ? (
<div className="text-center text-muted-foreground py-8 space-y-3">
<div>GitHub not connected. Connect your GitHub account in settings.</div>
<div>{t('session.githubPrPicker.empty.notConnected')}</div>
<div className="flex justify-center">
<Button variant="outline" size="sm" onClick={openGitHubSettings}>
Open settings
{t('session.githubPrPicker.actions.openSettings')}
</Button>
</div>
</div>
@@ -330,7 +332,7 @@ export function GitHubPrPickerDialog({
>
<span className="typography-meta text-muted-foreground w-5 text-right flex-shrink-0">#</span>
<p className="flex-1 min-w-0 typography-small text-foreground truncate ml-0.5">
Use pull request #{directNumber}
{t('session.githubPrPicker.actions.usePullRequest', { number: directNumber })}
</p>
<div className="flex-shrink-0 h-5 flex items-center mr-2">
{loadingPrNumber === directNumber ? (
@@ -341,7 +343,7 @@ export function GitHubPrPickerDialog({
) : null}
{filtered.length === 0 && !isLoading && connected && github && projectDirectory ? (
<div className="text-center text-muted-foreground py-8">{query ? 'No pull requests found' : 'No open pull requests found'}</div>
<div className="text-center text-muted-foreground py-8">{query ? t('session.githubPrPicker.empty.noPullRequestsFound') : t('session.githubPrPicker.empty.noOpenPullRequestsFound')}</div>
) : null}
{filtered.map((pr) => (
@@ -371,7 +373,7 @@ export function GitHubPrPickerDialog({
rel="noopener noreferrer"
className="hidden group-hover:flex h-5 w-5 items-center justify-center text-muted-foreground hover:text-foreground transition-colors"
onClick={(e) => e.stopPropagation()}
aria-label="Open in GitHub"
aria-label={t('session.githubPrPicker.actions.openInGitHubAria')}
>
<RiExternalLinkLine className="h-4 w-4" />
</a>
@@ -394,10 +396,10 @@ export function GitHubPrPickerDialog({
{isLoadingMore ? (
<span className="inline-flex items-center gap-2">
<RiLoader4Line className="h-4 w-4 animate-spin" />
Loading...
{t('session.githubPrPicker.loading.more')}
</span>
) : (
'Load more'
t('session.githubPrPicker.actions.loadMore')
)}
</button>
</div>