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
File diff suppressed because it is too large Load Diff
@@ -23,6 +23,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useI18n } from '@/lib/i18n';
interface McpSidebarProps {
onItemSelect?: () => void;
@@ -59,6 +60,7 @@ const StatusDot: React.FC<{ tone: StatusTone; enabled: boolean }> = ({ tone, ena
};
export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
const { t } = useI18n();
const bgClass = 'bg-background';
const { mcpServers, selectedMcpName, setSelectedMcp, setMcpDraft, loadMcpConfigs, deleteMcp } =
@@ -143,13 +145,13 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
if (result.ok) {
if (result.reloadFailed) {
toast.warning(result.message || `MCP server "${deleteTarget.name}" deleted, but OpenCode reload failed`, {
description: result.warning || 'Refresh the MCP list if the UI looks stale.',
description: result.warning || t('settings.mcp.sidebar.toast.refreshListIfStale'),
});
} else {
toast.success(result.message || `MCP server "${deleteTarget.name}" deleted`);
toast.success(result.message || t('settings.mcp.sidebar.toast.serverDeleted', { name: deleteTarget.name }));
}
} else {
toast.error('Failed to delete MCP server');
toast.error(t('settings.mcp.sidebar.toast.deleteFailed'));
}
setDeleteTarget(null);
setIsDeleting(false);
@@ -159,14 +161,14 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
<div className={cn('flex h-full flex-col', bgClass)}>
<div className="border-b px-3 pt-4 pb-3">
<div className="mb-3 flex items-center justify-between gap-3">
<h2 className="text-base font-semibold text-foreground">MCP Servers</h2>
<h2 className="text-base font-semibold text-foreground">{t('settings.mcp.sidebar.title')}</h2>
<button
type="button"
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"
disabled={isRefreshingStatus}
onClick={handleRefresh}
aria-label="Refresh MCP status"
title="Refresh MCP status"
aria-label={t('settings.mcp.sidebar.actions.refreshStatusAria')}
title={t('settings.mcp.sidebar.actions.refreshStatusTitle')}
>
<RiRefreshLine className={cn('h-4 w-4', isRefreshingStatus && 'animate-spin')} />
</button>
@@ -174,13 +176,13 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
<SettingsProjectSelector className="mb-3" />
<div className="flex items-center justify-between gap-2">
<span className="typography-meta text-muted-foreground">
Total {mcpServers.length}
{t('settings.mcp.sidebar.total', { count: mcpServers.length })}
</span>
<Button size="sm"
variant="ghost"
className="h-7 w-7 px-0 -my-1 text-muted-foreground"
onClick={handleCreateNew}
title="Add MCP server"
title={t('settings.mcp.sidebar.actions.addServerTitle')}
>
<RiAddLine className="h-3.5 w-3.5" />
</Button>
@@ -192,15 +194,15 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
{mcpServers.length === 0 ? (
<div className="py-12 px-4 text-center text-muted-foreground">
<RiPlugLine className="mx-auto mb-3 h-10 w-10 opacity-50" />
<p className="typography-ui-label font-medium">No MCP servers configured</p>
<p className="typography-meta mt-1 opacity-75">Use the + button above to add one</p>
<p className="typography-ui-label font-medium">{t('settings.mcp.sidebar.empty.title')}</p>
<p className="typography-meta mt-1 opacity-75">{t('settings.mcp.sidebar.empty.description')}</p>
</div>
) : (
<>
{projectServers.length > 0 && (
<>
<div className="px-2 pb-1.5 pt-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Project Servers
{t('settings.mcp.sidebar.group.projectServers')}
</div>
{projectServers.map((server) => {
const runtimeStatus = mcpStatus[server.name];
@@ -231,7 +233,10 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
<div className="flex items-center gap-2">
<StatusDot tone={tone} enabled={server.enabled} />
<span className="typography-ui-label font-normal truncate text-foreground">{server.name}</span>
<span title={server.type === 'local' ? 'Local server' : 'Remote server'}>
<span title={server.type === 'local'
? t('settings.mcp.sidebar.serverType.localTitle')
: t('settings.mcp.sidebar.serverType.remoteTitle')}
>
{server.type === 'local' ? (
<RiServerLine className="h-3 w-3 text-muted-foreground/60 flex-shrink-0" />
) : (
@@ -261,7 +266,7 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
className="text-destructive focus:text-destructive"
>
<RiDeleteBinLine className="h-4 w-4 mr-px" />
Delete
{t('settings.common.actions.delete')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
@@ -274,7 +279,7 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
{userServers.length > 0 && (
<>
<div className="px-2 pb-1.5 pt-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
User Servers
{t('settings.mcp.sidebar.group.userServers')}
</div>
{userServers.map((server) => {
const runtimeStatus = mcpStatus[server.name];
@@ -305,7 +310,10 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
<div className="flex items-center gap-2">
<StatusDot tone={tone} enabled={server.enabled} />
<span className="typography-ui-label font-normal truncate text-foreground">{server.name}</span>
<span title={server.type === 'local' ? 'Local server' : 'Remote server'}>
<span title={server.type === 'local'
? t('settings.mcp.sidebar.serverType.localTitle')
: t('settings.mcp.sidebar.serverType.remoteTitle')}
>
{server.type === 'local' ? (
<RiServerLine className="h-3 w-3 text-muted-foreground/60 flex-shrink-0" />
) : (
@@ -335,7 +343,7 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
className="text-destructive focus:text-destructive"
>
<RiDeleteBinLine className="h-4 w-4 mr-px" />
Delete
{t('settings.common.actions.delete')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
@@ -353,14 +361,14 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
open={deleteTarget !== null}
onOpenChange={(open) => { if (!open && !isDeleting) setDeleteTarget(null); }}
>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>Delete MCP Server</DialogTitle>
<DialogDescription>
Are you sure you want to delete "{deleteTarget?.name}"? This will remove it from{' '}
<code className="text-foreground">opencode.json</code>.
</DialogDescription>
</DialogHeader>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>{t('settings.mcp.sidebar.deleteDialog.title')}</DialogTitle>
<DialogDescription>
{t('settings.mcp.sidebar.deleteDialog.descriptionPrefix', { name: deleteTarget?.name || '' })}{' '}
<code className="text-foreground">opencode.json</code>.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
size="sm"
@@ -368,10 +376,10 @@ export const McpSidebar: React.FC<McpSidebarProps> = ({ onItemSelect }) => {
onClick={() => setDeleteTarget(null)}
disabled={isDeleting}
>
Cancel
{t('settings.common.actions.cancel')}
</Button>
<Button size="sm" onClick={handleDelete} disabled={isDeleting}>
{isDeleting ? 'Deleting' : 'Delete'}
{isDeleting ? t('settings.mcp.sidebar.actions.deleting') : t('settings.common.actions.delete')}
</Button>
</DialogFooter>
</DialogContent>