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
@@ -33,6 +33,7 @@ import type {
GitHubPullRequestSummary,
} from '@/lib/api/types';
import type { ProjectRef } from '@/lib/worktrees/worktreeManager';
import { useI18n } from '@/lib/i18n';
type GitHubTab = 'issues' | 'prs';
@@ -56,6 +57,7 @@ export function GitHubIntegrationDialog({
onOpenChange,
onSelect,
}: GitHubIntegrationDialogProps) {
const { t } = useI18n();
const isMobile = useUIStore((state) => state.isMobile);
const { github } = useRuntimeAPIs();
const githubAuthStatus = useGitHubAuthStore((state) => state.status);
@@ -101,7 +103,7 @@ export function GitHubIntegrationDialog({
if (activeTab === 'issues' && github.issuesList) {
const result = await github.issuesList(projectDirectory, { page: 1 });
if (result.connected === false) {
setError('GitHub not connected');
setError(t('session.githubIntegration.error.notConnected'));
setIssues([]);
} else {
setIssues(result.issues ?? []);
@@ -111,7 +113,7 @@ export function GitHubIntegrationDialog({
} else if (activeTab === 'prs' && github.prsList) {
const result = await github.prsList(projectDirectory, { page: 1 });
if (result.connected === false) {
setError('GitHub not connected');
setError(t('session.githubIntegration.error.notConnected'));
setPrs([]);
} else {
setPrs(result.prs ?? []);
@@ -120,11 +122,11 @@ export function GitHubIntegrationDialog({
}
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load data');
setError(err instanceof Error ? err.message : t('session.githubIntegration.error.loadDataFailed'));
} finally {
setLoading(false);
}
}, [projectDirectory, github, githubAuthChecked, githubAuthStatus, activeTab]);
}, [projectDirectory, github, githubAuthChecked, githubAuthStatus, activeTab, t]);
// Load more data
const loadMore = React.useCallback(async () => {
@@ -199,15 +201,15 @@ export function GitHubIntegrationDialog({
setValidations(prev => new Map(prev).set(branchName, {
isValid: !isBlocked,
error: isBlocked ? 'Branch is already checked out in a worktree' : null,
error: isBlocked ? t('session.githubIntegration.validation.branchAlreadyCheckedOut') : null,
}));
} catch {
setValidations(prev => new Map(prev).set(branchName, {
isValid: false,
error: 'Validation failed',
error: t('session.githubIntegration.validation.failed'),
}));
}
}, [projectRef, validations]);
}, [projectRef, validations, t]);
// Validate PR branches when loaded
React.useEffect(() => {
@@ -297,12 +299,12 @@ export function GitHubIntegrationDialog({
<div className="flex-1 flex flex-col items-center justify-center p-8 gap-4">
<RiGithubLine className="h-12 w-12 text-muted-foreground" />
<div className="text-center">
<p className="typography-ui-label text-foreground">Connect to GitHub</p>
<p className="typography-ui-label text-foreground">{t('session.githubIntegration.connect.title')}</p>
<p className="typography-small text-muted-foreground mt-1">
Link issues or pull requests to auto-fill worktree details
{t('session.githubIntegration.connect.description')}
</p>
</div>
<Button onClick={openGitHubSettings} size="sm">Connect GitHub</Button>
<Button onClick={openGitHubSettings} size="sm">{t('session.githubIntegration.connect.action')}</Button>
</div>
) : (
<>
@@ -312,7 +314,9 @@ export function GitHubIntegrationDialog({
<Input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={activeTab === 'issues' ? "Search issues or enter #123..." : "Search PRs or enter #456..."}
placeholder={activeTab === 'issues'
? t('session.githubIntegration.search.issuesPlaceholder')
: t('session.githubIntegration.search.prsPlaceholder')}
className="h-8 pl-9"
/>
</div>
@@ -360,7 +364,7 @@ export function GitHubIntegrationDialog({
))
) : (
<div className="flex items-center justify-center h-[300px] text-center typography-small text-muted-foreground">
No issues found
{t('session.githubIntegration.empty.noIssuesFound')}
</div>
)}
@@ -372,7 +376,7 @@ export function GitHubIntegrationDialog({
onClick={() => void loadMore()}
className="h-7 text-xs"
>
Load more
{t('session.githubIntegration.actions.loadMore')}
</Button>
</div>
)}
@@ -427,7 +431,7 @@ export function GitHubIntegrationDialog({
})
) : (
<div className="flex items-center justify-center h-[300px] text-center typography-small text-muted-foreground">
No pull requests found
{t('session.githubIntegration.empty.noPullRequestsFound')}
</div>
)}
@@ -439,7 +443,7 @@ export function GitHubIntegrationDialog({
onClick={() => void loadMore()}
className="h-7 text-xs"
>
Load more
{t('session.githubIntegration.actions.loadMore')}
</Button>
</div>
)}
@@ -473,7 +477,9 @@ export function GitHubIntegrationDialog({
<div className="flex items-center gap-2 px-2 h-8 rounded-md bg-muted/50 border border-border/50">
<RiCheckLine className="h-3.5 w-3.5 text-status-success shrink-0" />
<span className="typography-small truncate max-w-[150px]">
{selectedIssue ? `Issue #${selectedIssue.number}` : `PR #${selectedPr?.number}`}
{selectedIssue
? t('session.githubIntegration.selected.issueNumber', { number: selectedIssue.number })
: t('session.githubIntegration.selected.prNumber', { number: selectedPr?.number ?? '' })}
</span>
<button
onClick={handleClear}
@@ -490,10 +496,10 @@ export function GitHubIntegrationDialog({
<Checkbox
checked={includeDiff}
onChange={(checked) => setIncludeDiff(checked)}
ariaLabel="Include PR diff in session context"
ariaLabel={t('session.githubIntegration.includeDiffAria')}
/>
<span className="typography-small text-foreground">
Include PR diff
{t('session.githubIntegration.includeDiff')}
</span>
</label>
)}
@@ -510,7 +516,7 @@ export function GitHubIntegrationDialog({
onClick={() => onOpenChange(false)}
className={cn(isMobile && 'flex-1')}
>
Cancel
{t('session.githubIntegration.actions.cancel')}
</Button>
<Button
size="sm"
@@ -518,7 +524,7 @@ export function GitHubIntegrationDialog({
disabled={!canConfirm}
className={cn(isMobile && 'flex-1')}
>
Select
{t('session.githubIntegration.actions.select')}
</Button>
</div>
</div>
@@ -529,21 +535,21 @@ export function GitHubIntegrationDialog({
{isMobile ? (
<MobileOverlayPanel
open={open}
title="Select from GitHub"
title={t('session.githubIntegration.title')}
onClose={() => onOpenChange(false)}
footer={!isGitHubConnected ? undefined : footerContent}
renderHeader={(closeButton) => (
<div className="flex flex-col gap-2 px-3 py-2 border-b border-border/40">
<div className="flex items-center justify-between">
<h2 className="typography-ui-label font-semibold text-foreground">Select from GitHub</h2>
<h2 className="typography-ui-label font-semibold text-foreground">{t('session.githubIntegration.title')}</h2>
{closeButton}
</div>
{/* Tabs - using SortableTabsStrip */}
<div className="w-full">
<SortableTabsStrip
items={[
{ id: 'issues', label: 'Issues', icon: <RiGitBranchLine className="h-3.5 w-3.5" /> },
{ id: 'prs', label: 'Pull Requests', icon: <RiGitPullRequestLine className="h-3.5 w-3.5" /> },
{ id: 'issues', label: t('session.githubIntegration.tabs.issues'), icon: <RiGitBranchLine className="h-3.5 w-3.5" /> },
{ id: 'prs', label: t('session.githubIntegration.tabs.pullRequests'), icon: <RiGitPullRequestLine className="h-3.5 w-3.5" /> },
]}
activeId={activeTab}
onSelect={(id) => {
@@ -560,7 +566,9 @@ export function GitHubIntegrationDialog({
<div className="flex items-center gap-2 px-2 py-1 rounded-md bg-muted/50 border border-border/50">
<RiCheckLine className="h-3.5 w-3.5 text-status-success shrink-0" />
<span className="typography-small truncate flex-1">
{selectedIssue ? `Issue #${selectedIssue.number}` : `PR #${selectedPr?.number}`}
{selectedIssue
? t('session.githubIntegration.selected.issueNumber', { number: selectedIssue.number })
: t('session.githubIntegration.selected.prNumber', { number: selectedPr?.number ?? '' })}
</span>
<button
onClick={handleClear}
@@ -582,15 +590,15 @@ export function GitHubIntegrationDialog({
<div className="flex items-center gap-3">
<DialogTitle className="flex items-center gap-2 shrink-0">
<RiGithubLine className="h-5 w-5" />
Select from GitHub
{t('session.githubIntegration.title')}
</DialogTitle>
{/* Tabs - using SortableTabsStrip */}
<div className="w-[220px]">
<SortableTabsStrip
items={[
{ id: 'issues', label: 'Issues', icon: <RiGitBranchLine className="h-3.5 w-3.5" /> },
{ id: 'prs', label: 'Pull Requests', icon: <RiGitPullRequestLine className="h-3.5 w-3.5" /> },
{ id: 'issues', label: t('session.githubIntegration.tabs.issues'), icon: <RiGitBranchLine className="h-3.5 w-3.5" /> },
{ id: 'prs', label: t('session.githubIntegration.tabs.pullRequests'), icon: <RiGitPullRequestLine className="h-3.5 w-3.5" /> },
]}
activeId={activeTab}
onSelect={(id) => {