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
@@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button';
import { updateDesktopSettings } from '@/lib/persistence';
import { copyTextToClipboard } from '@/lib/clipboard';
import { restartDesktopApp } from '@/lib/desktop';
import { useI18n } from '@/lib/i18n';
const INSTALL_COMMAND = 'curl -fsSL https://opencode.ai/install | bash';
const DOCS_URL = 'https://opencode.ai/docs';
@@ -24,7 +25,7 @@ type LocalSetupScreenProps = {
onSwitchToRemote?: () => void;
};
function BashCommand({ onCopy }: { onCopy: () => void }) {
function BashCommand({ onCopy, copyTitle }: { onCopy: () => void; copyTitle: string }) {
return (
<div className="flex items-center justify-center gap-3">
<code>
@@ -37,7 +38,7 @@ function BashCommand({ onCopy }: { onCopy: () => void }) {
<button
onClick={onCopy}
className="inline-flex items-center text-muted-foreground hover:text-foreground transition-colors"
title="Copy to clipboard"
title={copyTitle}
>
<RiFileCopyLine className="h-4 w-4" />
</button>
@@ -53,6 +54,7 @@ export function LocalSetupScreen({
isFromRecovery = false,
onSwitchToRemote,
}: LocalSetupScreenProps) {
const { t } = useI18n();
const [copied, setCopied] = React.useState(false);
const [showHint, setShowHint] = React.useState(false);
const [isDesktopApp, setIsDesktopApp] = React.useState(false);
@@ -156,7 +158,7 @@ export function LocalSetupScreen({
try {
const selected = await tauri.dialog.open({
title: 'Select opencode binary',
title: t('onboarding.localSetup.dialog.selectOpencodeBinary'),
multiple: false,
directory: false,
});
@@ -166,7 +168,7 @@ export function LocalSetupScreen({
} catch {
// ignore
}
}, [isDesktopApp]);
}, [isDesktopApp, t]);
const handleApplyPath = React.useCallback(async () => {
setIsRetrying(true);
@@ -205,14 +207,14 @@ export function LocalSetupScreen({
// CLI is available, proceed to main screen
onCliAvailable?.();
} else {
setCheckError('OpenCode CLI is not ready yet. Please confirm installation is complete and try again.');
setCheckError(t('onboarding.localSetup.errors.cliNotReady'));
}
} catch (err) {
setCheckError(err instanceof Error ? err.message : 'Detection failed');
setCheckError(err instanceof Error ? err.message : t('onboarding.localSetup.errors.detectionFailed'));
} finally {
setIsChecking(false);
}
}, [checkCliAvailability, onCliAvailable]);
}, [checkCliAvailability, onCliAvailable, t]);
const docsUrl = platform === 'windows' ? WINDOWS_WSL_DOCS_URL : DOCS_URL;
const binaryPlaceholder =
@@ -234,26 +236,26 @@ export function LocalSetupScreen({
onClick={onBack}
className="p-0 text-muted-foreground hover:text-foreground"
>
&larr; Back
{t('onboarding.common.actions.back')}
</Button>
</div>
<div className="space-y-4">
<h1 className="text-3xl font-semibold tracking-tight text-foreground">
Setting Up OpenCode
{t('onboarding.localSetup.title')}
</h1>
<p className="text-muted-foreground">
Install OpenCode CLI to continue.
{t('onboarding.localSetup.description')}
</p>
</div>
{platform === 'windows' && (
<div className="mx-auto max-w-2xl rounded-lg border border-border bg-background/50 p-4 text-left">
<div className="text-sm text-foreground">Windows setup (WSL recommended)</div>
<div className="text-sm text-foreground">{t('onboarding.localSetup.windows.title')}</div>
<ol className="mt-2 list-decimal space-y-1 pl-5 text-sm text-muted-foreground">
<li>Install WSL (if needed) with <code className="text-foreground/80">wsl --install</code> in PowerShell.</li>
<li>Run the install command below inside your WSL terminal.</li>
<li>If OpenChamber does not detect OpenCode automatically, set the binary path below.</li>
<li>{t('onboarding.localSetup.windows.stepInstallWsl')} <code className="text-foreground/80">wsl --install</code> {t('onboarding.localSetup.windows.stepInstallWslSuffix')}</li>
<li>{t('onboarding.localSetup.windows.stepRunInstallInWsl')}</li>
<li>{t('onboarding.localSetup.windows.stepSetBinaryPath')}</li>
</ol>
</div>
)}
@@ -263,10 +265,10 @@ export function LocalSetupScreen({
{copied ? (
<div className="flex items-center justify-center gap-2" style={{ color: 'var(--status-success)' }}>
<RiCheckLine className="h-4 w-4" />
Copied to clipboard
{t('onboarding.common.status.copiedToClipboard')}
</div>
) : (
<BashCommand onCopy={handleCopy} />
<BashCommand onCopy={handleCopy} copyTitle={t('onboarding.common.copyToClipboard')} />
)}
</div>
</div>
@@ -277,7 +279,7 @@ export function LocalSetupScreen({
rel="noopener noreferrer"
className="text-sm text-muted-foreground hover:text-foreground transition-colors inline-flex items-center gap-1 justify-center"
>
{platform === 'windows' ? 'View Windows + WSL documentation' : 'View documentation'}
{platform === 'windows' ? t('onboarding.localSetup.docs.windows') : t('onboarding.localSetup.docs.default')}
<RiExternalLinkLine className="h-3 w-3" />
</a>
@@ -295,17 +297,17 @@ export function LocalSetupScreen({
className="w-full max-w-xs"
size="lg"
>
{isChecking ? 'Checking...' : "I've completed installation, check and continue"}
{isChecking ? t('onboarding.localSetup.actions.checking') : t('onboarding.localSetup.actions.checkAndContinue')}
</Button>
<p className="text-xs text-muted-foreground">
Click to check if OpenCode CLI is available. If successful, you'll automatically enter the main screen.
{t('onboarding.localSetup.helper.checkAndContinue')}
</p>
</div>
<div className="mx-auto w-full max-w-xl pt-4">
<div className="space-y-2">
<div className="text-sm text-muted-foreground">Already installed? Set the OpenCode CLI path:</div>
<div className="text-sm text-muted-foreground">{t('onboarding.localSetup.field.alreadyInstalled')}</div>
<div className="flex gap-2">
<Input
value={opencodeBinary}
@@ -320,30 +322,30 @@ export function LocalSetupScreen({
onClick={handleBrowse}
disabled={isRetrying || !isDesktopApp || !isTauriShell()}
>
Browse
{t('onboarding.localSetup.actions.browse')}
</Button>
<Button
type="button"
onClick={handleApplyPath}
disabled={isRetrying}
>
Apply
{t('onboarding.localSetup.actions.apply')}
</Button>
</div>
<div className="text-xs text-muted-foreground/70">Saves to OpenChamber settings and reloads OpenCode configuration.</div>
<div className="text-xs text-muted-foreground/70">{t('onboarding.localSetup.helper.saveAndReload')}</div>
</div>
</div>
{isFromRecovery && onSwitchToRemote && (
<div className="text-center pt-4">
<p className="text-sm text-muted-foreground mb-2">
Prefer to use a remote server?
{t('onboarding.localSetup.remotePreference')}
</p>
<Button
variant="link"
onClick={onSwitchToRemote}
>
Connect to Remote Server &rarr;
{t('onboarding.localSetup.actions.connectRemoteServer')}
</Button>
</div>
)}
@@ -354,22 +356,22 @@ export function LocalSetupScreen({
{platform === 'windows' ? (
<>
<p className="text-sm text-muted-foreground/70">
On Windows, install and run OpenCode in WSL for best compatibility.
{t('onboarding.localSetup.windows.hintInstallInWsl')}
</p>
<p className="text-sm text-muted-foreground/70">
If detection fails, set a native path (<code className="text-foreground/70">opencode.cmd</code>/<code className="text-foreground/70">opencode.exe</code>), <code className="text-foreground/70">wsl.exe</code>, or <code className="text-foreground/70">wsl:/usr/local/bin/opencode</code>.
{t('onboarding.localSetup.windows.hintDetectionFailed')}
</p>
</>
) : (
<>
<p className="text-sm text-muted-foreground/70">
Already installed? Make sure <code className="text-foreground/70">opencode</code> is in your PATH
{t('onboarding.localSetup.hint.ensurePath')}
</p>
<p className="text-sm text-muted-foreground/70">
or set <code className="text-foreground/70">OPENCODE_BINARY</code> environment variable.
{t('onboarding.localSetup.hint.setEnv')}
</p>
<p className="text-sm text-muted-foreground/70">
If you see <code className="text-foreground/70">env: node: No such file or directory</code> or <code className="text-foreground/70">env: bun: No such file or directory</code>, install that runtime or ensure it is on PATH.
{t('onboarding.localSetup.hint.missingRuntime')}
</p>
</>
)}