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
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>
|
||||
|
||||
Reference in New Issue
Block a user