This commit is contained in:
mcncarl 2026-05-31 06:35:13 +00:00 committed by GitHub
commit 3182c081d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
55 changed files with 6702 additions and 690 deletions

View file

@ -1421,19 +1421,20 @@ function PluginActionPanel({
) => Promise<{ message?: string; url?: string } | void> | { message?: string; url?: string } | void;
activePluginActionPaths?: Set<string>;
}) {
const t = useT();
const noticeByFolder = notices;
const runAction = onRunAction;
return (
<div className="plugin-action-panel" aria-label="Plugin next actions">
<div className="plugin-action-panel" aria-label={t("assistant.pluginAction.aria")}>
<div className="plugin-action-panel__head">
<span className="plugin-action-panel__icon" aria-hidden>
<Icon name="sparkles" size={15} />
</span>
<div>
<div className="plugin-action-panel__title">Plugin ready</div>
<div className="plugin-action-panel__title">{t("assistant.pluginAction.title")}</div>
<div className="plugin-action-panel__subtitle">
Send the next step to the agent so it can run the od CLI.
{t("assistant.pluginAction.subtitle")}
</div>
</div>
</div>
@ -1441,20 +1442,20 @@ function PluginActionPanel({
{folders.map((folder) => {
const actionBusy = activePluginActionPaths.has(folder.path);
return (
<div
key={folder.path}
className="plugin-action-card"
data-testid={`assistant-plugin-actions-${folder.path}`}
>
<div className="plugin-action-card__main">
<span className="plugin-action-card__folder-icon" aria-hidden>
<Icon name="folder" size={14} />
</span>
<div className="plugin-action-card__copy">
<code className="plugin-action-card__path">{folder.path}</code>
<span>{folder.fileCount} files ready for My plugins</span>
<div
key={folder.path}
className="plugin-action-card"
data-testid={`assistant-plugin-actions-${folder.path}`}
>
<div className="plugin-action-card__main">
<span className="plugin-action-card__folder-icon" aria-hidden>
<Icon name="folder" size={14} />
</span>
<div className="plugin-action-card__copy">
<code className="plugin-action-card__path">{folder.path}</code>
<span>{t("assistant.pluginAction.filesReady", { count: folder.fileCount })}</span>
</div>
</div>
</div>
<div className="plugin-action-card__actions">
<button
type="button"
@ -1468,7 +1469,9 @@ function PluginActionPanel({
size={13}
/>
<span>
{actionBusy && busyKey === `install:${folder.path}` ? "Sending..." : "Add to My plugins"}
{actionBusy && busyKey === `install:${folder.path}`
? t("assistant.pluginAction.sending")
: t("assistant.pluginAction.addToMyPlugins")}
</span>
</button>
<button
@ -1483,7 +1486,9 @@ function PluginActionPanel({
size={13}
/>
<span>
{actionBusy && busyKey === `publish:${folder.path}` ? "Sending..." : "Publish repo"}
{actionBusy && busyKey === `publish:${folder.path}`
? t("assistant.pluginAction.sending")
: t("assistant.pluginAction.publishRepo")}
</span>
</button>
<button
@ -1499,8 +1504,8 @@ function PluginActionPanel({
/>
<span>
{actionBusy && busyKey === `contribute:${folder.path}`
? "Sending..."
: "Open Design PR"}
? t("assistant.pluginAction.sending")
: t("assistant.pluginAction.openDesignPr")}
</span>
</button>
{onRequestOpenFile ? (
@ -1511,17 +1516,18 @@ function PluginActionPanel({
onClick={() => onRequestOpenFile(folder.manifestPath)}
>
<Icon name="file-code" size={13} />
<span>Open manifest</span>
<span>{t("assistant.pluginAction.openManifest")}</span>
</button>
) : null}
</div>
{noticeByFolder[folder.path] ? (
<div className="plugin-action-card__notice" role="status">
<ActionNoticeView notice={noticeByFolder[folder.path] ?? null} />
</div>
) : null}
</div>
)})}
{noticeByFolder[folder.path] ? (
<div className="plugin-action-card__notice" role="status">
<ActionNoticeView notice={noticeByFolder[folder.path] ?? null} />
</div>
) : null}
</div>
);
})}
</div>
</div>
);

View file

@ -2583,6 +2583,7 @@ function ToolsPluginsPanel({
onApply: (record: InstalledPluginRecord) => void | Promise<void>;
onShowDetails: (record: InstalledPluginRecord) => void;
}) {
const t = useT();
const [pendingId, setPendingId] = useState<string | null>(null);
const [source, setSource] = useState<'community' | 'mine'>('community');
const [query, setQuery] = useState('');
@ -2603,16 +2604,16 @@ function ToolsPluginsPanel({
return (
<>
<div className="composer-tools-filter">
<div className="composer-tools-segments" role="tablist" aria-label="Plugin source">
<div className="composer-tools-segments" role="tablist" aria-label={t('chat.tools.pluginSourceAria')}>
<button
type="button"
role="tab"
aria-selected={source === 'community'}
className={`composer-tools-segment${source === 'community' ? ' active' : ''}`}
onClick={() => setSource('community')}
title={`${communityPlugins.length} installed official plugins`}
title={t('chat.tools.officialTitle', { count: communityPlugins.length })}
>
Official
{t('chat.tools.official')}
</button>
<button
type="button"
@ -2620,30 +2621,39 @@ function ToolsPluginsPanel({
aria-selected={source === 'mine'}
className={`composer-tools-segment${source === 'mine' ? ' active' : ''}`}
onClick={() => setSource('mine')}
title={`${userPlugins.length} installed user plugins`}
title={t('chat.tools.myPluginsTitle', { count: userPlugins.length })}
>
My plugins
{t('chat.tools.myPlugins')}
</button>
</div>
<input
className="composer-tools-search"
value={query}
onChange={(e) => setQuery(e.currentTarget.value)}
placeholder="Search plugins…"
aria-label="Search plugins"
placeholder={t('chat.tools.searchPlugins')}
aria-label={t('pluginsHome.searchAria')}
/>
</div>
{visiblePlugins.length === 0 ? (
<div className="composer-tools-empty">
{plugins.length === 0 ? (
<>
No plugins installed yet. Browse Official or add your own with{' '}
{t('chat.tools.noPluginsInstalled')}{' '}
<code>od plugin install &lt;source&gt;</code>.
</>
) : query ? (
<>No {source === 'community' ? 'Official' : 'My plugins'} results for {query}.</>
<>
{t('chat.tools.noPluginResults', {
source: source === 'community' ? t('chat.tools.official') : t('chat.tools.myPlugins'),
query,
})}
</>
) : (
<>No {source === 'community' ? 'Official' : 'My plugins'} plugins available.</>
<>
{t('chat.tools.noPluginsAvailable', {
source: source === 'community' ? t('chat.tools.official') : t('chat.tools.myPlugins'),
})}
</>
)}
</div>
) : (
@ -2687,15 +2697,15 @@ function ToolsPluginsPanel({
)}
</span>
{pendingId === p.id ? (
<span className="composer-tools-row-pending">Applying</span>
<span className="composer-tools-row-pending">{t('chat.tools.applying')}</span>
) : null}
</button>
<button
type="button"
className="composer-tools-row-side"
onClick={() => onShowDetails(p)}
title={`View details for ${p.title}`}
aria-label={`View details for ${p.title}`}
title={t('chat.tools.viewDetails', { title: p.title })}
aria-label={t('chat.tools.viewDetails', { title: p.title })}
>
<Icon name="eye" size={12} />
</button>
@ -2718,6 +2728,7 @@ function ToolsMcpPanel({
onInsert: (serverId: string) => void;
onManage: () => void;
}) {
const t = useT();
const [query, setQuery] = useState('');
const visibleServers = useMemo(
() => servers.filter((s) => mcpServerMatchesQuery(s, query)),
@ -2735,19 +2746,19 @@ function ToolsMcpPanel({
className="composer-tools-search"
value={query}
onChange={(e) => setQuery(e.currentTarget.value)}
placeholder="Search MCP…"
aria-label="Search MCP servers and templates"
placeholder={t('chat.tools.searchMcp')}
aria-label={t('chat.tools.searchMcpAria')}
/>
</div>
{visibleServers.length === 0 ? (
<div className="composer-tools-empty">
{servers.length === 0
? 'No enabled MCP servers configured yet.'
: `No configured MCP results for “${query}”.`}
? t('chat.tools.noMcpServers')
: t('chat.tools.noMcpResults', { query })}
</div>
) : (
<div className="composer-tools-list">
<div className="composer-tools-section-label">Configured</div>
<div className="composer-tools-section-label">{t('chat.tools.configured')}</div>
{visibleServers.map((s) => (
<button
key={s.id}
@ -2759,7 +2770,7 @@ function ToolsMcpPanel({
// selectionStart isn't reset to 0 (#3195).
onMouseDown={(e) => e.preventDefault()}
onClick={() => onInsert(s.id)}
title={`Insert a hint that nudges the model to use ${s.label || s.id}`}
title={t('chat.tools.insertMcpHint', { name: s.label || s.id })}
>
<Icon name="link" size={12} />
<span className="composer-tools-row-body">
@ -2772,7 +2783,7 @@ function ToolsMcpPanel({
)}
{visibleTemplates.length > 0 ? (
<div className="composer-tools-list">
<div className="composer-tools-section-label">Templates</div>
<div className="composer-tools-section-label">{t('chat.tools.templates')}</div>
{visibleTemplates.map((tpl) => (
<button
key={tpl.id}
@ -2780,7 +2791,7 @@ function ToolsMcpPanel({
role="menuitem"
className="composer-tools-row"
onClick={onManage}
title={`Add ${tpl.label} from Settings`}
title={t('chat.tools.addMcpTemplate', { name: tpl.label })}
>
<Icon name="plus" size={12} />
<span className="composer-tools-row-body">
@ -2801,7 +2812,7 @@ function ToolsMcpPanel({
onClick={onManage}
>
<Icon name="settings" size={12} />
<span>Manage MCP servers</span>
<span>{t('chat.tools.manageMcp')}</span>
</button>
</>
);
@ -2817,6 +2828,7 @@ function ToolsSkillsPanel({
onPick: (skill: SkillSummary) => void | Promise<void>;
}) {
const { locale } = useI18n();
const t = useT();
const [query, setQuery] = useState('');
const [pendingId, setPendingId] = useState<string | null>(null);
const visibleSkills = useMemo(
@ -2830,13 +2842,13 @@ function ToolsSkillsPanel({
className="composer-tools-search"
value={query}
onChange={(e) => setQuery(e.currentTarget.value)}
placeholder="Search skills…"
aria-label="Search skills"
placeholder={t('chat.tools.searchSkills')}
aria-label={t('homeHero.mentionSkills')}
/>
</div>
{visibleSkills.length === 0 ? (
<div className="composer-tools-empty">
{skills.length === 0 ? 'No skills available yet.' : `No skills found for “${query}”.`}
{skills.length === 0 ? t('chat.tools.noSkills') : t('chat.tools.noSkillResults', { query })}
</div>
) : (
<div className="composer-tools-list">
@ -2872,7 +2884,7 @@ function ToolsSkillsPanel({
</span>
</span>
{pendingId === skill.id ? (
<span className="composer-tools-row-pending">Applying</span>
<span className="composer-tools-row-pending">{t('chat.tools.applying')}</span>
) : null}
</button>
);
@ -2957,7 +2969,7 @@ function mcpTemplateMatchesQuery(tpl: McpTemplate, query: string): boolean {
}
function pluginSourceLabel(plugin: InstalledPluginRecord, t: TranslateFn): string {
return plugin.sourceKind === 'bundled' ? t('chat.mentionPluginOfficial') : t('chat.mentionPluginMine');
return plugin.sourceKind === 'bundled' ? t('chat.tools.official') : t('chat.tools.myPlugins');
}
function ToolsImportPanel({
@ -3104,16 +3116,17 @@ function MentionPopover({
onPickMcp: (server: McpServerConfig) => void;
onPickConnector: (connector: ConnectorDetail) => void;
}) {
const { locale, t } = useI18n();
const { locale } = useI18n();
const t = useT();
const ref = useRef<HTMLDivElement | null>(null);
const [tab, setTab] = useState<MentionTab>('all');
const tabs: Array<{ id: MentionTab; label: string }> = [
{ id: 'all', label: t('chat.mentionTabAll') },
{ id: 'plugins', label: t('chat.mentionTabPlugins') },
{ id: 'skills', label: t('chat.mentionTabSkills') },
{ id: 'mcp', label: t('chat.mentionTabMcp') },
{ id: 'connectors', label: t('chat.mentionTabConnectors') },
{ id: 'files', label: t('chat.mentionTabFiles') },
{ id: 'all', label: t('chat.mention.all') },
{ id: 'plugins', label: t('chat.mention.plugins') },
{ id: 'skills', label: t('chat.mention.skills') },
{ id: 'mcp', label: t('chat.mention.mcp') },
{ id: 'connectors', label: t('chat.mention.connectors') },
{ id: 'files', label: t('chat.mention.files') },
];
const showPlugins = tab === 'all' || tab === 'plugins';
const showSkills = tab === 'all' || tab === 'skills';
@ -3131,7 +3144,7 @@ function MentionPopover({
}, [connectors, files, plugins, skills, mcpServers, tab]);
return (
<div className="mention-popover" data-testid="mention-popover">
<div className="mention-tabs" role="tablist" aria-label={t('chat.mentionTabsAria')}>
<div className="mention-tabs" role="tablist" aria-label={t('chat.mention.surfacesAria')}>
{tabs.map((item) => (
<button
key={item.id}
@ -3148,17 +3161,17 @@ function MentionPopover({
</div>
<div className="mention-results" ref={ref}>
{!hasVisibleResults ? (
<div className="mention-empty">
{query ? (
<>{t('chat.mentionNoResults', { query })}</>
) : (
<>{t('chat.mentionSearchPrompt')}</>
)}
</div>
<div className="mention-empty">
{query ? (
<>{t('chat.mention.noResults', { query })}</>
) : (
<>{t('chat.mention.emptyHint')}</>
)}
</div>
) : null}
{showPlugins && plugins.length > 0 ? (
<>
<div className="mention-section-label">{t('chat.mentionSectionPlugins')}</div>
<div className="mention-section-label">{t('chat.mention.plugins')}</div>
{plugins.map((p) => (
<button
key={`plugin-${p.id}`}
@ -3182,7 +3195,7 @@ function MentionPopover({
) : null}
{showSkills && skills.length > 0 ? (
<>
<div className="mention-section-label">{t('chat.mentionSectionSkills')}</div>
<div className="mention-section-label">{t('chat.mention.skills')}</div>
{skills.map((skill) => {
const active = skill.id === currentSkillId;
return (
@ -3201,7 +3214,7 @@ function MentionPopover({
{localizeSkillDescription(locale, skill) || skill.id}
</span>
</span>
<span className="mention-meta">{active ? t('chat.mentionActiveSkill') : skill.mode}</span>
<span className="mention-meta">{active ? t('chat.mention.active') : skill.mode}</span>
</button>
);
})}
@ -3209,7 +3222,7 @@ function MentionPopover({
) : null}
{showMcp && mcpServers.length > 0 ? (
<>
<div className="mention-section-label">{t('chat.mentionSectionMcp')}</div>
<div className="mention-section-label">{t('chat.mention.mcp')}</div>
{mcpServers.map((server) => (
<button
key={`mcp-${server.id}`}
@ -3217,7 +3230,7 @@ function MentionPopover({
type="button"
onMouseDown={(e) => e.preventDefault()}
onClick={() => onPickMcp(server)}
title={t('chat.mentionUseMcpTitle', { name: server.label || server.id })}
title={t('chat.mention.useMcp', { name: server.label || server.id })}
>
<Icon name="link" size={12} />
<span className="mention-item-body">
@ -3233,7 +3246,7 @@ function MentionPopover({
) : null}
{showConnectors && connectors.length > 0 ? (
<>
<div className="mention-section-label">{t('chat.mentionSectionConnectors')}</div>
<div className="mention-section-label">{t('chat.mention.connectors')}</div>
{connectors.map((connector) => (
<button
key={`connector-${connector.id}`}
@ -3241,7 +3254,7 @@ function MentionPopover({
type="button"
onMouseDown={(e) => e.preventDefault()}
onClick={() => onPickConnector(connector)}
title={t('chat.mentionUseConnectorTitle', { name: connector.name })}
title={t('chat.mention.useConnector', { name: connector.name })}
>
<Icon name="link" size={12} />
<span className="mention-item-body">
@ -3257,7 +3270,7 @@ function MentionPopover({
) : null}
{showFiles && files.length > 0 ? (
<>
<div className="mention-section-label">{t('chat.mentionSectionFiles')}</div>
<div className="mention-section-label">{t('chat.mention.files')}</div>
{files.map((f) => {
const key = f.path ?? f.name;
return (

View file

@ -1,7 +1,7 @@
import { Fragment, useEffect, useRef, useState, type MutableRefObject, type ReactNode } from 'react';
import { useAnalytics } from '../analytics/provider';
import { trackChatPanelClick } from '../analytics/events';
import { useT } from '../i18n';
import { useI18n, useT } from '../i18n';
import type { Dict } from '../i18n/types';
import { copyToClipboard } from '../lib/copy-to-clipboard';
import { projectRawUrl } from '../providers/registry';
@ -45,9 +45,8 @@ type TranslateFn = (key: keyof Dict, vars?: Record<string, string | number>) =>
// generic prototype trio. The default (prototype/deck/template/other/
// live-artifact) set stays i18n-translated via existing chat.example*
// keys so the user-facing copy keeps its localizations. The new media
// sets are inline English literals — they are technical agent prompts
// that work well across locales without translation, and going through
// i18n for each of them would balloon every Dict entry by 12+ keys.
// media sets carry Simplified Chinese mirrors inline so project-kind
// starter cards stay localized without ballooning the global Dict.
type StarterPrompt = {
icon: string;
title: string;
@ -190,18 +189,116 @@ const AUDIO_STARTERS: StarterPrompt[] = [
},
];
const IMAGE_STARTERS_ZH: StarterPrompt[] = [
{
icon: '◯',
title: '编辑肖像',
tag: '人像',
prompt:
'一张年轻创意总监的近景编辑肖像柔和自然光从高挑工作室窗户照入暖中性色调85mm f/1.8 浅景深,直视镜头,轻微胶片颗粒,自然妆感。',
},
{
icon: '▭',
title: '产品主视觉',
tag: '电商',
prompt:
'一张高级产品主视觉:单只哑光陶瓷咖啡杯置于暖奶油纸面背景上。左上方硬边轮廓光,柔和长阴影延伸到右下,杯口有细微蒸汽。正方形裁切,居中构图。',
},
{
icon: '◐',
title: '扁平插画',
tag: '插画',
prompt:
'一张雨窗旁舒适阅读角的扁平矢量插画:几何形状、克制的 5 色调色板、1.5px 细线点缀,不使用渐变和纹理,只保留柔和投影。',
},
];
const VIDEO_SEEDANCE_STARTERS_ZH: StarterPrompt[] = [
{
icon: '◉',
title: '产品揭幕',
tag: '电影感',
prompt:
'一支 5 秒产品揭幕短片:一瓶极简高端护肤品放在干净的奶油色石面上,镜头左侧柔和侧光,缓慢推近,焦点从瓶盖轻微转移到标签,动作克制,不要文字叠加和人物入镜。',
},
{
icon: '▣',
title: '灯笼近景',
tag: '氛围',
prompt:
'一支 6 秒电影感近景:一位年轻女性在金色时刻的薄雾松林里手持发光纸灯笼。焦点落在眼睛上,镜头轻柔推近,暖色光束中漂浮细微粒子,无对白。',
},
{
icon: '⌘',
title: '霓虹街头漂移',
tag: '动作',
prompt:
'一支 5 秒夜间街头赛车跟拍:霓虹照亮的赛博朋克香港巷道里,低机位跟随一辆哑黑跑车高速过弯漂移,雨后柏油路反射灯光,画面不要屏幕文字。',
},
];
const VIDEO_HYPERFRAMES_STARTERS_ZH: StarterPrompt[] = [
{
icon: '◉',
title: '放大镜揭示',
tag: 'HTML 画布',
prompt:
'制作一段 5 秒 HyperFrames 作品:干净画布上只有一行加粗展示文字。让圆形放大镜从左到右掠过文字,用轻微玻璃折射扭曲下方字形,并通过 html-in-canvas 捕获文字 DOM。',
},
{
icon: '▦',
title: 'CRT 终端场景',
tag: '复古特效',
prompt:
'制作一个 CRT 屏幕构图:深色画布,等宽终端文字依次输入命令,并在实时 DOM 上叠加轻微凸面曲率、扫描线、色差和柔和荧光效果。',
},
{
icon: '◈',
title: '故障分解',
tag: '故障风',
prompt:
'制作一段 6 秒构图深色画布上展示主标题和一句副标题随后进入强烈数字故障分解RGB 通道分离、横向位移带、短暂掉帧,最后恢复干净画面。',
},
];
const AUDIO_STARTERS_ZH: StarterPrompt[] = [
{
icon: '♪',
title: '品牌旁白',
tag: '语音',
prompt:
'一段 30 秒产品发布视频旁白:温暖、有信心但像日常交谈,中速节奏,在品牌名后稍作停顿。中文普通话,清晰自然。',
},
{
icon: '♫',
title: '新手引导旁白',
tag: '语音',
prompt:
'一段 20 秒移动 App 首次启动页旁白:友好、让人安心、带微笑感,语速慢到显得贴心但不生硬。',
},
{
icon: '♬',
title: '故事开篇朗读',
tag: '语音',
prompt:
'一段 45 秒电影感开篇朗读:低沉、克制,每句之间有呼吸,近距离收音质感稍亲密,适合悬疑或都市故事开场。',
},
];
function pickStarters(
metadata: ProjectMetadata | undefined,
t: TranslateFn,
locale: ReturnType<typeof useI18n>['locale'],
): StarterPrompt[] {
const kind = metadata?.kind;
if (kind === 'image') return IMAGE_STARTERS;
if (kind === 'image') return locale === 'zh-CN' ? IMAGE_STARTERS_ZH : IMAGE_STARTERS;
if (kind === 'video') {
return metadata?.videoModel === 'hyperframes-html'
? VIDEO_HYPERFRAMES_STARTERS
: VIDEO_SEEDANCE_STARTERS;
if (metadata?.videoModel === 'hyperframes-html') {
return locale === 'zh-CN' ? VIDEO_HYPERFRAMES_STARTERS_ZH : VIDEO_HYPERFRAMES_STARTERS;
}
return locale === 'zh-CN' ? VIDEO_SEEDANCE_STARTERS_ZH : VIDEO_SEEDANCE_STARTERS;
}
if (kind === 'audio') return AUDIO_STARTERS;
if (kind === 'audio') return locale === 'zh-CN' ? AUDIO_STARTERS_ZH : AUDIO_STARTERS;
return DEFAULT_STARTER_KEYS.map((entry) => ({
icon: entry.icon,
title: t(entry.titleKey),
@ -406,8 +503,8 @@ export function ChatPane({
onChangeByokImageModel,
composerFooterAccessory,
}: Props) {
const t = useT();
const analytics = useAnalytics();
const { locale, t } = useI18n();
const logRef = useRef<HTMLDivElement | null>(null);
const historyWrapRef = useRef<HTMLDivElement | null>(null);
const composerRef = useRef<ChatComposerHandle | null>(null);
@ -1054,7 +1151,7 @@ export function ChatPane({
</span>
</div>
<div className="chat-examples" role="list">
{pickStarters(projectMetadata, t).map((ex, i) => (
{pickStarters(projectMetadata, t, locale).map((ex, i) => (
<button
key={`${ex.title}-${i}`}
type="button"

View file

@ -1294,7 +1294,7 @@ export function DesignFilesPanel({
<span className="df-row-name-wrap">
<span className="df-row-name">{folder.path}</span>
<span className="df-row-sub">
{folder.fileCount} files · ready to add to My plugins
{t('designFiles.pluginFilesReady', { count: folder.fileCount })}
</span>
</span>
</button>
@ -1310,7 +1310,9 @@ export function DesignFilesPanel({
void handlePluginFolderAgentAction(folder.path, 'install')
}
>
{installingFolder === folder.path ? 'Sending…' : 'Add to My plugins'}
{installingFolder === folder.path
? t('designFiles.pluginSending')
: t('designFiles.addToMyPlugins')}
</button>
<button
type="button"
@ -1321,7 +1323,9 @@ export function DesignFilesPanel({
void handlePluginFolderAgentAction(folder.path, 'publish')
}
>
{sharingFolder === `publish:${folder.path}` ? 'Sending…' : 'Publish repo'}
{sharingFolder === `publish:${folder.path}`
? t('designFiles.pluginSending')
: t('designFiles.publishRepo')}
</button>
<button
type="button"
@ -1332,7 +1336,9 @@ export function DesignFilesPanel({
void handlePluginFolderAgentAction(folder.path, 'contribute')
}
>
{sharingFolder === `contribute:${folder.path}` ? 'Sending…' : 'Open Design PR'}
{sharingFolder === `contribute:${folder.path}`
? t('designFiles.pluginSending')
: t('designFiles.openDesignPr')}
</button>
</div>
) : null}

View file

@ -31,6 +31,7 @@ import {
saveTabs,
} from '../state/projects';
import { appendErrorStatusEvent } from '../runtime/chat-events';
import { useT } from '../i18n';
import {
buildDesignSystemPackageAuditRepairPrompt,
summarizeDesignSystemPackageAudit,
@ -297,6 +298,7 @@ export function DesignSystemCreationFlow({
onBeforeGenerate,
onGenerateSettled,
}: CreationProps) {
const t = useT();
const [step, setStep] = useState<SetupStep>('setup');
const [state, setState] = useState<SetupState>(EMPTY_SETUP);
const [error, setError] = useState<string | null>(null);
@ -311,6 +313,7 @@ export function DesignSystemCreationFlow({
const [githubAuthorizationUrl, setGithubAuthorizationUrl] = useState<string | null>(null);
const githubConnectorRefreshId = useRef(0);
const githubConnectorRequestInFlight = useRef(false);
const tRef = useRef(t);
const embedded = chrome === 'embedded';
// DS create page_view (v2 doc). Only fires for the standalone
@ -358,6 +361,10 @@ export function DesignSystemCreationFlow({
});
}
useEffect(() => {
tRef.current = t;
}, [t]);
const refreshGithubConnector = useCallback(async () => {
if (!composioConfigured) {
githubConnectorRefreshId.current += 1;
@ -387,13 +394,13 @@ export function DesignSystemCreationFlow({
}
if (timedOut) {
setGithubConnectorError(
'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
tRef.current('ds.githubStatusCheckTimeout'),
);
}
} catch (err) {
if (githubConnectorRefreshId.current !== refreshId) return;
setGithubConnector(null);
setGithubConnectorError(err instanceof Error ? err.message : 'Could not check the GitHub connector.');
setGithubConnectorError(err instanceof Error ? err.message : tRef.current('ds.githubStatusCheckError'));
} finally {
if (githubConnectorRefreshId.current === refreshId) {
githubConnectorRequestInFlight.current = false;
@ -444,7 +451,7 @@ export function DesignSystemCreationFlow({
setGithubAuthorizationUrl(null);
}
} catch (err) {
setGithubConnectorError(err instanceof Error ? err.message : 'Could not start GitHub authorization.');
setGithubConnectorError(err instanceof Error ? err.message : t('ds.githubAuthorizationStartError'));
} finally {
setGithubConnectorAction(null);
}
@ -460,7 +467,7 @@ export function DesignSystemCreationFlow({
setGithubAuthorizationPending(false);
setGithubAuthorizationUrl(null);
} catch (err) {
setGithubConnectorError(err instanceof Error ? err.message : 'Could not disconnect GitHub.');
setGithubConnectorError(err instanceof Error ? err.message : t('ds.githubDisconnectError'));
} finally {
setGithubConnectorAction(null);
}
@ -592,7 +599,7 @@ export function DesignSystemCreationFlow({
provenance: buildProvenance(state),
});
if (!created) {
setError('Could not generate this design system.');
setError(t('ds.setupGenerateError'));
setStep('setup');
emitCreateResult('failed', undefined, 'DS_DRAFT_CREATE_FAILED', undefined);
onGenerateSettled?.(snapshot, {
@ -603,7 +610,7 @@ export function DesignSystemCreationFlow({
}
const workspace = await ensureDesignSystemWorkspace(created.id);
if (!workspace) {
setError('Could not open the design system workspace.');
setError(t('ds.setupWorkspaceError'));
setStep('setup');
emitCreateResult('failed', created.id, 'DS_WORKSPACE_OPEN_FAILED', undefined);
onGenerateSettled?.(snapshot, {
@ -632,7 +639,7 @@ export function DesignSystemCreationFlow({
});
});
} catch (err) {
setError(err instanceof Error ? err.message : 'Could not prepare the design system project.');
setError(err instanceof Error ? err.message : t('ds.setupPrepareError'));
setStep('setup');
const errorCode = err instanceof Error
? `DS_GENERATE_THREW:${err.message.slice(0, 80)}`
@ -648,12 +655,12 @@ export function DesignSystemCreationFlow({
return (
<div className="ds-setup-shell ds-setup-shell--center">
<div className="ds-setup-center-card">
<h1>It will take about 5 minutes to generate your design system.</h1>
<p>You can step away. Keep the tab open in the background.</p>
<h1>{t('ds.setupConfirmTitle')}</h1>
<p>{t('ds.setupConfirmBody')}</p>
<div className="ds-setup-actions">
<button type="button" className="ghost" onClick={() => setStep('setup')}>
<Icon name="arrow-left" />
Back
{t('ds.setupBack')}
</button>
<button
type="button"
@ -662,7 +669,7 @@ export function DesignSystemCreationFlow({
onClick={() => void generate()}
>
<Icon name="sparkles" />
{generationStarting ? 'Opening project...' : 'Generate'}
{generationStarting ? t('ds.setupOpeningProject') : t('ds.setupGenerate')}
</button>
</div>
</div>
@ -689,7 +696,7 @@ export function DesignSystemCreationFlow({
<header className="ds-setup-topbar">
<button type="button" className="ghost" onClick={onBack}>
<Icon name="arrow-left" />
Back
{t('ds.setupBack')}
</button>
<span className="ds-setup-mark">
<Icon name="blocks" />
@ -700,38 +707,38 @@ export function DesignSystemCreationFlow({
disabled={!state.company.trim()}
onClick={() => {
if (!state.company.trim()) {
setError('Tell Open Design about the company or design system first.');
setError(t('ds.setupRequiredError'));
return;
}
setStep('confirm');
}}
>
Continue to generation
{t('ds.setupContinue')}
<Icon name="chevron-right" />
</button>
</header>
)}
<main className="ds-setup-form">
<h1>Generate from your material</h1>
<p>Start with a short description, then add any source files you already have.</p>
<h1>{t('ds.setupTitle')}</h1>
<p>{t('ds.setupBody')}</p>
<label className="ds-setup-field">
<span>Describe your brand or product</span>
<span>{t('ds.setupCompanyLabel')}</span>
<textarea
rows={4}
value={state.company}
onChange={(event) => setState((curr) => ({ ...curr, company: event.target.value }))}
placeholder="e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website"
placeholder={t('ds.setupCompanyPlaceholder')}
/>
</label>
<section className="ds-resource-section">
<h2>Add source material <span>(optional)</span></h2>
<p>Use anything that shows your current style.</p>
<h2>{t('ds.setupExamplesTitle')} <span>{t('ds.setupExamplesOptional')}</span></h2>
<p>{t('ds.setupExamplesBody')}</p>
<div className="ds-resource-card">
<div className="ds-resource-row">
<strong>GitHub repo</strong>
<strong>{t('ds.setupGithubLabel')}</strong>
<div className="ds-resource-inline">
<input
value={state.githubUrl}
@ -744,18 +751,18 @@ export function DesignSystemCreationFlow({
disabled={!state.githubUrl.trim()}
onClick={handleAddGithubUrl}
>
Add
{t('ds.setupGithubAdd')}
</button>
</div>
{state.githubUrls.length > 0 ? (
<div className="ds-github-url-list" aria-label="Added GitHub repositories">
<div className="ds-github-url-list" aria-label={t('ds.setupAddedGithubReposAria')}>
{state.githubUrls.map((url) => (
<span key={url}>
<Icon name="github" />
{githubRepoLabel(url)}
<button
type="button"
aria-label={`Remove ${githubRepoLabel(url)}`}
aria-label={t('ds.setupRemoveGithubRepo', { repo: githubRepoLabel(url) })}
onClick={() => handleRemoveGithubUrl(url)}
>
x
@ -779,9 +786,9 @@ export function DesignSystemCreationFlow({
/>
</div>
<DropZone
label="Link local code"
helper="Use a folder or selected files from this computer."
prompt="Drag a folder here or browse"
label={t('ds.setupLinkLocalLabel')}
helper={t('ds.setupLinkLocalHelper')}
prompt={t('ds.setupLinkLocalPrompt')}
names={localCodeSourceLabels(state)}
directory
onBrowseFolder={() => void handlePickCodeFolder()}
@ -800,9 +807,9 @@ export function DesignSystemCreationFlow({
}}
/>
<DropZone
label="Upload .fig"
helper="Parsed locally; only a summary is added."
prompt="Drop .fig here or browse"
label={t('ds.setupUploadFigLabel')}
helper={t('ds.setupUploadFigHelper')}
prompt={t('ds.setupUploadFigPrompt')}
accept=".fig"
names={state.figFiles}
onError={setError}
@ -819,8 +826,8 @@ export function DesignSystemCreationFlow({
}}
/>
<DropZone
label="Add assets"
prompt="Drag files here or browse"
label={t('ds.setupAssetsLabel')}
prompt={t('ds.setupAssetsPrompt')}
names={state.assetFiles}
onRemoveName={handleRemoveAssetFile}
onError={setError}
@ -841,13 +848,13 @@ export function DesignSystemCreationFlow({
{embedded ? null : (
<label className="ds-setup-field">
<span>Notes</span>
<textarea
rows={4}
value={state.notes}
onChange={(event) => setState((curr) => ({ ...curr, notes: event.target.value }))}
placeholder="e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional..."
/>
<span>{t('ds.setupNotesLabel')}</span>
<textarea
rows={4}
value={state.notes}
onChange={(event) => setState((curr) => ({ ...curr, notes: event.target.value }))}
placeholder={t('ds.setupNotesPlaceholder')}
/>
</label>
)}
{error ? <div className="ds-editor-error">{error}</div> : null}
@ -855,7 +862,7 @@ export function DesignSystemCreationFlow({
<div className="ds-setup-actions ds-setup-actions--embedded">
<button type="button" className="ghost" onClick={onBack}>
<Icon name="arrow-left" />
Back
{t('ds.setupBack')}
</button>
<button
type="button"
@ -863,13 +870,13 @@ export function DesignSystemCreationFlow({
disabled={!state.company.trim()}
onClick={() => {
if (!state.company.trim()) {
setError('Tell Open Design about the company or design system first.');
setError(t('ds.setupRequiredError'));
return;
}
setStep('confirm');
}}
>
Generate
{t('ds.setupContinue')}
<Icon name="chevron-right" />
</button>
</div>
@ -2584,6 +2591,7 @@ function DropZone({
onProcessingStart,
onFiles,
}: DropZoneProps) {
const t = useT();
const inputRef = useRef<HTMLInputElement | null>(null);
const fileDialogPendingRef = useRef(false);
const fileDialogCanShowLoadingRef = useRef(false);
@ -2735,16 +2743,16 @@ function DropZone({
</label>
{onBrowseFolder ? (
<button type="button" className="ghost" onClick={onBrowseFolder}>
Browse folder
{t('ds.dropZoneBrowseFolder')}
</button>
) : null}
</div>
{names.length > 0 && onRemoveName ? (
<div className="ds-local-code-list" aria-label={`${label} selections`}>
<div className="ds-local-code-list" aria-label={t('ds.dropZoneSelectionsAria', { label })}>
{names.map((name) => (
<span key={name}>
{name}
<button type="button" aria-label={`Remove ${name}`} onClick={() => onRemoveName(name)}>
<button type="button" aria-label={t('ds.dropZoneRemoveSelection', { name })} onClick={() => onRemoveName(name)}>
x
</button>
</span>
@ -2884,62 +2892,63 @@ function GitHubRepositoryAccessPanel({
onOpenAuthorization: () => void;
onDisconnect: () => void;
}) {
const t = useT();
const [methodsExpanded, setMethodsExpanded] = useState(false);
const connected = isGithubConnectorConnected(connector);
const account = getDisplayableGithubAccountLabel(connector);
const busy = action !== null;
let composioBadge = 'Optional';
let composioBadge = t('ds.githubAccessBadgeOptional');
let composioTone: AccessBadgeTone = 'muted';
let composioDescription = 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.';
let composioDescription = t('ds.githubAccessDescOptional');
let composioIcon: IconName = 'settings';
if (!composioConfigured) {
composioBadge = 'Not configured';
composioDescription = 'Add a Composio API key only if this project needs connector-backed GitHub tools.';
composioBadge = t('ds.githubAccessBadgeNotConfigured');
composioDescription = t('ds.githubAccessDescNotConfigured');
} else if (connected) {
composioBadge = 'Connected';
composioBadge = t('ds.githubAccessBadgeConnected');
composioTone = 'success';
composioIcon = 'github';
composioDescription = account
? `Composio GitHub connector connected as ${account}; it is available as fallback when this device cannot read the repository.`
: 'Composio GitHub connector is available as fallback when this device cannot read the repository.';
? t('ds.githubAccessDescConnectedAs', { account })
: t('ds.githubAccessDescConnected');
} else if (authorizationPending) {
composioBadge = 'Pending';
composioBadge = t('ds.githubAccessBadgePending');
composioTone = 'warning';
composioIcon = 'external-link';
composioDescription = 'Finish the Composio authorization window; local GitHub intake remains available.';
composioDescription = t('ds.githubAccessDescPending');
} else if (loading) {
composioBadge = 'Checking';
composioBadge = t('ds.githubAccessBadgeChecking');
composioTone = 'loading';
composioIcon = 'spinner';
composioDescription = 'Checking connector status in the background; URL intake is not blocked.';
composioDescription = t('ds.githubAccessDescChecking');
} else if (error) {
composioBadge = 'Needs attention';
composioBadge = t('ds.githubAccessBadgeNeedsAttention');
composioTone = 'warning';
} else if (connector?.status === 'error') {
composioBadge = 'Needs attention';
composioBadge = t('ds.githubAccessBadgeNeedsAttention');
composioTone = 'danger';
composioDescription = 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.';
composioDescription = t('ds.githubAccessDescReconnect');
}
const composioAction = !composioConfigured ? (
<button type="button" className="ghost" onClick={onOpenConnectorsTab}>
Configure Composio
{t('ds.githubAccessConfigureComposio')}
</button>
) : connected || authorizationPending ? (
<>
{authorizationPending && authorizationUrl ? (
<button type="button" className="ghost" disabled={busy} onClick={onOpenAuthorization}>
Open authorization
{t('ds.githubAccessOpenAuthorization')}
</button>
) : null}
<button type="button" className="ghost" disabled={busy} onClick={onDisconnect}>
{action === 'disconnect' ? 'Disconnecting...' : 'Disconnect'}
{action === 'disconnect' ? t('ds.githubAccessDisconnecting') : t('ds.githubAccessDisconnect')}
</button>
</>
) : (
<button type="button" className="ghost" disabled={busy} onClick={onConnect}>
{action === 'connect' ? 'Connecting...' : 'Connect via Composio'}
{action === 'connect' ? t('ds.githubAccessConnecting') : t('ds.githubAccessConnectComposio')}
</button>
);
@ -2947,23 +2956,23 @@ function GitHubRepositoryAccessPanel({
{
id: 'local',
icon: 'github',
title: 'This device',
badge: 'Automatic',
title: t('ds.githubAccessLocalTitle'),
badge: t('ds.githubAccessLocalBadge'),
tone: 'success',
description: 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
description: t('ds.githubAccessLocalDesc'),
},
{
id: 'native-oauth',
icon: 'link',
title: 'Open Design account',
badge: 'Coming soon',
title: t('ds.githubAccessNativeTitle'),
badge: t('ds.githubAccessNativeBadge'),
tone: 'muted',
description: 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
description: t('ds.githubAccessNativeDesc'),
},
{
id: 'composio',
icon: composioIcon,
title: 'Connector platform',
title: t('ds.githubAccessConnectorTitle'),
badge: composioBadge,
tone: composioTone,
description: composioDescription,
@ -2981,8 +2990,8 @@ function GitHubRepositoryAccessPanel({
>
<div className="ds-github-access-header">
<span>
<strong>Repository access: Auto</strong>
<p>Paste a GitHub URL. Open Design will use the first working access method.</p>
<strong>{t('ds.githubAccessHeaderTitle')}</strong>
<p>{t('ds.githubAccessHeaderBody')}</p>
</span>
<button
type="button"
@ -2992,7 +3001,7 @@ function GitHubRepositoryAccessPanel({
onClick={() => setMethodsExpanded((current) => !current)}
>
<Icon name={methodsExpanded ? 'chevron-down' : 'chevron-right'} />
{methodsExpanded ? 'Hide access methods' : 'Show access methods'}
{methodsExpanded ? t('ds.githubAccessHideMethods') : t('ds.githubAccessShowMethods')}
</button>
</div>
<div
@ -3002,7 +3011,7 @@ function GitHubRepositoryAccessPanel({
aria-hidden={!methodsExpanded}
>
<div className="accordion-collapsible-inner">
<div className="ds-github-access-methods" aria-label="GitHub repository access methods">
<div className="ds-github-access-methods" aria-label={t('ds.githubAccessMethodsAria')}>
{methods.map((method) => (
<div key={method.id} className="ds-github-access-method">
<Icon name={method.icon} />

View file

@ -88,11 +88,15 @@ function mapStatusToTracking(
}
}
function formatShortDate(value: number | string | undefined): string {
if (!value) return 'just now';
function formatShortDate(
value: number | string | undefined,
locale?: string,
emptyLabel = 'just now',
): string {
if (!value) return emptyLabel;
const time = typeof value === 'number' ? value : Date.parse(value);
if (!Number.isFinite(time)) return String(value);
return new Intl.DateTimeFormat(undefined, {
return new Intl.DateTimeFormat(locale, {
month: 'short',
day: 'numeric',
hour: '2-digit',
@ -111,6 +115,13 @@ export function DesignSystemsTab({
templates = [],
}: Props) {
const { locale, t } = useI18n();
const isZhCN = locale === 'zh-CN';
const primaryDesignSystemLabel = isZhCN ? t('ds.userSystemsEyebrow') : 'Design system';
const primaryTemplateLabel = isZhCN ? t('ds.templatesTitle') : 'Template';
const userSystemsLabel = isZhCN ? t('ds.userSystemsTitle') : 'Your systems';
const officialPresetsLabel = isZhCN ? t('ds.libraryTitle') : 'Official presets';
const enterpriseLabel = isZhCN ? '企业' : 'Enterprise';
const templateMineLabel = isZhCN ? t('ds.templatesTitle') : 'Your templates';
const analytics = useAnalytics();
const designSystemsPageViewFiredRef = useRef(false);
useEffect(() => {
@ -161,6 +172,10 @@ export function DesignSystemsTab({
if (userFilter === 'all') return editable;
return editable.filter((system) => (system.status ?? 'draft') === userFilter);
}, [systems, userFilter]);
const showOfficialLibrary =
primaryCollection === 'design-system' &&
(designSystemCollection === 'official' ||
(designSystemCollection === 'mine' && userSystems.length === 0 && librarySystems.length > 0));
// Total systems per surface, ignoring every active filter. Drives the
// "this surface is now empty" fallback below — that guard must react to
@ -298,7 +313,7 @@ export function DesignSystemsTab({
}
async function deleteSystem(system: DesignSystemSummary) {
const ok = window.confirm(`Delete "${system.title}"? This removes the draft design system from this device.`);
const ok = window.confirm(t('ds.deleteConfirm', { title: system.title }));
if (!ok) {
trackDesignSystemStatusResult(analytics.track, {
page_name: 'design_systems',
@ -386,7 +401,7 @@ export function DesignSystemsTab({
className={primaryCollection === 'design-system' ? 'active' : ''}
onClick={() => setPrimaryCollection('design-system')}
>
Design system
{primaryDesignSystemLabel}
</button>
<button
type="button"
@ -395,7 +410,7 @@ export function DesignSystemsTab({
className={primaryCollection === 'template' ? 'active' : ''}
onClick={() => setPrimaryCollection('template')}
>
Template
{primaryTemplateLabel}
</button>
</div>
</div>
@ -410,7 +425,7 @@ export function DesignSystemsTab({
className={designSystemCollection === 'mine' ? 'active' : ''}
onClick={() => setDesignSystemCollection('mine')}
>
Your systems
{userSystemsLabel}
</button>
<button
type="button"
@ -419,7 +434,7 @@ export function DesignSystemsTab({
className={designSystemCollection === 'official' ? 'active' : ''}
onClick={() => setDesignSystemCollection('official')}
>
Official presets
{officialPresetsLabel}
</button>
<button
type="button"
@ -428,7 +443,7 @@ export function DesignSystemsTab({
className={designSystemCollection === 'enterprise' ? 'active' : ''}
onClick={() => setDesignSystemCollection('enterprise')}
>
Enterprise
{enterpriseLabel}
</button>
</div>
</div>
@ -442,7 +457,7 @@ export function DesignSystemsTab({
className={templateCollection === 'mine' ? 'active' : ''}
onClick={() => setTemplateCollection('mine')}
>
Your templates
{templateMineLabel}
</button>
<button
type="button"
@ -457,37 +472,41 @@ export function DesignSystemsTab({
</div>
)}
{primaryCollection === 'design-system' ? (
<p className="ds-private-note">{t('ds.privateNote')}</p>
) : null}
{primaryCollection === 'design-system' && designSystemCollection === 'mine' ? (
<section className="ds-settings-card" aria-label="Your design systems">
<section className="ds-settings-card" aria-label={t('ds.userSystemsAria')}>
<div className="ds-settings-card__head">
<div>
<span className="ds-manager-eyebrow">Design Systems</span>
<h2>Your systems</h2>
<span className="ds-manager-eyebrow">{t('ds.userSystemsEyebrow')}</span>
<h2>{t('ds.userSystemsTitle')}</h2>
</div>
<select
aria-label="Filter design systems"
aria-label={t('ds.userFilterAria')}
value={userFilter}
onChange={(event) => setUserFilter(event.target.value as UserListFilter)}
>
<option value="all">All</option>
<option value="published">Published</option>
<option value="draft">Draft</option>
<option value="all">{t('ds.categoryAll')}</option>
<option value="published">{t('ds.statusPublished')}</option>
<option value="draft">{t('ds.statusDraft')}</option>
</select>
</div>
{onCreate ? (
<button type="button" className="ds-create-row" onClick={onCreate}>
<span>
<strong>Create new design system</strong>
<small>Teach Open Design your brand, product, code, assets, and design references.</small>
<strong>{t('ds.createTitle')}</strong>
<small>{t('ds.createDescription')}</small>
</span>
<span className="ds-create-row__action">Create</span>
<span className="ds-create-row__action">{t('ds.createAction')}</span>
</button>
) : null}
{userSystems.length === 0 ? (
<div className="ds-user-empty">
No design systems yet. Create one from real product context, review the draft, then publish it for future projects.
{t('ds.userEmpty')}
</div>
) : (
<div className="ds-user-list">
@ -505,10 +524,12 @@ export function DesignSystemsTab({
>
<span className="ds-user-row__title">
<span>{system.title}</span>
{selected ? <span className="ds-card-badge">Default</span> : null}
{selected ? <span className="ds-card-badge">{t('ds.badgeDefaultInline')}</span> : null}
</span>
<span className="ds-user-row__meta">
You · updated {formatShortDate(system.updatedAt)}
{t('ds.userUpdatedMeta', {
date: formatShortDate(system.updatedAt, locale, t('ds.dateJustNow')),
})}
</span>
</button>
<div className="ds-user-row__actions">
@ -519,7 +540,7 @@ export function DesignSystemsTab({
onClick={() => onOpenSystem(system.id)}
disabled={busy}
>
Edit
{t('ds.actionEdit')}
</button>
) : null}
{!selected && canUseInProjects ? (
@ -529,7 +550,7 @@ export function DesignSystemsTab({
onClick={() => handleMakeDefaultClick(system)}
disabled={busy}
>
Make default
{t('ds.actionMakeDefault')}
</button>
) : null}
<button
@ -539,14 +560,14 @@ export function DesignSystemsTab({
onClick={() => void togglePublished(system)}
disabled={busy}
>
<span>{status === 'published' ? 'Published' : 'Draft'}</span>
<span>{status === 'published' ? t('ds.statusPublished') : t('ds.statusDraft')}</span>
<i aria-hidden />
</button>
{onOpenSystem ? (
<button
type="button"
className="icon-btn"
aria-label={`Open ${system.title}`}
aria-label={t('ds.actionOpenNamed', { title: system.title })}
onClick={() => onOpenSystem(system.id)}
>
<Icon name="external-link" />
@ -555,7 +576,7 @@ export function DesignSystemsTab({
<button
type="button"
className="icon-btn danger"
aria-label={`Delete ${system.title}`}
aria-label={t('ds.actionDeleteNamed', { title: system.title })}
onClick={() => void deleteSystem(system)}
disabled={busy}
>
@ -570,12 +591,12 @@ export function DesignSystemsTab({
</section>
) : null}
{primaryCollection === 'design-system' && designSystemCollection === 'official' ? (
<section className="ds-settings-card" aria-label="Official design system presets">
{showOfficialLibrary ? (
<section className="ds-settings-card" aria-label={t('ds.libraryAria')}>
<div className="ds-settings-card__head">
<div>
<span className="ds-manager-eyebrow">Library</span>
<h2>Official presets</h2>
<span className="ds-manager-eyebrow">{t('ds.libraryEyebrow')}</span>
<h2>{t('ds.libraryTitle')}</h2>
</div>
</div>
<div className="tab-panel-toolbar ds-manager-toolbar">
@ -846,7 +867,7 @@ function DesignSystemCard({
>
{thumbHtml ? (
<iframe
title={`${system.title} preview`}
title={t('ds.previewFrameTitle', { title: system.title })}
sandbox="allow-scripts"
srcDoc={buildSrcdoc(thumbHtml)}
tabIndex={-1}

View file

@ -31,6 +31,14 @@ import type {
InstalledPluginRecord,
McpServerConfig,
} from '@open-design/contracts';
import { useI18n, useT } from '../i18n';
import {
localizePluginBriefTemplate,
localizePluginDisplayValue,
localizePluginInputLabel,
localizePluginInputValues,
localizePluginPlaceholder,
} from '../i18n/plugin-content';
import type { SkillSummary } from '../types';
import { isImeComposing } from '../utils/imeComposing';
import { Icon, type IconName } from './Icon';
@ -47,7 +55,6 @@ import {
inlineMentionToken,
type InlineMentionEntity,
} from '../utils/inlineMentions';
import { useI18n, useT } from '../i18n';
import type { Locale } from '../i18n/types';
import {
localizeSkillDescription,
@ -250,7 +257,7 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
{ id: 'plugins', label: t('entry.navPlugins'), count: pluginMatches.length },
{ id: 'skills', label: t('homeHero.skills'), count: skillMatches.length },
{ id: 'mcp', label: 'MCP', count: mcpMatches.length },
{ id: 'connectors', label: 'Connectors', count: connectorMatches.length },
{ id: 'connectors', label: t('entry.tabConnectors'), count: connectorMatches.length },
];
const showPlugins = mentionTab === 'all' || mentionTab === 'plugins';
const showSkills = mentionTab === 'all' || mentionTab === 'skills';
@ -266,7 +273,7 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
icon: 'sparkles',
title: plugin.title,
description: plugin.manifest?.description ?? plugin.id,
meta: pendingPluginId === plugin.id ? t('homeHero.applying') : getPluginSourceLabel(plugin),
meta: pendingPluginId === plugin.id ? t('homeHero.applying') : getPluginSourceLabel(plugin, t),
pluginRecord: plugin,
disabled: pendingPluginId !== null,
onPick: () => pickPlugin(plugin),
@ -304,7 +311,7 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
showConnectors
? {
id: 'connectors',
label: 'Connectors',
label: t('entry.tabConnectors'),
options: connectorMatches.map((connector) => ({
id: `connector-${connector.id}`,
icon: 'link',
@ -333,6 +340,7 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
connectorOptions,
selectedPluginContexts,
skillOptions,
t,
}),
[
activePluginRecord,
@ -343,6 +351,7 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
connectorOptions,
selectedPluginContexts,
skillOptions,
t,
],
);
const pluginByMentionId = useMemo(() => {
@ -352,14 +361,22 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
if (activePluginRecord) map.set(activePluginRecord.id, activePluginRecord);
return map;
}, [activePluginRecord, pluginOptions, selectedPluginContexts]);
const localizedPluginInputTemplate = useMemo(
() => localizePluginBriefTemplate(locale, pluginInputTemplate),
[locale, pluginInputTemplate],
);
const localizedPluginInputValues = useMemo(
() => localizePluginInputValues(locale, pluginInputValues, pluginInputFields),
[locale, pluginInputFields, pluginInputValues],
);
const promptOverlayParts = useMemo(
() => buildPromptOverlayParts(
pluginInputTemplate,
pluginInputValues,
localizedPluginInputTemplate,
localizedPluginInputValues,
prompt,
promptMentionEntities,
),
[pluginInputTemplate, pluginInputValues, prompt, promptMentionEntities],
[localizedPluginInputTemplate, localizedPluginInputValues, prompt, promptMentionEntities],
);
const promptMentionRanges = useMemo(
() => buildPromptMentionRanges(promptOverlayParts),
@ -782,7 +799,13 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
part.kind === 'slot' ? (
part.key && footerInputNameSet.has(part.key) ? (
<span key={`footer-slot-${part.key}-${index}`} aria-hidden>
{formatPromptInputValue(fieldByName.get(part.key) ?? null, pluginInputValues[part.key], part.text, t)}
{formatPromptInputValue(
locale,
fieldByName.get(part.key) ?? null,
pluginInputValues[part.key],
part.text,
t,
)}
</span>
) : (
<InlinePromptInput
@ -794,6 +817,7 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
filled={part.filled === true}
editable={Boolean(part.key && editableInputNames.has(part.key))}
open={part.key === openInlineInputName}
locale={locale}
onOpenChange={(open) => setOpenInlineInputName(open ? part.key ?? null : null)}
/>
)
@ -891,6 +915,7 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
<InlinePromptOptionPopover
field={openInlineInputField}
value={pluginInputValues[openInlineInputField.name]}
locale={locale}
onChange={(value) => {
onPluginInputValuesChange({
...pluginInputValues,
@ -1025,7 +1050,7 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
>
<div>
<span className="home-hero__plugin-hover-kicker">
{getPluginSourceLabel(hoveredPlugin)}
{getPluginSourceLabel(hoveredPlugin, t)}
</span>
<strong>{hoveredPlugin.title}</strong>
<p>{hoveredPlugin.manifest?.description ?? hoveredPlugin.id}</p>
@ -1507,6 +1532,7 @@ function buildHomeMentionEntities({
pluginOptions,
selectedPluginContexts,
skillOptions,
t,
}: {
activePluginRecord: InstalledPluginRecord | null;
activeSkillId: string | null;
@ -1516,6 +1542,7 @@ function buildHomeMentionEntities({
pluginOptions: InstalledPluginRecord[];
selectedPluginContexts: InstalledPluginRecord[];
skillOptions: SkillSummary[];
t: ReturnType<typeof useT>;
}): InlineMentionEntity[] {
const entities: InlineMentionEntity[] = [];
const pluginSeen = new Set<string>();
@ -1527,7 +1554,7 @@ function buildHomeMentionEntities({
kind: 'plugin',
label: plugin.title,
token: pluginMentionText(plugin),
title: `Plugin: ${plugin.title}`,
title: t('homeHero.pluginPrefix', { title: plugin.title }),
});
}
if (activePluginRecord && !pluginSeen.has(activePluginRecord.id)) {
@ -1536,7 +1563,7 @@ function buildHomeMentionEntities({
kind: 'plugin',
label: activePluginRecord.title,
token: pluginMentionText(activePluginRecord),
title: `Plugin: ${activePluginRecord.title}`,
title: t('homeHero.pluginPrefix', { title: activePluginRecord.title }),
});
}
const skillSeen = new Set<string>();
@ -1548,7 +1575,7 @@ function buildHomeMentionEntities({
kind: 'skill',
label: skill.name,
token: inlineMentionToken(skill.name),
title: `Skill: ${skill.name}`,
title: t('homeHero.skillPrefix', { title: skill.name }),
});
if (skill.id !== skill.name) {
entities.push({
@ -1556,7 +1583,7 @@ function buildHomeMentionEntities({
kind: 'skill',
label: skill.id,
token: inlineMentionToken(skill.id),
title: `Skill: ${skill.name}`,
title: t('homeHero.skillPrefix', { title: skill.name }),
});
}
}
@ -1566,7 +1593,7 @@ function buildHomeMentionEntities({
kind: 'skill',
label: activeSkillTitle,
token: inlineMentionToken(activeSkillTitle),
title: `Skill: ${activeSkillTitle}`,
title: t('homeHero.skillPrefix', { title: activeSkillTitle }),
});
}
for (const server of mcpOptions) {
@ -1620,6 +1647,7 @@ function InlineMentionToken({
text: string;
onOpenPluginDetails: (record: InstalledPluginRecord) => void;
}) {
const t = useT();
if (entity.kind === 'plugin' && pluginRecord) {
return (
<button
@ -1629,7 +1657,7 @@ function InlineMentionToken({
data-testid={`home-hero-prompt-plugin-${pluginRecord.id}`}
onMouseDown={(event) => event.preventDefault()}
onClick={() => onOpenPluginDetails(pluginRecord)}
title={entity.title ?? `Plugin: ${pluginRecord.title}`}
title={entity.title ?? t('homeHero.pluginPrefix', { title: pluginRecord.title })}
>
{text}
</button>
@ -1655,6 +1683,7 @@ interface InlinePromptInputProps {
editable?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
locale: ReturnType<typeof useI18n>['locale'];
}
// Render plugin-input placeholders as read-only styled spans. Earlier
@ -1676,9 +1705,10 @@ function InlinePromptInput({
editable = false,
open = false,
onOpenChange = () => undefined,
locale,
}: InlinePromptInputProps) {
const label = field?.label ?? name;
const displayValue = formatPromptInputValue(field, value, fallbackText);
const label = field ? localizePluginInputLabel(locale, field) : name;
const displayValue = formatPromptInputValue(locale, field, value, fallbackText);
// No aria-label here: the editable control with this label lives in
// the PluginInputsForm below, and findByLabelText must resolve to one
// element. The span is decorative — it just highlights where the
@ -1729,26 +1759,30 @@ function InlinePromptOptionPopover({
field,
value,
onChange,
locale,
}: {
field: InputFieldSpec;
value: unknown;
onChange: (value: unknown) => void;
locale: ReturnType<typeof useI18n>['locale'];
}) {
const label = localizePluginInputLabel(locale, field);
const note = fieldPopoverNote(field);
return (
<div
className="home-hero__prompt-option-popover"
data-testid={`home-hero-prompt-option-${field.name}`}
onMouseDown={(event) => event.stopPropagation()}
>
<span className="home-hero__prompt-option-label">{field.label ?? field.name}</span>
{renderInlinePromptEditor(field, value, onChange)}
{fieldPopoverNote(field) ? (
<span className="home-hero__prompt-option-label">{label}</span>
{renderInlinePromptEditor(field, value, onChange, locale)}
{note ? (
<span
className="home-hero__prompt-option-note"
data-tone={fieldPopoverNoteTone(field)}
data-testid={`home-hero-prompt-option-${field.name}-note`}
>
{fieldPopoverNote(field)}
{localizePluginPlaceholder(locale, note)}
</span>
) : null}
</div>
@ -2259,7 +2293,9 @@ function renderInlinePromptEditor(
field: InputFieldSpec,
value: unknown,
onChange: (value: unknown) => void,
locale: ReturnType<typeof useI18n>['locale'],
) {
const label = localizePluginInputLabel(locale, field);
if (field.type === 'select' && Array.isArray(field.options)) {
const optionLabels = optionLabelMap(field);
return (
@ -2268,12 +2304,14 @@ function renderInlinePromptEditor(
value={value === undefined || value === null ? '' : String(value)}
onChange={(event) => onChange(event.target.value)}
data-testid={`home-hero-prompt-option-${field.name}-select`}
aria-label={field.label ?? field.name}
aria-label={label}
>
{field.placeholder ? <option value="">{field.placeholder}</option> : null}
{field.placeholder ? (
<option value="">{localizePluginPlaceholder(locale, field.placeholder)}</option>
) : null}
{field.options.map((option) => (
<option key={option} value={option}>
{optionLabels[option] ?? option}
{localizePluginDisplayValue(locale, optionLabels[option] ?? option)}
</option>
))}
</select>
@ -2285,21 +2323,26 @@ function renderInlinePromptEditor(
value={value === undefined || value === null ? '' : String(value)}
onChange={(event) => onChange(event.target.value)}
data-testid={`home-hero-prompt-option-${field.name}-input`}
aria-label={field.label ?? field.name}
aria-label={label}
placeholder={localizePluginPlaceholder(locale, field.placeholder)}
/>
);
}
function formatPromptInputValue(
locale: ReturnType<typeof useI18n>['locale'],
field: InputFieldSpec | null,
value: unknown,
fallbackText: string,
t?: ReturnType<typeof useT>,
): string {
if (value === undefined || value === null || value === '') return fallbackText;
if (value === undefined || value === null || value === '') {
return field ? localizePluginPlaceholder(locale, field.placeholder, fallbackText) : fallbackText;
}
const raw = String(value);
if (!field) return raw;
return t ? footerInputValueLabel(field, raw, t) : optionLabelMap(field)[raw] ?? raw;
const optionLabel = field ? optionLabelMap(field)[raw] : undefined;
const label = field && t ? footerInputValueLabel(field, raw, t) : optionLabel ?? raw;
return localizePluginDisplayValue(locale, label);
}
function optionLabelMap(field: InputFieldSpec): Record<string, string> {
@ -2404,8 +2447,8 @@ function connectorMatchesQuery(connector: ConnectorDetail, query: string): boole
.includes(q);
}
function getPluginSourceLabel(plugin: InstalledPluginRecord): string {
return plugin.sourceKind === 'bundled' ? 'Official' : 'My plugin';
function getPluginSourceLabel(plugin: InstalledPluginRecord, t: ReturnType<typeof useT>): string {
return plugin.sourceKind === 'bundled' ? t('homeHero.official') : t('homeHero.myPlugin');
}
function getPluginQueryPreview(plugin: InstalledPluginRecord): string {
@ -2464,6 +2507,7 @@ function RailGroup({
const cls = isTabs
? ['home-hero__type-tab', `home-hero__type-tab--${group}`]
: ['home-hero__rail-chip', `home-hero__rail-chip--${group}`];
const label = homeHeroChipLabel(chip, t);
if (isActive) cls.push('is-active');
if (isPending) cls.push('is-pending');
return (
@ -2486,7 +2530,7 @@ function RailGroup({
className={isTabs ? 'home-hero__type-tab-icon' : 'home-hero__rail-chip-icon'}
/>
<span className={isTabs ? 'home-hero__type-tab-label' : 'home-hero__rail-chip-label'}>
{homeHeroChipLabel(chip.id, t)}
{label}
</span>
</button>
);
@ -2505,13 +2549,13 @@ function ActiveTypeChip({ chip, onClear }: { chip: HomeHeroChip; onClear: () =>
data-testid="home-hero-active-type-chip"
data-chip-id={chip.id}
title={homeHeroChipTitle(chip, t)}
aria-label={`${homeHeroChipLabel(chip.id, t)} ${t('common.delete')}`}
aria-label={`${homeHeroChipLabel(chip, t)} ${t('common.delete')}`}
onClick={onClear}
>
<span className="home-hero__active-type-chip-icon" aria-hidden>
<Icon name={chip.icon} size={13} />
</span>
<span>{homeHeroChipLabel(chip.id, t)}</span>
<span>{homeHeroChipLabel(chip, t)}</span>
<Icon name="close" size={12} className="home-hero__active-type-chip-close" />
</button>
);
@ -2595,7 +2639,7 @@ function ShortcutsMenu({
onClick={() => onPickChip(chip)}
>
<Icon name={chip.icon} size={14} className="home-hero__shortcut-menu-icon" />
<span>{homeHeroChipLabel(chip.id, t)}</span>
<span>{homeHeroChipLabel(chip, t)}</span>
</button>
);
})}
@ -2605,31 +2649,12 @@ function ShortcutsMenu({
);
}
function homeHeroChipLabel(chipId: string, t: ReturnType<typeof useT>): string {
switch (chipId) {
case 'prototype': return t('homeHero.chip.prototype');
case 'live-artifact': return t('homeHero.chip.liveArtifact');
case 'deck': return t('homeHero.chip.deck');
case 'image': return t('homeHero.chip.image');
case 'video': return t('homeHero.chip.video');
case 'hyperframes': return t('homeHero.chip.hyperframes');
case 'audio': return t('homeHero.chip.audio');
case 'create-plugin': return t('homeHero.chip.createPlugin');
case 'figma': return t('homeHero.chip.figma');
case 'template': return t('homeHero.chip.template');
default: return chipId;
}
function homeHeroChipLabel(chip: HomeHeroChip, t: ReturnType<typeof useT>): string {
return t(chip.labelKey);
}
function homeHeroChipTitle(chip: HomeHeroChip, t: ReturnType<typeof useT>): string {
switch (chip.id) {
case 'live-artifact': return t('homeHero.chip.liveArtifactHint');
case 'hyperframes': return t('homeHero.chip.hyperframesHint');
case 'create-plugin': return t('homeHero.chip.createPluginHint');
case 'figma': return t('homeHero.chip.figmaHint');
case 'template': return t('homeHero.chip.templateHint');
default: return homeHeroChipLabel(chip.id, t);
}
return chip.hintKey ? t(chip.hintKey) : homeHeroChipLabel(chip, t);
}
function homeHeroExamplePluginsForChip(

View file

@ -31,7 +31,6 @@ import {
import {
applyPlugin,
listPlugins,
renderPluginBriefTemplate,
resolvePluginQueryFallback,
} from '../state/projects';
import { fetchMcpServers } from '../state/mcp';
@ -42,6 +41,11 @@ import {
} from '../i18n/content';
import { fetchElevenLabsVoiceOptions } from '../providers/elevenlabs-voices';
import { fetchProjectFiles, projectFileUrl } from '../providers/registry';
import {
localizePluginBriefTemplate,
rawPluginValueFromLocalizedDisplay,
renderLocalizedPluginBriefTemplate,
} from '../i18n/plugin-content';
import type {
DesignSystemSummary,
Project,
@ -177,6 +181,7 @@ interface Props {
// Stage B: optional callbacks the rail's migration chips need.
// HomeView itself never imports them; EntryShell threads them
// through so the dispatcher can stay declarative.
onImportFolder?: () => Promise<void> | void;
onOpenNewProject?: (tab: 'template') => void;
promptHandoff?: HomePromptHandoff | null;
skills?: SkillSummary[];
@ -199,6 +204,7 @@ export function HomeView({
onOpenProject,
onViewAllProjects,
onBrowseRegistry,
onImportFolder,
onOpenNewProject,
promptHandoff,
skills = EMPTY_SKILLS,
@ -351,7 +357,12 @@ export function HomeView({
elevenLabsVoicesLoading,
},
);
const nextRendered = renderPluginBriefTemplate(composer.queryTemplate, composer.inputs);
const nextRendered = renderLocalizedPluginBriefTemplate(
locale,
composer.queryTemplate,
composer.inputs,
composer.fields,
);
// When the plugin was bound through a type chip the user owns the
// textarea; never back-fill from this effect even if external
// lists (ElevenLabs voices, prompt templates) reload after the
@ -598,7 +609,7 @@ export function HomeView({
nextPrompt !== undefined && nextPrompt !== null
? nextPrompt
: queryTemplate
? renderPluginBriefTemplate(queryTemplate, optimisticInputs)
? renderLocalizedPluginBriefTemplate(locale, queryTemplate, optimisticInputs, inputFields)
: null;
if (options?.chipId && shouldResolveImmediately) setPendingChipId(options.chipId);
setError(null);
@ -656,7 +667,7 @@ export function HomeView({
// still matches the visible active state — concurrent clicks
// would otherwise stomp a successful later apply.
setActive((prev) => (prev?.record.id === record.id ? { ...prev, inputsValid: false } : prev));
setError(`Failed to apply ${record.title}. Make sure the daemon is reachable.`);
setError(t('home.error.applyFailed', { title: record.title }));
return;
}
const reconciledInputs: Record<string, unknown> = { ...optimisticInputs };
@ -665,17 +676,15 @@ export function HomeView({
reconciledInputs[field.name] = field.default;
}
}
const reconciledFields = options?.preserveInputFields ? inputFields : result.inputs ?? inputFields;
setActive((prev) =>
prev && prev.record.id === record.id
? {
...prev,
result,
inputs: reconciledInputs,
inputFields: options?.preserveInputFields ? inputFields : result.inputs ?? inputFields,
inputsValid: pluginInputsAreValid(
options?.preserveInputFields ? inputFields : result.inputs ?? inputFields,
reconciledInputs,
),
inputFields: reconciledFields,
inputsValid: pluginInputsAreValid(reconciledFields, reconciledInputs),
projectMetadata: homeCreateProjectMetadata(
prev.projectKind,
reconciledInputs,
@ -697,7 +706,12 @@ export function HomeView({
? options.queryTemplate
: result.query || resolvePluginQueryFallback(record.manifest?.od?.useCase?.query, locale);
if (reconciledQuery) {
const reconciledPrompt = renderPluginBriefTemplate(reconciledQuery, reconciledInputs);
const reconciledPrompt = renderLocalizedPluginBriefTemplate(
locale,
reconciledQuery,
reconciledInputs,
reconciledFields,
);
if (reconciledPrompt !== optimisticPrompt) {
setPrompt((current) => {
if (current !== optimisticPrompt) return current;
@ -825,7 +839,12 @@ export function HomeView({
: resolvePluginQueryFallback(record.manifest?.od?.useCase?.query, locale);
if (!query) return null;
const fields = options?.inputFields ?? record.manifest?.od?.inputs ?? [];
return renderPluginBriefTemplate(query, hydratePluginInputs(fields, options?.inputs));
return renderLocalizedPluginBriefTemplate(
locale,
query,
hydratePluginInputs(fields, options?.inputs),
fields,
);
}
function renderPluginContextPrompt(
@ -834,9 +853,11 @@ export function HomeView({
): string | null {
const query = resolvePluginQueryFallback(record.manifest?.od?.useCase?.query, locale);
if (!query) return null;
return renderPluginBriefTemplate(
return renderLocalizedPluginBriefTemplate(
locale,
query,
hydratePluginInputs(record.manifest?.od?.inputs ?? [], inputs),
record.manifest?.od?.inputs ?? [],
);
}
@ -846,7 +867,7 @@ export function HomeView({
setPendingPluginUseHandoff(null);
if (!record) {
setError(
`Plugin "${pendingPluginUseHandoff.pluginId}" is not installed. Refresh Plugins and try again.`,
t('home.error.pluginNotInstalled', { id: pendingPluginUseHandoff.pluginId }),
);
return;
}
@ -892,6 +913,7 @@ export function HomeView({
active.queryTemplate,
nextPrompt,
active.inputFields,
locale,
);
if (!extracted) return;
const nextInputs = { ...active.inputs, ...extracted };
@ -945,7 +967,7 @@ export function HomeView({
const inputsValid = pluginInputsAreValid(inputFields, normalized);
const nextRendered =
queryTemplate !== null
? renderPluginBriefTemplate(queryTemplate, normalized)
? renderLocalizedPluginBriefTemplate(locale, queryTemplate, normalized, inputFields)
: active.lastRenderedPrompt;
if (
!active.suppressPromptSync &&
@ -1106,7 +1128,7 @@ export function HomeView({
const record = plugins.find((p) => p.id === targetId);
if (!record) {
setError(
`Bundled scenario "${targetId}" is not installed. Reinstall the daemon to restore the default plugin set.`,
t('home.error.bundledScenarioMissing', { id: targetId }),
);
return;
}
@ -1169,9 +1191,17 @@ export function HomeView({
queuePluginAuthoring(chip.id);
return;
}
case 'import-folder': {
if (!onImportFolder) {
setError(t('home.error.folderImportUnavailable'));
return;
}
void onImportFolder();
return;
}
case 'open-template-picker': {
if (!onOpenNewProject) {
setError('Template picker is not available in this shell.');
setError(t('home.error.templatePickerUnavailable'));
return;
}
onOpenNewProject('template');
@ -1194,7 +1224,7 @@ export function HomeView({
});
let submittedActive = active;
if (submittedActive && !submittedActive.inputsValid) {
setError('Fill the required plugin parameters before running.');
setError(t('home.error.fillRequiredParams'));
return;
}
const defaultInputs = { prompt: trimmed };
@ -1216,7 +1246,7 @@ export function HomeView({
if (submittedActive && (!submittedActive.result || activeInputsChangedForSubmit)) {
const result = await resolveActivePlugin(submittedActive.record, submittedPluginInputs);
if (!result) {
setError(`Failed to apply ${submittedActive.record.title}. Check the plugin parameters and try again.`);
setError(t('home.error.applyFailedWithParams', { title: submittedActive.record.title }));
return;
}
submittedActive = { ...submittedActive, result, inputs: submittedPluginInputs };
@ -1533,7 +1563,7 @@ function footerInputNamesForChip(chipId: string | null): string[] {
if (chipId === 'deck') return ['designSystem', 'slideCount', 'speakerNotes'];
if (chipId === 'image') return ['designSystem', 'model', 'ratio', 'resolution'];
if (chipId === 'video') return ['designSystem', 'model', 'ratio', 'duration', 'resolution'];
if (chipId === 'audio') return ['audioType', 'model', 'duration'];
if (chipId === 'audio') return ['audioType', 'model', 'duration', 'voice'];
if (chipId === 'hyperframes') return ['ratio', 'duration'];
return [];
}
@ -1839,24 +1869,26 @@ function extractPluginInputsFromPrompt(
template: string,
prompt: string,
fields: InputFieldSpec[],
locale: ReturnType<typeof useI18n>['locale'],
): Record<string, unknown> | null {
const localizedTemplate = localizePluginBriefTemplate(locale, template) ?? template;
TEMPLATE_INPUT_PATTERN.lastIndex = 0;
const fieldByName = new Map(fields.map((field) => [field.name, field]));
const keys: string[] = [];
let pattern = '^';
let lastIndex = 0;
let match: RegExpExecArray | null;
while ((match = TEMPLATE_INPUT_PATTERN.exec(template)) !== null) {
while ((match = TEMPLATE_INPUT_PATTERN.exec(localizedTemplate)) !== null) {
const placeholder = match[0];
const key = match[1];
if (!key) continue;
pattern += escapeRegExp(template.slice(lastIndex, match.index));
pattern += escapeRegExp(localizedTemplate.slice(lastIndex, match.index));
pattern += '([\\s\\S]*?)';
keys.push(key);
lastIndex = match.index + placeholder.length;
}
if (keys.length === 0) return null;
pattern += escapeRegExp(template.slice(lastIndex));
pattern += escapeRegExp(localizedTemplate.slice(lastIndex));
const renderedMatch = new RegExp(pattern + '$').exec(prompt);
if (!renderedMatch) return null;
const next: Record<string, unknown> = {};
@ -1864,30 +1896,49 @@ function extractPluginInputsFromPrompt(
const field = fieldByName.get(key);
if (!field) return;
const raw = renderedMatch[index + 1] ?? '';
next[key] = coercePromptInputValue(raw, field);
next[key] = coercePromptInputValue(raw, field, locale);
});
return next;
}
function coercePromptInputValue(raw: string, field: InputFieldSpec): unknown {
function coercePromptInputValue(
raw: string,
field: InputFieldSpec,
locale: ReturnType<typeof useI18n>['locale'],
): unknown {
const rawType = (field as { type?: unknown }).type;
const type = typeof rawType === 'string' ? rawType : 'string';
const trimmed = raw.trim();
if (type === 'number') {
if (trimmed.length === 0) return undefined;
const parsed = Number(trimmed);
const parsed = parsePromptNumberInput(trimmed, locale);
return Number.isFinite(parsed) ? parsed : raw;
}
if (type === 'boolean') {
if (trimmed.toLowerCase() === 'true') return true;
if (trimmed.toLowerCase() === 'false') return false;
}
if (type === 'select' && Array.isArray(field.options) && field.options.includes(trimmed)) {
return trimmed;
if (type === 'select' && Array.isArray(field.options)) {
if (field.options.includes(trimmed)) return trimmed;
return rawPluginValueFromLocalizedDisplay(locale, field, trimmed) ?? raw;
}
return raw;
}
function parsePromptNumberInput(
value: string,
locale: ReturnType<typeof useI18n>['locale'],
): number {
const parsed = Number(value);
if (Number.isFinite(parsed)) return parsed;
if (locale !== 'zh-CN') return Number.NaN;
const normalized = value.replace(/,/g, '');
const localizedMatch = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+))\s*(?:秒|秒钟|分钟|分|小时|时|页|张|个|轮|次)?$/.exec(normalized);
if (!localizedMatch?.[1]) return Number.NaN;
const localizedParsed = Number(localizedMatch[1]);
return Number.isFinite(localizedParsed) ? localizedParsed : Number.NaN;
}
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

View file

@ -44,7 +44,7 @@ interface Props {
}
export function InlinePluginsRail(props: Props) {
const { locale } = useI18n();
const { locale, t } = useI18n();
const [plugins, setPlugins] = useState<InstalledPluginRecord[]>([]);
const [pendingId, setPendingId] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
@ -74,9 +74,7 @@ export function InlinePluginsRail(props: Props) {
});
setPendingId(null);
if (!result) {
setError(
`Failed to apply ${record.title}. Make sure the daemon is reachable.`,
);
setError(t('home.error.applyFailed', { title: record.title }));
return;
}
props.onApplied(record, result);

View file

@ -30,6 +30,8 @@ import type {
} from '../state/mcp';
import { fetchAgents } from '../providers/registry';
import type { AgentInfo } from '../types';
import { useI18n } from '../i18n';
import { zhCN } from '../i18n/inline';
import { Icon } from './Icon';
import { useT } from '../i18n';
@ -216,47 +218,65 @@ const ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
const CATEGORY_ORDER: ReadonlyArray<{
id: NonNullable<McpTemplate['category']>;
label: string;
labelZh: string;
hint: string;
hintZh: string;
}> = [
{
id: 'image-generation',
label: 'Image generation',
labelZh: '图片生成',
hint: 'Models that produce raster, vector or video assets.',
hintZh: '生成位图、矢量或视频素材的模型。',
},
{
id: 'image-editing',
label: 'Image editing',
labelZh: '图片编辑',
hint: 'Local post-processing, OCR and CV-driven edits.',
hintZh: '本地后处理、OCR 和计算机视觉编辑。',
},
{
id: 'web-capture',
label: 'Web capture',
labelZh: '网页捕获',
hint: 'Render a URL into an image so the agent can see what it built.',
hintZh: '把 URL 渲染成图片,让代理能看到自己构建的内容。',
},
{
id: 'design-systems',
label: 'Design systems',
labelZh: '设计体系',
hint: 'Figma read/write, design-token translation, brand inspiration.',
hintZh: 'Figma 读写、设计 Token 转换和品牌灵感。',
},
{
id: 'ui-components',
label: 'UI components',
labelZh: 'UI 组件',
hint: 'Designer-grade components, blocks and landing-page material.',
hintZh: '设计师级组件、区块和落地页素材。',
},
{
id: 'data-viz',
label: 'Data viz',
labelZh: '数据可视化',
hint: 'Charts and diagrams as proper image artifacts.',
hintZh: '把图表和图示生成为正式图片产物。',
},
{
id: 'publishing',
label: 'Publishing',
labelZh: '发布',
hint: 'Push generated artifacts to a public URL.',
hintZh: '把生成的产物发布到公开 URL。',
},
{
id: 'utilities',
label: 'Utilities',
labelZh: '实用工具',
hint: 'Filesystem, fetch, GitHub and similar generic tools.',
hintZh: '文件系统、fetch、GitHub 等通用工具。',
},
];
@ -271,21 +291,32 @@ function templateMatchesQuery(tpl: McpTemplate, q: string): boolean {
);
}
function validateRow(r: DraftRow): string | null {
function validateRow(
r: DraftRow,
locale: ReturnType<typeof useI18n>['locale'],
): string | null {
if (!ID_PATTERN.test(r.id)) {
return 'ID must start with a letter or digit and only contain letters, digits, dash, or underscore (max 64 chars).';
return zhCN(
locale,
'ID must start with a letter or digit and only contain letters, digits, dash, or underscore (max 64 chars).',
'ID 必须以字母或数字开头,只能包含字母、数字、短横线或下划线(最多 64 个字符)。',
);
}
if (r.transport === 'stdio') {
if (!r.command || !r.command.trim()) return 'Command is required for stdio transport.';
if (!r.command || !r.command.trim()) {
return zhCN(locale, 'Command is required for stdio transport.', 'stdio 传输需要填写命令。');
}
} else {
if (!r.url || !r.url.trim()) return 'URL is required for SSE / HTTP transport.';
if (!r.url || !r.url.trim()) {
return zhCN(locale, 'URL is required for SSE / HTTP transport.', 'SSE / HTTP 传输需要填写 URL。');
}
try {
const parsed = new URL(r.url);
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
return 'URL must use http:// or https://.';
return zhCN(locale, 'URL must use http:// or https://.', 'URL 必须使用 http:// 或 https://。');
}
} catch {
return 'URL is malformed.';
return zhCN(locale, 'URL is malformed.', 'URL 格式不正确。');
}
}
return null;
@ -301,6 +332,7 @@ export const McpClientSection = forwardRef<McpClientSectionHandle, Props>(
function McpClientSection({ onServersChanged, onDirtyChange }, ref) {
const t = useT();
const analytics = useAnalytics();
const { locale } = useI18n();
const [rows, setRows] = useState<DraftRow[]>([]);
const [savedSig, setSavedSig] = useState<string>('[]');
const [templates, setTemplates] = useState<McpTemplate[]>([]);
@ -388,7 +420,7 @@ export const McpClientSection = forwardRef<McpClientSectionHandle, Props>(
const save = async (): Promise<boolean> => {
for (const r of rows) {
const err = validateRow(r);
const err = validateRow(r, locale);
if (err) {
setError(`${r.label || r.id}: ${err}`);
return false;
@ -465,6 +497,7 @@ export const McpClientSection = forwardRef<McpClientSectionHandle, Props>(
onPick={addFromTemplate}
onPickBlank={addBlank}
onClose={() => setPickerOpen(false)}
locale={locale}
/>
) : null}
@ -496,6 +529,7 @@ export const McpClientSection = forwardRef<McpClientSectionHandle, Props>(
onRemove={() => removeRow(idx)}
onMoveUp={idx > 0 ? () => moveRow(idx, -1) : undefined}
onMoveDown={idx < rows.length - 1 ? () => moveRow(idx, 1) : undefined}
locale={locale}
/>
))}
</div>
@ -536,6 +570,7 @@ interface PickerPanelProps {
onPick: (tpl: McpTemplate) => void;
onPickBlank: () => void;
onClose: () => void;
locale: ReturnType<typeof useI18n>['locale'];
}
/**
@ -558,6 +593,7 @@ function PickerPanel({
onPick,
onPickBlank,
onClose,
locale,
}: PickerPanelProps) {
const grouped = useMemo(() => {
const buckets = new Map<McpTemplate['category'], McpTemplate[]>();
@ -597,15 +633,19 @@ function PickerPanel({
open={defaultOpen}
>
<summary className="mcp-picker-group-summary">
<span className="mcp-picker-group-summary-title">{cat.label}</span>
<span className="mcp-picker-group-summary-title">
{zhCN(locale, cat.label, cat.labelZh)}
</span>
<span className="mcp-picker-group-summary-count">
{hasQuery ? `${matched.length}/${all.length}` : all.length}
</span>
<span className="mcp-picker-group-summary-hint">{cat.hint}</span>
<span className="mcp-picker-group-summary-hint">
{zhCN(locale, cat.hint, cat.hintZh)}
</span>
</summary>
<div className="mcp-picker-grid">
{matched.map((tpl) => (
<PickerCard key={tpl.id} tpl={tpl} onPick={() => onPick(tpl)} />
<PickerCard key={tpl.id} tpl={tpl} onPick={() => onPick(tpl)} locale={locale} />
))}
</div>
</details>
@ -616,24 +656,32 @@ function PickerPanel({
<div className="mcp-picker">
<div className="mcp-picker-head">
<div className="mcp-picker-head-row">
<strong>Pick a template</strong>
<strong>{zhCN(locale, 'Pick a template', '选择模板')}</strong>
<button
type="button"
className="icon-btn mcp-picker-close"
onClick={onClose}
title="Close picker"
aria-label="Close picker"
title={zhCN(locale, 'Close picker', '关闭模板选择器')}
aria-label={zhCN(locale, 'Close picker', '关闭模板选择器')}
>
×
</button>
</div>
<span className="hint">
Pre-fills the form. You can still edit any field after.
{zhCN(
locale,
'Pre-fills the form. You can still edit any field after.',
'选择后会自动填充表单,之后仍可编辑任何字段。',
)}
</span>
<input
type="search"
className="mcp-picker-search"
placeholder="Filter by name, transport, capability…"
placeholder={zhCN(
locale,
'Filter by name, transport, capability…',
'按名称、传输方式或能力筛选…',
)}
value={query}
onChange={(e) => onQueryChange(e.target.value)}
spellCheck={false}
@ -645,8 +693,11 @@ function PickerPanel({
{renderGroups}
{hasQuery && visibleTotal === 0 ? (
<div className="mcp-picker-empty hint">
No templates match &ldquo;{trimmed}&rdquo;. Try clearing the filter
or use the custom server option below.
{zhCN(
locale,
`No templates match “${trimmed}”. Try clearing the filter or use the custom server option below.`,
`没有模板匹配“${trimmed}”。可以清除筛选,或使用下方的自定义服务器选项。`,
)}
</div>
) : null}
</div>
@ -659,10 +710,14 @@ function PickerPanel({
>
<span className="mcp-picker-item-head">
<Icon name="settings" size={13} />
<strong>Custom server</strong>
<strong>{zhCN(locale, 'Custom server', '自定义服务器')}</strong>
</span>
<span className="mcp-picker-desc">
Empty form. Pick stdio or SSE / HTTP and fill the fields yourself.
{zhCN(
locale,
'Empty form. Pick stdio or SSE / HTTP and fill the fields yourself.',
'空表单。选择 stdio 或 SSE / HTTP 后自行填写字段。',
)}
</span>
</button>
</div>
@ -673,9 +728,11 @@ function PickerPanel({
function PickerCard({
tpl,
onPick,
locale,
}: {
tpl: McpTemplate;
onPick: () => void;
locale: ReturnType<typeof useI18n>['locale'];
}) {
return (
<div className="mcp-picker-item">
@ -693,7 +750,9 @@ function PickerCard({
<span className="mcp-picker-desc">{tpl.description}</span>
{tpl.example ? (
<span className="mcp-picker-example">
<span className="mcp-picker-example-label">Try:</span>
<span className="mcp-picker-example-label">
{zhCN(locale, 'Try:', '试试:')}
</span>
<span className="mcp-picker-example-text">"{tpl.example}"</span>
</span>
) : null}
@ -707,7 +766,7 @@ function PickerCard({
title={tpl.homepage}
>
<Icon name="external-link" size={11} />
<span>Homepage</span>
<span>{zhCN(locale, 'Homepage', '主页')}</span>
</a>
) : null}
</div>
@ -726,13 +785,24 @@ interface RowProps {
onRemove: () => void;
onMoveUp?: () => void;
onMoveDown?: () => void;
locale: ReturnType<typeof useI18n>['locale'];
}
function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMoveDown }: RowProps) {
function McpRow({
row,
idx,
total,
template,
onChange,
onRemove,
onMoveUp,
onMoveDown,
locale,
}: RowProps) {
const isHttpLike = row.transport === 'http' || row.transport === 'sse';
const usesManagedOAuth = isHttpLike && effectiveMcpAuthMode(row) === 'oauth';
const [expanded, setExpanded] = useState<boolean>(false);
const summaryTitle = row.label?.trim() || row.id || 'Unnamed MCP server';
const summaryTitle = row.label?.trim() || row.id || zhCN(locale, 'Unnamed MCP server', '未命名 MCP 服务器');
const [showMcpExample, setShowMcpExample] = useState<boolean>(false);
const helperId = `mcp-json-helper-panel-${row._localId}`;
@ -743,12 +813,15 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
}`}
>
<div className="mcp-row-head">
<label className="mcp-row-toggle" title={row.enabled ? 'Enabled' : 'Disabled'}>
<label
className="mcp-row-toggle"
title={row.enabled ? zhCN(locale, 'Enabled', '已启用') : zhCN(locale, 'Disabled', '已禁用')}
>
<input
type="checkbox"
checked={row.enabled}
onChange={(e) => onChange({ enabled: e.target.checked })}
aria-label="Enable this MCP server"
aria-label={zhCN(locale, 'Enable this MCP server', '启用这个 MCP 服务器')}
/>
</label>
{expanded ? (
@ -756,7 +829,7 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
type="text"
className="mcp-row-label"
value={row.label ?? ''}
placeholder="Display name (optional)"
placeholder={zhCN(locale, 'Display name (optional)', '显示名称(可选)')}
onChange={(e) => onChange({ label: e.target.value })}
/>
) : (
@ -764,12 +837,16 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
type="button"
className="mcp-row-summary-title"
onClick={() => setExpanded(true)}
title="Expand to edit"
title={zhCN(locale, 'Expand to edit', '展开编辑')}
>
<span className="mcp-row-summary-name">{summaryTitle}</span>
<span
className="mcp-row-summary-transport"
aria-label={`Transport: ${row.transport}`}
aria-label={zhCN(
locale,
`Transport: ${row.transport}`,
`传输方式:${row.transport}`,
)}
>
{row.transport}
</span>
@ -780,12 +857,22 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
</span>
<div className="mcp-row-actions">
{onMoveUp ? (
<button type="button" className="icon-btn" onClick={onMoveUp} title="Move up">
<button
type="button"
className="icon-btn"
onClick={onMoveUp}
title={zhCN(locale, 'Move up', '上移')}
>
</button>
) : null}
{onMoveDown ? (
<button type="button" className="icon-btn" onClick={onMoveDown} title="Move down">
<button
type="button"
className="icon-btn"
onClick={onMoveDown}
title={zhCN(locale, 'Move down', '下移')}
>
</button>
) : null}
@ -793,7 +880,7 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
type="button"
className="icon-btn"
onClick={onRemove}
title="Remove this MCP server"
title={zhCN(locale, 'Remove this MCP server', '移除这个 MCP 服务器')}
>
×
</button>
@ -802,8 +889,10 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
className="icon-btn mcp-row-toggle-btn"
onClick={() => setExpanded((v) => !v)}
aria-expanded={expanded}
aria-label={expanded ? 'Collapse this MCP server' : 'Expand this MCP server'}
title={expanded ? 'Collapse' : 'Expand'}
aria-label={expanded
? zhCN(locale, 'Collapse this MCP server', '折叠这个 MCP 服务器')
: zhCN(locale, 'Expand this MCP server', '展开这个 MCP 服务器')}
title={expanded ? zhCN(locale, 'Collapse', '折叠') : zhCN(locale, 'Expand', '展开')}
>
<Icon name="chevron-down" size={13} />
</button>
@ -816,7 +905,7 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
<details className="mcp-row-info">
<summary className="mcp-row-info-summary">
<span className="mcp-row-info-summary-label">
About {template.label}
{zhCN(locale, `About ${template.label}`, `关于 ${template.label}`)}
</span>
{template.homepage ? (
<a
@ -828,7 +917,7 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
onClick={(e) => e.stopPropagation()}
>
<Icon name="external-link" size={11} />
<span>Homepage</span>
<span>{zhCN(locale, 'Homepage', '主页')}</span>
</a>
) : null}
</summary>
@ -839,9 +928,15 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
{template.example ? (
<p
className="mcp-row-info-example"
title="Paste this prompt into the chat composer to try the server end-to-end"
title={zhCN(
locale,
'Paste this prompt into the chat composer to try the server end-to-end',
'把这个提示词粘贴到聊天输入框,可以端到端测试这个服务器',
)}
>
<span className="mcp-row-info-example-label">Try:</span>{' '}
<span className="mcp-row-info-example-label">
{zhCN(locale, 'Try:', '试试:')}
</span>{' '}
<span className="mcp-row-info-example-text">"{template.example}"</span>
</p>
) : null}
@ -854,22 +949,38 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
<McpOAuthControl serverId={row.id} />
) : (
<div className="mcp-oauth-hint hint">
<strong>No managed OAuth.</strong> Open Design will use this
server as configured. Add headers below if the server needs a
token.
<strong>
{zhCN(locale, 'No managed OAuth.', '无托管 OAuth。')}
</strong>{' '}
{zhCN(
locale,
'Open Design will use this server as configured. Add headers below if the server needs a token.',
'Open Design 会按当前配置使用这个服务器。如果服务器需要 token请在下方添加请求头。',
)}
</div>
)
) : null}
{isHttpLike && row._isNew && usesManagedOAuth ? (
<div className="mcp-oauth-hint hint">
Save first, then click <strong>Connect</strong> to grant Open Design
access via the provider's OAuth flow.
{zhCN(locale, 'Save first, then click', '先保存,然后点击')}{' '}
<strong>{zhCN(locale, 'Connect', '连接')}</strong>{' '}
{zhCN(
locale,
"to grant Open Design access via the provider's OAuth flow.",
'通过提供方的 OAuth 流程授予 Open Design 访问权限。',
)}
</div>
) : null}
{isHttpLike && row._isNew && !usesManagedOAuth ? (
<div className="mcp-oauth-hint hint">
<strong>No managed OAuth.</strong> Save this server and Open Design
will use it directly.
<strong>
{zhCN(locale, 'No managed OAuth.', '无托管 OAuth。')}
</strong>{' '}
{zhCN(
locale,
'Save this server and Open Design will use it directly.',
'保存这个服务器后Open Design 会直接使用它。',
)}
</div>
) : null}
@ -884,7 +995,9 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
/>
</label>
<label className="mcp-row-field">
<span className="mcp-row-field-label">Transport</span>
<span className="mcp-row-field-label">
{zhCN(locale, 'Transport', '传输方式')}
</span>
<select
value={row.transport}
onChange={(e) => {
@ -907,21 +1020,25 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
{row.transport === 'stdio' ? (
<>
<label className="mcp-row-field mcp-row-field-stack">
<span className="mcp-row-field-label">Command</span>
<span className="mcp-row-field-label">
{zhCN(locale, 'Command', '命令')}
</span>
<input
type="text"
value={row.command ?? ''}
placeholder="e.g. npx, node, /path/to/binary"
placeholder={zhCN(locale, 'e.g. npx, node, /path/to/binary', '例如 npx、node、/path/to/binary')}
onChange={(e) => onChange({ command: e.target.value })}
spellCheck={false}
/>
</label>
<label className="mcp-row-field mcp-row-field-stack">
<span className="mcp-row-field-label">Args</span>
<span className="mcp-row-field-label">
{zhCN(locale, 'Args', '参数')}
</span>
<input
type="text"
value={(row.args ?? []).join(' ')}
placeholder="space-separated"
placeholder={zhCN(locale, 'space-separated', '用空格分隔')}
onChange={(e) =>
onChange({
args: e.target.value
@ -934,7 +1051,9 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
/>
</label>
<label className="mcp-row-field mcp-row-field-stack">
<span className="mcp-row-field-label">Env (KEY=VALUE)</span>
<span className="mcp-row-field-label">
{zhCN(locale, 'Env (KEY=VALUE)', '环境变量KEY=VALUE')}
</span>
<textarea
rows={Math.max(2, (row._envText ?? '').split('\n').length)}
value={row._envText ?? ''}
@ -947,7 +1066,9 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
) : (
<>
<label className="mcp-row-field mcp-row-field-stack">
<span className="mcp-row-field-label">OAuth mode</span>
<span className="mcp-row-field-label">
{zhCN(locale, 'OAuth mode', 'OAuth 模式')}
</span>
<select
value={effectiveMcpAuthMode(row)}
onChange={(e) =>
@ -956,8 +1077,8 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
})
}
>
<option value="none">No managed OAuth</option>
<option value="oauth">Managed OAuth</option>
<option value="none">{zhCN(locale, 'No managed OAuth', '无托管 OAuth')}</option>
<option value="oauth">{zhCN(locale, 'Managed OAuth', '托管 OAuth')}</option>
</select>
</label>
<label className="mcp-row-field mcp-row-field-stack">
@ -974,7 +1095,9 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
/>
</label>
<label className="mcp-row-field mcp-row-field-stack">
<span className="mcp-row-field-label">Headers (KEY=VALUE)</span>
<span className="mcp-row-field-label">
{zhCN(locale, 'Headers (KEY=VALUE)', '请求头KEY=VALUE')}
</span>
<textarea
rows={Math.max(2, (row._headersText ?? '').split('\n').length)}
value={row._headersText ?? ''}
@ -999,7 +1122,11 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
<Icon name="eye" />
</span>
<span className="mcp-json-helper-toggle-text">
Need help? Map your MCP server's JSON config using the example below.
{zhCN(
locale,
"Need help? Map your MCP server's JSON config using the example below.",
'需要帮助?可以参考下方示例,把 MCP 服务器的 JSON 配置映射到表单字段。',
)}
</span>
</span>
<span className="mcp-json-helper-toggle-icon">
@ -1014,7 +1141,7 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
{showMcpExample && (
<div className="mcp-json-helper-example" id={helperId}>
<div className="mcp-json-helper-example-head">
Example MCP JSON
{zhCN(locale, 'Example MCP JSON', 'MCP JSON 示例')}
</div>
<pre className="mcp-json-helper-code">
<code>
@ -1056,20 +1183,26 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
</pre>
<div className="mcp-json-helper-conversion">
<div>
<strong>Command</strong>
<strong>{zhCN(locale, 'Command', '命令')}</strong>
<code>npx</code>
</div>
<div>
<strong>Args</strong>
<strong>{zhCN(locale, 'Args', '参数')}</strong>
<code>-y tdesign-mcp-server@latest</code>
</div>
<div>
<strong>Env</strong>
<strong>{zhCN(locale, 'Env', '环境变量')}</strong>
<code>API_KEY = your-key-here</code>
</div>
<div>
<strong>HTTP / SSE</strong>
<code>use url + headers instead of command / args</code>
<code>
{zhCN(
locale,
'use url + headers instead of command / args',
'使用 URL + 请求头,而不是命令 / 参数',
)}
</code>
</div>
</div>
</div>
@ -1092,6 +1225,7 @@ function McpRow({ row, idx, total, template, onChange, onRemove, onMoveUp, onMov
* reach back via postMessage (cross-origin tab opener edge cases).
*/
function McpOAuthControl({ serverId }: { serverId: string }) {
const { locale } = useI18n();
const [status, setStatus] = useState<McpOAuthStatusResponse | null>(null);
const [busy, setBusy] = useState<'idle' | 'starting' | 'awaiting' | 'disconnecting' | 'refreshing'>('idle');
const [error, setError] = useState<string | null>(null);
@ -1239,7 +1373,7 @@ function McpOAuthControl({ serverId }: { serverId: string }) {
setPendingAuthUrl(null);
setStatus({ connected: false });
} else {
setError('Disconnect failed. Check daemon logs.');
setError(zhCN(locale, 'Disconnect failed. Check daemon logs.', '断开失败。请检查守护进程日志。'));
}
};
@ -1257,11 +1391,15 @@ function McpOAuthControl({ serverId }: { serverId: string }) {
<>
<span className="mcp-oauth-dot mcp-oauth-dot-ok" aria-hidden />
<span>
<strong>Connected.</strong>{' '}
<strong>{zhCN(locale, 'Connected.', '已连接。')}</strong>{' '}
{expiresLabel ? (
<span className="hint">Token expires {expiresLabel}.</span>
<span className="hint">
{zhCN(locale, `Token expires ${expiresLabel}.`, `Token 到期时间:${expiresLabel}`)}
</span>
) : (
<span className="hint">Non-expiring token.</span>
<span className="hint">
{zhCN(locale, 'Non-expiring token.', 'Token 不会过期。')}
</span>
)}
</span>
</>
@ -1269,11 +1407,15 @@ function McpOAuthControl({ serverId }: { serverId: string }) {
<>
<span className="mcp-oauth-dot mcp-oauth-dot-pending" aria-hidden />
<span>
<strong>Waiting for authorization</strong>{' '}
<strong>
{zhCN(locale, 'Waiting for authorization…', '等待授权…')}
</strong>{' '}
<span className="hint">
Approve in the browser tab that opened. We'll catch the callback
automatically or click Refresh below if you completed it
already.
{zhCN(
locale,
"Approve in the browser tab that opened. We'll catch the callback automatically — or click Refresh below if you completed it already.",
'请在打开的浏览器标签页中授权。Open Design 会自动接收回调;如果你已经完成,也可以点击下方“刷新”。',
)}
</span>
</span>
</>
@ -1281,9 +1423,13 @@ function McpOAuthControl({ serverId }: { serverId: string }) {
<>
<span className="mcp-oauth-dot" aria-hidden />
<span>
<strong>Not connected.</strong>{' '}
<strong>{zhCN(locale, 'Not connected.', '未连接。')}</strong>{' '}
<span className="hint">
Click Connect to grant Open Design access via the provider's OAuth flow.
{zhCN(
locale,
"Click Connect to grant Open Design access via the provider's OAuth flow.",
'点击“连接”,通过提供方的 OAuth 流程授予 Open Design 访问权限。',
)}
</span>
</span>
</>
@ -1298,24 +1444,30 @@ function McpOAuthControl({ serverId }: { serverId: string }) {
className="primary"
onClick={onConnect}
disabled={busy !== 'idle' && busy !== 'refreshing'}
title="Reauthenticate (replaces the existing token)"
title={zhCN(locale, 'Reauthenticate (replaces the existing token)', '重新授权(替换现有 token')}
>
{busy === 'starting' || busy === 'awaiting' ? 'Connecting…' : 'Reconnect'}
{busy === 'starting' || busy === 'awaiting'
? zhCN(locale, 'Connecting…', '连接中…')
: zhCN(locale, 'Reconnect', '重新连接')}
</button>
<button
type="button"
onClick={onRefreshStatus}
disabled={busy !== 'idle' && busy !== 'refreshing'}
title="Re-check token status against the daemon"
title={zhCN(locale, 'Re-check token status against the daemon', '通过守护进程重新检查 token 状态')}
>
{busy === 'refreshing' ? 'Checking…' : 'Refresh'}
{busy === 'refreshing'
? zhCN(locale, 'Checking…', '检查中…')
: zhCN(locale, 'Refresh', '刷新')}
</button>
<button
type="button"
onClick={onDisconnect}
disabled={busy !== 'idle' && busy !== 'refreshing'}
>
{busy === 'disconnecting' ? 'Disconnecting…' : 'Disconnect'}
{busy === 'disconnecting'
? zhCN(locale, 'Disconnecting…', '断开中…')
: zhCN(locale, 'Disconnect', '断开')}
</button>
</>
) : isAwaiting ? (
@ -1325,12 +1477,14 @@ function McpOAuthControl({ serverId }: { serverId: string }) {
className="primary"
onClick={onRefreshStatus}
disabled={busy === 'refreshing'}
title="I've completed authorization — check connection status now"
title={zhCN(locale, "I've completed authorization — check connection status now", '我已完成授权,现在检查连接状态')}
>
{busy === 'refreshing' ? 'Checking…' : 'I\u2019ve approved — Refresh'}
{busy === 'refreshing'
? zhCN(locale, 'Checking…', '检查中…')
: zhCN(locale, 'I\u2019ve approved — Refresh', '我已授权,刷新')}
</button>
<button type="button" onClick={onCancelPending}>
Cancel
{zhCN(locale, 'Cancel', '取消')}
</button>
</>
) : (
@ -1340,7 +1494,9 @@ function McpOAuthControl({ serverId }: { serverId: string }) {
onClick={onConnect}
disabled={busy !== 'idle'}
>
{busy === 'starting' ? 'Starting…' : 'Connect'}
{busy === 'starting'
? zhCN(locale, 'Starting…', '启动中…')
: zhCN(locale, 'Connect', '连接')}
</button>
)}
</div>
@ -1348,14 +1504,14 @@ function McpOAuthControl({ serverId }: { serverId: string }) {
{pendingAuthUrl && !connected ? (
<div className="mcp-oauth-fallback">
<span className="hint">
Browser didn't open?{' '}
{zhCN(locale, "Browser didn't open?", '浏览器没有打开?')}{' '}
<a
href={pendingAuthUrl}
target="_blank"
rel="noreferrer noopener"
className="md-link"
>
Open authorization page
{zhCN(locale, 'Open authorization page', '打开授权页面')}
</a>
.
</span>

View file

@ -15,6 +15,9 @@ import type {
} from '@open-design/contracts';
import { Icon, type IconName } from './Icon';
import { useI18n } from '../i18n';
import { zhCN } from '../i18n/inline';
import type { Locale } from '../i18n/types';
import type { SkillSummary } from '../types';
import { listPlugins } from '../state/projects';
import { fetchMcpServers, type McpServerConfig } from '../state/mcp';
@ -91,8 +94,20 @@ function listSupportedTimezones(): string[] {
return FALLBACK_TIMEZONES;
}
function tzCityLabel(timezone: string): string {
function tzCityLabel(timezone: string, locale: Locale = 'en'): string {
if (timezone === 'UTC') return 'UTC';
if (locale === 'zh-CN') {
const zhTimezoneLabels: Record<string, string> = {
'Asia/Shanghai': '上海',
'Asia/Tokyo': '东京',
'Asia/Singapore': '新加坡',
'Europe/London': '伦敦',
'Europe/Berlin': '柏林',
'America/New_York': '纽约',
'America/Los_Angeles': '洛杉矶',
};
if (zhTimezoneLabels[timezone]) return zhTimezoneLabels[timezone];
}
const last = timezone.split('/').pop() ?? timezone;
return last.replace(/_/g, ' ');
}
@ -114,37 +129,66 @@ function formatTime12h(time: string): string {
* weekday names only needs to happen here.
*/
type ScheduleParts =
| { kind: 'hourly'; minute: string }
| { kind: 'hourly'; minute: string; freq: string }
| { kind: 'timed'; freq: string; time: string; tz: string };
function decomposeSchedule(schedule: RoutineSchedule): ScheduleParts {
function formatScheduleTime(time: string, locale: Locale): string {
return locale === 'zh-CN' ? time : formatTime12h(time);
}
function weekdayLongLabel(locale: Locale, weekday: Weekday): string {
const fallback = WEEKDAY_LABELS.find((w) => w.value === weekday)?.long ?? 'Sunday';
if (locale !== 'zh-CN') return fallback;
return ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'][weekday] ?? fallback;
}
function weekdayShortLabel(locale: Locale, weekday: Weekday): string {
const fallback = WEEKDAY_LABELS.find((w) => w.value === weekday)?.short ?? 'Sun';
if (locale !== 'zh-CN') return fallback;
return ['日', '一', '二', '三', '四', '五', '六'][weekday] ?? fallback;
}
function scheduleKindLabel(locale: Locale, kind: ScheduleKind, fallback: string): string {
if (kind === 'hourly') return zhCN(locale, fallback, '每小时');
if (kind === 'daily') return zhCN(locale, fallback, '每天');
if (kind === 'weekdays') return zhCN(locale, fallback, '工作日');
return zhCN(locale, fallback, '每周');
}
function decomposeSchedule(schedule: RoutineSchedule, locale: Locale = 'en'): ScheduleParts {
if (schedule.kind === 'hourly') {
return { kind: 'hourly', minute: String(schedule.minute).padStart(2, '0') };
return {
kind: 'hourly',
minute: String(schedule.minute).padStart(2, '0'),
freq: scheduleKindLabel(locale, 'hourly', 'Hourly'),
};
}
const tz = tzCityLabel(schedule.timezone);
const time = formatTime12h(schedule.time);
const tz = tzCityLabel(schedule.timezone, locale);
const time = formatScheduleTime(schedule.time, locale);
const freq =
schedule.kind === 'daily'
? 'Daily'
? scheduleKindLabel(locale, 'daily', 'Daily')
: schedule.kind === 'weekdays'
? 'Weekdays'
: WEEKDAY_LABELS.find((w) => w.value === schedule.weekday)?.long ?? 'Sunday';
? scheduleKindLabel(locale, 'weekdays', 'Weekdays')
: weekdayLongLabel(locale, schedule.weekday);
return { kind: 'timed', freq, time, tz };
}
export function describeScheduleSummary(schedule: RoutineSchedule): string {
const parts = decomposeSchedule(schedule);
if (parts.kind === 'hourly') return `Hourly at :${parts.minute}`;
return `${parts.freq} at ${parts.time} · ${parts.tz}`;
export function describeScheduleSummary(schedule: RoutineSchedule, locale: Locale = 'en'): string {
const parts = decomposeSchedule(schedule, locale);
if (parts.kind === 'hourly') {
return zhCN(locale, `Hourly at :${parts.minute}`, `每小时 :${parts.minute}`);
}
return zhCN(locale, `${parts.freq} at ${parts.time} · ${parts.tz}`, `${parts.freq} ${parts.time} · ${parts.tz}`);
}
/** Renders the schedule summary as structured pill segments for better visual hierarchy. */
function buildScheduleSummaryNode(schedule: RoutineSchedule): ReactNode {
const parts = decomposeSchedule(schedule);
function buildScheduleSummaryNode(schedule: RoutineSchedule, locale: Locale = 'en'): ReactNode {
const parts = decomposeSchedule(schedule, locale);
if (parts.kind === 'hourly') {
return (
<span className="automation-pill__segments">
<span className="automation-pill__freq">Hourly</span>
<span className="automation-pill__freq">{parts.freq}</span>
<span className="automation-pill__sep">·</span>
<span className="automation-pill__time">:{parts.minute}</span>
</span>
@ -255,6 +299,7 @@ export function NewAutomationModal({
onClose,
onSaved,
}: Props) {
const { locale } = useI18n();
const editingId = initial?.routine?.id ?? null;
const [form, setForm] = useState<FormState>(emptyForm);
const [submitting, setSubmitting] = useState(false);
@ -449,12 +494,12 @@ export function NewAutomationModal({
e.preventDefault();
setError(null);
if (!form.name.trim()) {
setError('Add a title for this automation.');
setError(zhCN(locale, 'Add a title for this automation.', '请为这个自动化添加标题。'));
titleRef.current?.focus();
return;
}
if (!form.prompt.trim()) {
setError('Add a prompt for the scheduled conversation.');
setError(zhCN(locale, 'Add a prompt for the scheduled conversation.', '请为计划对话添加提示词。'));
return;
}
setSubmitting(true);
@ -496,7 +541,7 @@ export function NewAutomationModal({
});
if (!res.ok) {
const j = await res.json().catch(() => ({}));
throw new Error(j.error || `${isEdit ? 'update' : 'create'} failed: ${res.status}`);
throw new Error(j.error || zhCN(locale, `${isEdit ? 'update' : 'create'} failed: ${res.status}`, `${isEdit ? '更新' : '创建'}失败:${res.status}`));
}
const json = await res.json();
onSaved(json.routine);
@ -509,10 +554,11 @@ export function NewAutomationModal({
};
const projectName = projects.find((p) => p.id === form.projectId)?.name ?? null;
const currentSchedule = buildSchedule(form);
const projectLabel =
form.mode === 'reuse' && projectName ? projectName : 'New project each run';
const scheduleLabel = describeScheduleSummary(buildSchedule(form));
const scheduleLabelNode = buildScheduleSummaryNode(buildSchedule(form));
form.mode === 'reuse' && projectName ? projectName : zhCN(locale, 'New project each run', '每次运行新建项目');
const scheduleLabel = describeScheduleSummary(currentSchedule, locale);
const scheduleLabelNode = buildScheduleSummaryNode(currentSchedule, locale);
const mentionQueryNorm = (mention?.query ?? '').trim().toLowerCase();
const filteredSkills = filterCapabilities(
skills,
@ -551,7 +597,7 @@ export function NewAutomationModal({
kind: 'skills' as const,
id,
label: skill?.name ?? id,
meta: 'Skill',
meta: zhCN(locale, 'Skill', '技能'),
icon: 'file' as IconName,
};
}),
@ -561,7 +607,7 @@ export function NewAutomationModal({
kind: 'plugins' as const,
id,
label: plugin?.title ?? id,
meta: 'Plugin',
meta: zhCN(locale, 'Plugin', '插件'),
icon: 'sparkles' as IconName,
};
}),
@ -581,7 +627,9 @@ export function NewAutomationModal({
kind: 'connectors' as const,
id,
label: connector?.name ?? id,
meta: connector?.accountLabel ? `Connector · ${connector.accountLabel}` : 'Connector',
meta: connector?.accountLabel
? `${zhCN(locale, 'Connector', '连接器')} · ${connector.accountLabel}`
: zhCN(locale, 'Connector', '连接器'),
icon: 'link' as IconName,
};
}),
@ -592,7 +640,7 @@ export function NewAutomationModal({
className="automation-modal-backdrop"
role="dialog"
aria-modal="true"
aria-label={editingId ? 'Edit automation' : 'New automation'}
aria-label={editingId ? zhCN(locale, 'Edit automation', '编辑自动化') : zhCN(locale, 'New automation', '新建自动化')}
data-testid="automation-modal"
onClick={(e) => {
if (e.target === e.currentTarget) onClose();
@ -609,10 +657,10 @@ export function NewAutomationModal({
ref={titleRef}
type="text"
className="automation-modal__title-input"
placeholder="Automation title"
placeholder={zhCN(locale, 'Automation title', '自动化标题')}
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
aria-label="Automation title"
aria-label={zhCN(locale, 'Automation title', '自动化标题')}
data-testid="automation-modal-title"
/>
<div className="automation-modal__head-actions">
@ -623,7 +671,7 @@ export function NewAutomationModal({
onClick={() => setPopover((p) => (p === 'template' ? null : 'template'))}
>
<Icon name="sparkles" size={13} />
<span>{selectedTemplate?.defaultName ?? selectedTemplate?.title ?? 'Use template'}</span>
<span>{selectedTemplate?.defaultName ?? selectedTemplate?.title ?? zhCN(locale, 'Use template', '使用模板')}</span>
<Icon name="chevron-down" size={11} />
</button>
{popover === 'template' ? (
@ -631,6 +679,7 @@ export function NewAutomationModal({
templates={templates}
selectedId={selectedTemplateId}
onSelect={(template) => applyTemplate(template, { closePopover: true })}
locale={locale}
/>
) : null}
</div>
@ -638,7 +687,7 @@ export function NewAutomationModal({
type="button"
className="automation-modal__close"
onClick={onClose}
aria-label="Close (Esc)"
aria-label={zhCN(locale, 'Close (Esc)', '关闭Esc')}
>
<Icon name="close" size={14} />
</button>
@ -650,7 +699,7 @@ export function NewAutomationModal({
<textarea
ref={promptRef}
className="automation-modal__prompt"
placeholder="Ask the agent what to run on this schedule, or @mention context..."
placeholder={zhCN(locale, 'Ask the agent what to run on this schedule, or @mention context...', '告诉代理在这个计划中要运行什么,或用 @ 提及上下文...')}
value={form.prompt}
onChange={(e) => updatePrompt(e.target.value, e.target.selectionStart ?? e.target.value.length)}
onClick={refreshMentionFromPrompt}
@ -669,17 +718,17 @@ export function NewAutomationModal({
id="automation-context-picker"
className="automation-mention-popover"
role="listbox"
aria-label="Automation context results"
aria-label={zhCN(locale, 'Automation context results', '自动化上下文结果')}
data-testid="automation-mention-popover"
onMouseDown={(e) => e.preventDefault()}
>
<div className="automation-mention-tabs" role="tablist" aria-label="Context type">
<div className="automation-mention-tabs" role="tablist" aria-label={zhCN(locale, 'Context type', '上下文类型')}>
{[
['all', 'All'],
['skills', 'Skills'],
['plugins', 'Plugins'],
['all', zhCN(locale, 'All', '全部')],
['skills', zhCN(locale, 'Skills', '技能')],
['plugins', zhCN(locale, 'Plugins', '插件')],
['mcp', 'MCP'],
['connectors', 'Connectors'],
['connectors', zhCN(locale, 'Connectors', '连接器')],
].map(([id, label]) => (
<button
key={id}
@ -699,11 +748,13 @@ export function NewAutomationModal({
<div className="automation-mention-results">
{!hasMentionResults ? (
<div className="automation-mention-empty">
{mention.query ? `No results for "${mention.query}".` : 'Search skills, plugins, MCP servers, and connectors.'}
{mention.query
? zhCN(locale, `No results for "${mention.query}".`, `没有找到“${mention.query}”的结果。`)
: zhCN(locale, 'Search skills, plugins, MCP servers, and connectors.', '搜索技能、插件、MCP 服务器和连接器。')}
</div>
) : null}
{showSkills && filteredSkills.length > 0 ? (
<MentionSection label="Skills">
<MentionSection label={zhCN(locale, 'Skills', '技能')}>
{filteredSkills.map((skill) => (
<MentionItem
key={`skill-${skill.id}`}
@ -717,7 +768,7 @@ export function NewAutomationModal({
</MentionSection>
) : null}
{showPlugins && filteredPlugins.length > 0 ? (
<MentionSection label="Plugins">
<MentionSection label={zhCN(locale, 'Plugins', '插件')}>
{filteredPlugins.map((plugin) => (
<MentionItem
key={`plugin-${plugin.id}`}
@ -745,7 +796,7 @@ export function NewAutomationModal({
</MentionSection>
) : null}
{showConnectors && filteredConnectors.length > 0 ? (
<MentionSection label="Connectors">
<MentionSection label={zhCN(locale, 'Connectors', '连接器')}>
{filteredConnectors.map((connector) => (
<MentionItem
key={`connector-${connector.id}`}
@ -763,14 +814,14 @@ export function NewAutomationModal({
) : null}
{selectedContextItems.length > 0 ? (
<div className="automation-selected-context" aria-label="Selected automation context">
<div className="automation-selected-context" aria-label={zhCN(locale, 'Selected automation context', '已选择的自动化上下文')}>
{selectedContextItems.map((item) => (
<button
key={`${item.kind}-${item.id}`}
type="button"
className={`automation-selected-context__chip is-${item.kind}`}
onClick={() => removeSelectedContext(item.kind, item.id)}
title={`Remove ${item.label}`}
title={zhCN(locale, `Remove ${item.label}`, `移除 ${item.label}`)}
>
<Icon name={item.icon} size={11} />
<span>{item.label}</span>
@ -803,12 +854,12 @@ export function NewAutomationModal({
setForm({ ...form, mode: 'create_each_run', projectId: '' });
setPopover(null);
}}
label="New project each run"
hint="Each run starts a fresh project and conversation."
label={zhCN(locale, 'New project each run', '每次运行新建项目')}
hint={zhCN(locale, 'Each run starts a fresh project and conversation.', '每次运行都会启动一个全新的项目和对话。')}
/>
{projects.length > 0 ? (
<>
<div className="automation-popover__section-label">Existing projects</div>
<div className="automation-popover__section-label">{zhCN(locale, 'Existing projects', '已有项目')}</div>
{projects.map((p) => (
<PopoverItem
key={p.id}
@ -842,6 +893,7 @@ export function NewAutomationModal({
setForm={setForm}
timezones={timezones}
onDone={() => setPopover(null)}
locale={locale}
/>
) : null}
</PillButton>
@ -853,7 +905,7 @@ export function NewAutomationModal({
className="automation-modal__cancel"
onClick={onClose}
>
Cancel
{zhCN(locale, 'Cancel', '取消')}
</button>
<button
type="submit"
@ -862,11 +914,11 @@ export function NewAutomationModal({
>
{editingId
? submitting
? 'Saving...'
: 'Save'
? zhCN(locale, 'Saving...', '保存中...')
: zhCN(locale, 'Save', '保存')
: submitting
? 'Creating...'
: 'Create'}
? zhCN(locale, 'Creating...', '创建中...')
: zhCN(locale, 'Create', '创建')}
</button>
</div>
</footer>
@ -900,10 +952,12 @@ function TemplatePopover({
templates,
selectedId,
onSelect,
locale,
}: {
templates: AutomationTemplate[];
selectedId: string | null;
onSelect: (template: AutomationTemplate) => void;
locale: Locale;
}) {
return (
<div className="automation-popover automation-popover--templates">
@ -919,7 +973,7 @@ function TemplatePopover({
</span>
<span className="automation-template-option__body">
<span className="automation-template-option__title">{template.defaultName ?? template.title}</span>
<span className="automation-template-option__meta">{kindLabel(template.kind)}</span>
<span className="automation-template-option__meta">{kindLabel(template.kind, locale)}</span>
</span>
{selectedId === template.id ? <Icon name="check" size={13} /> : null}
</button>
@ -1054,11 +1108,13 @@ function SchedulePopover({
setForm,
timezones,
onDone,
locale,
}: {
form: FormState;
setForm: (next: FormState) => void;
timezones: string[];
onDone: () => void;
locale: Locale;
}) {
return (
<div className="automation-popover automation-popover--schedule">
@ -1072,14 +1128,14 @@ function SchedulePopover({
className={`automation-popover__kind${form.kind === k.kind ? ' is-active' : ''}`}
onClick={() => setForm({ ...form, kind: k.kind })}
>
{k.label}
{scheduleKindLabel(locale, k.kind, k.label)}
</button>
))}
</div>
{form.kind === 'hourly' ? (
<label className="automation-popover__field">
<span>Minute of every hour</span>
<span>{zhCN(locale, 'Minute of every hour', '每小时的第几分钟')}</span>
<input
type="number"
min={0}
@ -1097,23 +1153,23 @@ function SchedulePopover({
) : (
<>
{form.kind === 'weekly' ? (
<div className="automation-popover__weekdays" aria-label="Weekday">
<div className="automation-popover__weekdays" aria-label={zhCN(locale, 'Weekday', '星期')}>
{WEEKDAY_LABELS.map((d) => (
<button
key={d.value}
type="button"
className={`automation-popover__weekday${form.weekday === d.value ? ' is-active' : ''}`}
onClick={() => setForm({ ...form, weekday: d.value })}
title={d.long}
title={weekdayLongLabel(locale, d.value)}
>
{d.short}
{weekdayShortLabel(locale, d.value)}
</button>
))}
</div>
) : null}
<div className="automation-popover__row">
<label className="automation-popover__field">
<span>Time</span>
<span>{zhCN(locale, 'Time', '时间')}</span>
<input
type="time"
value={form.time}
@ -1121,14 +1177,14 @@ function SchedulePopover({
/>
</label>
<label className="automation-popover__field">
<span>Timezone</span>
<span>{zhCN(locale, 'Timezone', '时区')}</span>
<select
value={form.timezone}
onChange={(e) => setForm({ ...form, timezone: e.target.value })}
>
{timezones.map((tz) => (
<option key={tz} value={tz}>
{tzCityLabel(tz)}
{tzCityLabel(tz, locale)}
</option>
))}
</select>
@ -1143,7 +1199,7 @@ function SchedulePopover({
className="automation-popover__done-btn"
onClick={onDone}
>
Done
{zhCN(locale, 'Done', '完成')}
</button>
</div>
</div>
@ -1155,8 +1211,8 @@ function clampMinute(value: number): number {
return Math.max(0, Math.min(59, Math.round(value)));
}
function kindLabel(kind: AutomationTemplateKind): string {
function kindLabel(kind: AutomationTemplateKind, locale: Locale): string {
if (kind === 'orbit') return 'Orbit';
if (kind === 'live-artifact') return 'Live artifact';
return 'Automation';
if (kind === 'live-artifact') return zhCN(locale, 'Live artifact', '实时制品');
return zhCN(locale, 'Automation', '自动化');
}

View file

@ -797,7 +797,7 @@ export function NewProjectPanel({
type="button"
className={`newproj-tabs-arrow left${tabScroll.left ? '' : ' hidden'}`}
onClick={() => scrollTabs(-1)}
aria-label="Scroll project types left"
aria-label={t('newproj.scrollTypesLeft')}
tabIndex={tabScroll.left ? 0 : -1}
>
<Icon name="chevron-left" size={16} strokeWidth={2} />
@ -830,7 +830,7 @@ export function NewProjectPanel({
type="button"
className={`newproj-tabs-arrow right${tabScroll.right ? '' : ' hidden'}`}
onClick={() => scrollTabs(1)}
aria-label="Scroll project types right"
aria-label={t('newproj.scrollTypesRight')}
tabIndex={tabScroll.right ? 0 : -1}
>
<Icon name="chevron-right" size={16} strokeWidth={2} />
@ -843,7 +843,7 @@ export function NewProjectPanel({
// "Beta" is an internationally adopted brand-style status marker;
// intentionally not run through t() (consistent with short product
// status pills that read the same across our supported locales).
<span className="newproj-title-badge" aria-label="Beta feature">Beta</span>
<span className="newproj-title-badge" aria-label={t('newproj.betaFeature')}>Beta</span>
) : null}
</h3>
@ -1149,7 +1149,7 @@ function PlatformPicker({
className="newproj-section ds-picker platform-picker"
ref={wrapRef}
>
<label className="newproj-label">Target platforms</label>
<label className="newproj-label">{t('newproj.targetPlatformsLabel')}</label>
<button
type="button"
className={`ds-picker-trigger${open ? ' open' : ''}${primary ? '' : ' empty'}`}
@ -1160,7 +1160,7 @@ function PlatformPicker({
>
<span className="ds-picker-meta">
<span className="ds-picker-title">
{primary ? t(primary.labelKey) : 'Pick a platform'}
{primary ? t(primary.labelKey) : t('newproj.pickPlatform')}
{extraCount > 0 ? (
<span className="ds-picker-extra-pill">+{extraCount}</span>
) : null}
@ -1178,7 +1178,7 @@ function PlatformPicker({
className="ds-picker-popover"
id={listboxId}
role="listbox"
aria-label="Target platforms"
aria-label={t('newproj.targetPlatformsLabel')}
aria-multiselectable="true"
>
<div className="ds-picker-list">
@ -1922,6 +1922,7 @@ function TemplateOption({
name: string;
description: string;
}) {
const t = useT();
return (
<div className={`template-option${active ? ' active' : ''}`}>
<button
@ -1940,8 +1941,8 @@ function TemplateOption({
type="button"
className="template-option-delete"
onClick={(e) => { e.stopPropagation(); onDelete(); }}
title="Delete template"
aria-label={`Delete template ${name}`}
title={t('newproj.deleteTemplate')}
aria-label={t('newproj.deleteTemplateAria', { name })}
>
</button>

View file

@ -16,6 +16,12 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import type { InputFieldSpec } from '@open-design/contracts';
import { useI18n } from '../i18n';
import {
localizePluginDisplayValue,
localizePluginInputLabel,
localizePluginPlaceholder,
} from '../i18n/plugin-content';
interface Props {
fields: InputFieldSpec[];
@ -25,6 +31,7 @@ interface Props {
}
export function PluginInputsForm(props: Props) {
const { locale } = useI18n();
const fields = props.fields ?? [];
const onValidityChangeRef = useRef(props.onValidityChange);
const lastValidityRef = useRef<boolean | null>(null);
@ -90,10 +97,10 @@ export function PluginInputsForm(props: Props) {
data-filled={hasFieldValue(values[field.name]) ? 'true' : 'false'}
>
<span className="plugin-inputs-form__label">
{field.label ?? field.name}
{localizePluginInputLabel(locale, field)}
{field.required ? <span className="plugin-inputs-form__required">*</span> : null}
</span>
{renderField(field, values[field.name], (v) => update(field.name, v))}
{renderField(field, values[field.name], (v) => update(field.name, v), locale)}
</label>
))}
</div>
@ -104,6 +111,7 @@ function renderField(
field: InputFieldSpec,
value: unknown,
onChange: (value: unknown) => void,
locale: ReturnType<typeof useI18n>['locale'],
) {
const type = fieldType(field);
if (type === 'select' && Array.isArray(field.options)) {
@ -115,10 +123,10 @@ function renderField(
onChange={(e) => onChange(e.target.value)}
data-field-name={field.name}
>
<option value="">{field.placeholder ?? 'Select…'}</option>
<option value="">{localizePluginPlaceholder(locale, field.placeholder, 'Select…')}</option>
{field.options.map((opt) => (
<option key={opt} value={opt}>
{optionLabels[opt] ?? opt}
{localizePluginDisplayValue(locale, optionLabels[opt] ?? opt)}
</option>
))}
</select>
@ -130,7 +138,7 @@ function renderField(
type="number"
className="plugin-inputs-form__input"
value={value === undefined || value === null ? '' : String(value)}
placeholder={field.placeholder ?? ''}
placeholder={localizePluginPlaceholder(locale, field.placeholder)}
onChange={(e) => {
const raw = e.target.value;
if (raw === '') return onChange(undefined);
@ -167,7 +175,7 @@ function renderField(
{...(typeof field.accept === 'string' ? { accept: field.accept } : {})}
/>
<span className="plugin-inputs-form__file-label">
{fileValue ?? field.placeholder ?? 'Choose file…'}
{fileValue ?? localizePluginPlaceholder(locale, field.placeholder, 'Choose file…')}
</span>
</span>
);
@ -178,7 +186,7 @@ function renderField(
className="plugin-inputs-form__input plugin-inputs-form__input--textarea"
rows={3}
value={value === undefined || value === null ? '' : String(value)}
placeholder={field.placeholder ?? ''}
placeholder={localizePluginPlaceholder(locale, field.placeholder)}
onChange={(e) => onChange(e.target.value)}
data-field-name={field.name}
/>
@ -189,7 +197,7 @@ function renderField(
type="text"
className="plugin-inputs-form__input"
value={value === undefined || value === null ? '' : String(value)}
placeholder={field.placeholder ?? ''}
placeholder={localizePluginPlaceholder(locale, field.placeholder)}
onChange={(e) => onChange(e.target.value)}
data-field-name={field.name}
/>

View file

@ -7,10 +7,10 @@ import type {
import {
applyPlugin,
listPlugins,
renderPluginBriefTemplate,
resolvePluginQueryFallback,
} from '../state/projects';
import { useI18n } from '../i18n';
import { renderLocalizedPluginBriefTemplate } from '../i18n/plugin-content';
import { Icon } from './Icon';
import { PluginDetailsModal } from './PluginDetailsModal';
import { TrustBadge } from './TrustBadge';
@ -57,7 +57,7 @@ interface ActivePlugin {
}
export function PluginLoopHome({ onSubmit }: Props) {
const { locale } = useI18n();
const { locale, t } = useI18n();
const analytics = useAnalytics();
const [plugins, setPlugins] = useState<InstalledPluginRecord[]>([]);
const [loading, setLoading] = useState(true);
@ -99,7 +99,7 @@ export function PluginLoopHome({ onSubmit }: Props) {
const result = await applyPlugin(record.id, { locale });
setPendingApplyId(null);
if (!result) {
setError(`Failed to apply ${record.title}. Make sure the daemon is reachable.`);
setError(t('home.error.applyFailed', { title: record.title }));
return;
}
const inputs: Record<string, unknown> = {};
@ -109,7 +109,7 @@ export function PluginLoopHome({ onSubmit }: Props) {
setActive({ record, result, inputs });
const query = result.query || resolvePluginQueryFallback(record.manifest?.od?.useCase?.query, locale);
if (query) {
setPrompt(renderPluginBriefTemplate(query, inputs));
setPrompt(renderLocalizedPluginBriefTemplate(locale, query, inputs));
}
setDetailsRecord(null);
requestAnimationFrame(() => textareaRef.current?.focus());

View file

@ -140,11 +140,14 @@ export function PluginsHomeSection({
}
}
const sectionTitle = title ?? t('pluginsHome.title');
const resolvedEmptyMessage = emptyMessage ?? t('pluginsHome.emptyCatalog');
return (
<section className="plugins-home" data-testid="plugins-home-section">
<header className="plugins-home__head">
<div className="plugins-home__heading">
<h2 className="plugins-home__title">{title ?? t('pluginsHome.title')}</h2>
<h2 className="plugins-home__title">{sectionTitle}</h2>
{subtitle ? (
<p className="plugins-home__subtitle">{subtitle}</p>
) : null}
@ -167,14 +170,14 @@ export function PluginsHomeSection({
<div className="plugins-home__empty">{t('pluginsHome.loadingCatalog')}</div>
) : visiblePlugins.length === 0 ? (
<div className="plugins-home__empty">
{emptyMessage ?? t('pluginsHome.emptyCatalog')}
{resolvedEmptyMessage}
</div>
) : (
<>
<div
className="plugins-home__facets"
role="group"
aria-label="Plugin filters"
aria-label={t('pluginsHome.filtersAria')}
>
<CategoryRow
options={catalog.category}
@ -319,7 +322,7 @@ function CategoryRow({
<CategoryPill
key={opt.slug}
slug={opt.slug}
label={opt.label}
label={localizedFacetLabel(opt, t)}
count={opt.count}
active={selectedSlug === opt.slug}
onPick={onPick}
@ -366,7 +369,7 @@ function SubcategoryRow({ parent, options, selectedSlug, onPick }: SubcategoryRo
<CategoryPill
key={opt.slug}
slug={opt.slug}
label={opt.label}
label={localizedFacetLabel(opt, t)}
count={opt.count}
active={selectedSlug === opt.slug}
onPick={onPick}
@ -491,3 +494,7 @@ function SearchInput({ value, onChange }: SearchInputProps) {
</div>
);
}
function localizedFacetLabel(option: FacetOption, t: ReturnType<typeof useT>): string {
return pluginFacetLabel(option.slug, option.label, t);
}

View file

@ -38,6 +38,7 @@ import { PluginDetailsModal } from './PluginDetailsModal';
import { PluginsHomeSection } from './PluginsHomeSection';
import { TrustBadge } from './TrustBadge';
import { useI18n } from '../i18n';
import { zhCN } from '../i18n/inline';
import { copyToClipboard } from '../lib/copy-to-clipboard';
import type { PluginUseAction } from './plugins-home/useActions';
@ -63,34 +64,59 @@ const PLUGINS_TABS: ReadonlyArray<{
const PLUGIN_SHARE_DETAILS: Record<PluginShareAction, {
eyebrow: string;
eyebrowZh: string;
fallbackTitle: string;
fallbackTitleZh: string;
fallbackDescription: string;
fallbackDescriptionZh: string;
confirmLabel: string;
confirmLabelZh: string;
steps: string[];
stepsZh: string[];
}> = {
'publish-github': {
eyebrow: 'GitHub repository',
eyebrowZh: 'GitHub 仓库',
fallbackTitle: 'Publish Plugin to GitHub',
fallbackTitleZh: '发布插件到 GitHub',
fallbackDescription:
'Creates a public GitHub repository for this local Open Design plugin.',
fallbackDescriptionZh:
'为这个本地 Open Design 插件创建一个公开 GitHub 仓库。',
confirmLabel: 'Start publishing',
confirmLabelZh: '开始发布',
steps: [
'Create a new Open Design project for the publish workflow.',
'Copy this plugin into that project as isolated source context.',
'Run the official publish action plugin against the local daemon.',
],
stepsZh: [
'为发布流程创建一个新的 Open Design 项目。',
'把这个插件复制到该项目中,作为隔离的源码上下文。',
'通过本地守护进程运行官方发布动作插件。',
],
},
'contribute-open-design': {
eyebrow: 'Open Design pull request',
eyebrowZh: 'Open Design Pull Request',
fallbackTitle: 'Contribute Plugin to Open Design',
fallbackTitleZh: '向 Open Design 贡献插件',
fallbackDescription:
'Opens a pull request that adds this plugin to the Open Design community catalog.',
fallbackDescriptionZh:
'打开一个 Pull Request把这个插件加入 Open Design 社区目录。',
confirmLabel: 'Start contribution',
confirmLabelZh: '开始贡献',
steps: [
'Create a new Open Design project for the contribution workflow.',
'Copy this plugin into that project as isolated source context.',
'Run the official contribution action plugin against the local daemon.',
],
stepsZh: [
'为贡献流程创建一个新的 Open Design 项目。',
'把这个插件复制到该项目中,作为隔离的源码上下文。',
'通过本地守护进程运行官方贡献动作插件。',
],
},
};
@ -202,7 +228,7 @@ export function PluginsView({
if (!result) {
setNotice({
ok: false,
message: `Failed to apply ${record.title}. Make sure the daemon is reachable.`,
message: t('home.error.applyFailed', { title: record.title }),
});
return;
}
@ -210,7 +236,11 @@ export function PluginsView({
setDetailsRecord(null);
setNotice({
ok: true,
message: `${record.title} is ready. Use it from Home with @ search or pick it from the gallery.`,
message: zhCN(
locale,
`${record.title} is ready. Use it from Home with @ search or pick it from the gallery.`,
`${record.title} 已准备好。你可以在主页用 @ 搜索使用,也可以从插件库中选择。`,
),
});
}
@ -221,7 +251,11 @@ export function PluginsView({
if (!onCreatePluginShareProject) {
setNotice({
ok: false,
message: 'Plugin sharing is not available in this shell.',
message: zhCN(
locale,
'Plugin sharing is not available in this shell.',
'当前外壳不支持插件分享。',
),
});
setShareConfirm(null);
return;
@ -588,12 +622,14 @@ function PluginShareConfirmModal({
onClose: () => void;
onConfirm: () => void;
}) {
const { locale } = useI18n();
const details = PLUGIN_SHARE_DETAILS[action];
const actionTitle = actionRecord?.title ?? details.fallbackTitle;
const actionTitle = actionRecord?.title ?? zhCN(locale, details.fallbackTitle, details.fallbackTitleZh);
const actionDescription =
actionRecord?.manifest?.description ?? details.fallbackDescription;
actionRecord?.manifest?.description ?? zhCN(locale, details.fallbackDescription, details.fallbackDescriptionZh);
const actionQuery = readLocalizedUseCaseQuery(actionRecord);
const stagedPath = `plugin-source/${pluginShareSlug(sourceRecord.id)}`;
const steps = locale === 'zh-CN' ? details.stepsZh : details.steps;
return (
<div
@ -611,11 +647,13 @@ function PluginShareConfirmModal({
<div className="plugin-details-modal__head-titles">
<div className="plugin-details-modal__head-row">
<h2 className="plugin-details-modal__title">{actionTitle}</h2>
<TrustBadge trust="official" label="Action plugin" />
<TrustBadge trust="official" label={zhCN(locale, 'Action plugin', '动作插件')} />
</div>
<div className="plugin-details-modal__meta">
<span>{details.eyebrow}</span>
<span>· for {sourceRecord.title}</span>
<span>{zhCN(locale, details.eyebrow, details.eyebrowZh)}</span>
<span>
{zhCN(locale, `· for ${sourceRecord.title}`, `· 用于 ${sourceRecord.title}`)}
</span>
{actionRecord ? <span>· v{actionRecord.version}</span> : null}
</div>
</div>
@ -624,8 +662,8 @@ function PluginShareConfirmModal({
className="plugin-details-modal__close"
onClick={onClose}
disabled={pending}
aria-label="Close share confirmation"
title="Close"
aria-label={zhCN(locale, 'Close share confirmation', '关闭分享确认')}
title={zhCN(locale, 'Close', '关闭')}
>
<Icon name="close" size={18} />
</button>
@ -635,14 +673,14 @@ function PluginShareConfirmModal({
<section className="plugin-details-modal__section">
<div className="plugin-details-modal__section-head">
<h3 className="plugin-details-modal__section-title">
What this starts
{zhCN(locale, 'What this starts', '将会启动什么')}
</h3>
</div>
<p className="plugin-details-modal__description">
{actionDescription}
</p>
<ol className="plugin-share-confirm__steps">
{details.steps.map((step) => (
{steps.map((step) => (
<li key={step}>{step}</li>
))}
</ol>
@ -651,12 +689,12 @@ function PluginShareConfirmModal({
<section className="plugin-details-modal__section">
<div className="plugin-details-modal__section-head">
<h3 className="plugin-details-modal__section-title">
Source plugin
{zhCN(locale, 'Source plugin', '源插件')}
</h3>
</div>
<dl className="plugin-share-confirm__facts">
<div>
<dt>Plugin</dt>
<dt>{zhCN(locale, 'Plugin', '插件')}</dt>
<dd>{sourceRecord.title}</dd>
</div>
<div>
@ -666,13 +704,13 @@ function PluginShareConfirmModal({
</dd>
</div>
<div>
<dt>Copied to</dt>
<dt>{zhCN(locale, 'Copied to', '复制到')}</dt>
<dd>
<code>{stagedPath}</code>
</dd>
</div>
<div>
<dt>Trust</dt>
<dt>{zhCN(locale, 'Trust', '可信级别')}</dt>
<dd>
<TrustBadge trust={sourceRecord.trust} />
</dd>
@ -684,7 +722,7 @@ function PluginShareConfirmModal({
<section className="plugin-details-modal__section">
<div className="plugin-details-modal__section-head">
<h3 className="plugin-details-modal__section-title">
Action prompt
{zhCN(locale, 'Action prompt', '动作提示词')}
</h3>
</div>
<pre className="plugin-details-modal__query">{actionQuery}</pre>
@ -699,7 +737,7 @@ function PluginShareConfirmModal({
onClick={onClose}
disabled={pending}
>
Cancel
{zhCN(locale, 'Cancel', '取消')}
</button>
<button
type="button"
@ -709,7 +747,9 @@ function PluginShareConfirmModal({
aria-busy={pending ? 'true' : undefined}
data-testid="plugin-share-confirm-start"
>
{pending ? 'Starting…' : details.confirmLabel}
{pending
? zhCN(locale, 'Starting…', '启动中…')
: zhCN(locale, details.confirmLabel, details.confirmLabelZh)}
</button>
</footer>
</div>
@ -767,6 +807,7 @@ function Notice({
}: {
outcome: PluginInstallOutcome | { ok: boolean; message: string };
}) {
const { locale } = useI18n();
const warnings = 'warnings' in outcome ? outcome.warnings : [];
const log = 'log' in outcome ? outcome.log : [];
return (
@ -774,12 +815,16 @@ function Notice({
<div>{outcome.message}</div>
{warnings.length > 0 ? (
<div className="plugins-view__notice-sub">
{warnings.length} warning{warnings.length === 1 ? '' : 's'}
{zhCN(
locale,
`${warnings.length} warning${warnings.length === 1 ? '' : 's'}`,
`${warnings.length} 条警告`,
)}
</div>
) : null}
{log.length > 0 ? (
<details className="plugins-view__notice-log">
<summary>Install log</summary>
<summary>{zhCN(locale, 'Install log', '安装日志')}</summary>
<ul>
{log.map((line, idx) => (
<li key={`${line}-${idx}`}>{line}</li>
@ -833,6 +878,7 @@ function AvailablePluginsPanel({
onSourceDropdown?: () => void;
t: ReturnType<typeof useI18n>['t'];
}) {
const { locale } = useI18n();
const [query, setQuery] = useState('');
const [sourceFilter, setSourceFilter] = useState('all');
const searchTrackedRef = useRef(false);
@ -854,7 +900,11 @@ function AvailablePluginsPanel({
<span className="plugins-view__section-count">
{filteredPlugins.length === plugins.length
? plugins.length
: `${filteredPlugins.length} of ${plugins.length}`}
: zhCN(
locale,
`${filteredPlugins.length} of ${plugins.length}`,
`${filteredPlugins.length} / ${plugins.length}`,
)}
</span>
</div>
{plugins.length > 0 ? (
@ -978,7 +1028,7 @@ function AvailablePluginDetailsModal({
onClose: () => void;
onInstall: (plugin: AvailableMarketplacePlugin) => void;
}) {
const { t } = useI18n();
const { locale, t } = useI18n();
const versions = useMemo(() => availablePluginVersions(plugin.entry), [plugin.entry]);
const [selectedVersion, setSelectedVersion] = useState(
() => versions[0]?.version ?? plugin.entry.version ?? 'latest',
@ -1057,8 +1107,8 @@ function AvailablePluginDetailsModal({
className="plugin-details-modal__close"
onClick={onClose}
disabled={pending}
aria-label="Close available plugin details"
title="Close"
aria-label={zhCN(locale, 'Close available plugin details', '关闭可用插件详情')}
title={zhCN(locale, 'Close', '关闭')}
>
<Icon name="close" size={18} />
</button>
@ -1081,10 +1131,13 @@ function AvailablePluginDetailsModal({
<section className="plugin-details-modal__section">
<div className="plugin-details-modal__section-head">
<h3 className="plugin-details-modal__section-title">About</h3>
<h3 className="plugin-details-modal__section-title">
{zhCN(locale, 'About', '关于')}
</h3>
</div>
<p className="plugin-details-modal__description">
{plugin.entry.description ?? 'No description provided.'}
{plugin.entry.description
?? zhCN(locale, 'No description provided.', '暂无描述。')}
</p>
</section>
@ -1160,11 +1213,13 @@ function AvailablePluginDetailsModal({
<section className="plugin-details-modal__section">
<div className="plugin-details-modal__section-head">
<h3 className="plugin-details-modal__section-title">Catalog</h3>
<h3 className="plugin-details-modal__section-title">
{zhCN(locale, 'Catalog', '目录')}
</h3>
</div>
<dl className="plugin-details-modal__source">
<div>
<dt>Source</dt>
<dt>{zhCN(locale, 'Source', '来源')}</dt>
<dd>
<code>{selectedVersionInfo?.source ?? plugin.entry.source}</code>
</dd>
@ -1186,11 +1241,11 @@ function AvailablePluginDetailsModal({
</div>
) : null}
<div>
<dt>Catalog</dt>
<dt>{zhCN(locale, 'Catalog', '目录')}</dt>
<dd>{sourceName}</dd>
</div>
<div>
<dt>Catalog URL</dt>
<dt>{zhCN(locale, 'Catalog URL', '目录 URL')}</dt>
<dd>
<a href={plugin.marketplace.url} target="_blank" rel="noreferrer">
{plugin.marketplace.url}
@ -1199,13 +1254,13 @@ function AvailablePluginDetailsModal({
</div>
{plugin.entry.license ? (
<div>
<dt>License</dt>
<dt>{zhCN(locale, 'License', '许可证')}</dt>
<dd>{plugin.entry.license}</dd>
</div>
) : null}
{publisherLabel ? (
<div>
<dt>Publisher</dt>
<dt>{zhCN(locale, 'Publisher', '发布者')}</dt>
<dd>
{publisher?.url ? (
<a href={publisher.url} target="_blank" rel="noreferrer">
@ -1219,7 +1274,7 @@ function AvailablePluginDetailsModal({
) : null}
{plugin.entry.homepage ? (
<div>
<dt>Homepage</dt>
<dt>{zhCN(locale, 'Homepage', '主页')}</dt>
<dd>
<a href={plugin.entry.homepage} target="_blank" rel="noreferrer">
{plugin.entry.homepage}
@ -1233,7 +1288,9 @@ function AvailablePluginDetailsModal({
{permissions.length > 0 || tags.length > 0 || capabilitySummary.length > 0 ? (
<section className="plugin-details-modal__section">
<div className="plugin-details-modal__section-head">
<h3 className="plugin-details-modal__section-title">Metadata</h3>
<h3 className="plugin-details-modal__section-title">
{zhCN(locale, 'Metadata', '元数据')}
</h3>
</div>
<div className="plugin-details-modal__context">
{permissions.length > 0 ? (
@ -1255,7 +1312,9 @@ function AvailablePluginDetailsModal({
) : null}
{tags.length > 0 ? (
<div className="plugin-details-modal__ctx-group">
<div className="plugin-details-modal__ctx-label">Tags</div>
<div className="plugin-details-modal__ctx-label">
{zhCN(locale, 'Tags', '标签')}
</div>
<div className="plugin-details-modal__chips">
{tags.map((tag) => (
<span key={tag} className="plugin-details-modal__chip">
@ -1294,7 +1353,7 @@ function AvailablePluginDetailsModal({
onClick={onClose}
disabled={pending}
>
Close
{zhCN(locale, 'Close', '关闭')}
</button>
<button
type="button"
@ -1304,7 +1363,9 @@ function AvailablePluginDetailsModal({
aria-busy={pending ? 'true' : undefined}
data-testid={`plugins-available-details-install-${plugin.entry.name}`}
>
{pending ? 'Installing...' : 'Install'}
{pending
? zhCN(locale, 'Installing...', '安装中...')
: zhCN(locale, 'Install', '安装')}
</button>
</footer>
</div>
@ -1331,6 +1392,7 @@ function SourcesPanel({
onTrust: (marketplace: PluginMarketplace, trust: PluginMarketplaceTrust) => void;
t: ReturnType<typeof useI18n>['t'];
}) {
const { locale } = useI18n();
const [url, setUrl] = useState('');
const [trust, setTrust] = useState<PluginMarketplaceTrust>('restricted');
const trimmedUrl = url.trim();
@ -1458,6 +1520,7 @@ function PluginImportModal({
onUploadZip: (file: File) => Promise<PluginInstallOutcome>;
onUploadFolder: (files: File[]) => Promise<PluginInstallOutcome>;
}) {
const { locale } = useI18n();
const [kind, setKind] = useState<ImportKind>('github');
const [source, setSource] = useState('');
const [zipFile, setZipFile] = useState<File | null>(null);
@ -1496,39 +1559,46 @@ function PluginImportModal({
>
<header className="plugins-import-modal__head">
<div>
<p className="plugins-view__kicker">User plugins</p>
<h2 id="plugins-import-title">Import a plugin</h2>
<p className="plugins-view__kicker">
{zhCN(locale, 'User plugins', '用户插件')}
</p>
<h2 id="plugins-import-title">
{zhCN(locale, 'Import a plugin', '导入插件')}
</h2>
</div>
<button
type="button"
className="plugins-import-modal__close"
onClick={onClose}
aria-label="Close import dialog"
aria-label={zhCN(locale, 'Close import dialog', '关闭导入弹窗')}
>
<Icon name="close" size={16} />
</button>
</header>
<nav className="plugins-import-modal__tabs" aria-label="Import source">
<nav
className="plugins-import-modal__tabs"
aria-label={zhCN(locale, 'Import source', '导入来源')}
>
<ImportChoice
active={kind === 'github'}
icon="github"
title="From GitHub"
body="Install github:owner/repo paths."
title={zhCN(locale, 'From GitHub', '从 GitHub')}
body={zhCN(locale, 'Install github:owner/repo paths.', '安装 github:owner/repo 路径。')}
onClick={() => setKind('github')}
/>
<ImportChoice
active={kind === 'zip'}
icon="upload"
title="Upload zip"
body="Upload a plugin archive."
title={zhCN(locale, 'Upload zip', '上传 zip')}
body={zhCN(locale, 'Upload a plugin archive.', '上传插件压缩包。')}
onClick={() => setKind('zip')}
/>
<ImportChoice
active={kind === 'folder'}
icon="folder"
title="Upload folder"
body="Upload a plugin directory."
title={zhCN(locale, 'Upload folder', '上传文件夹')}
body={zhCN(locale, 'Upload a plugin directory.', '上传插件目录。')}
onClick={() => setKind('folder')}
/>
</nav>
@ -1536,7 +1606,9 @@ function PluginImportModal({
<div className="plugins-import-modal__body">
{kind === 'github' ? (
<div className="plugins-view__install-card">
<label htmlFor="plugin-source">GitHub, archive, or marketplace source</label>
<label htmlFor="plugin-source">
{zhCN(locale, 'GitHub, archive, or marketplace source', 'GitHub、压缩包或插件市场来源')}
</label>
<div className="plugins-view__source-row">
<input
id="plugin-source"
@ -1551,23 +1623,31 @@ function PluginImportModal({
onClick={runImport}
disabled={working || !canSubmit}
>
{working ? 'Importing…' : 'Import'}
{working
? zhCN(locale, 'Importing…', '导入中…')
: zhCN(locale, 'Import', '导入')}
</button>
</div>
<div className="plugins-view__source-help">
Supports <code>github:owner/repo[@ref][/subpath]</code>, HTTPS{' '}
<code>.tar.gz</code>/<code>.tgz</code> archives, or marketplace plugin names.
{zhCN(locale, 'Supports', '支持')}{' '}
<code>github:owner/repo[@ref][/subpath]</code>, HTTPS{' '}
<code>.tar.gz</code>/<code>.tgz</code>{' '}
{zhCN(locale, 'archives, or marketplace plugin names.', '压缩包,或插件市场中的插件名。')}
</div>
</div>
) : null}
{kind === 'zip' ? (
<FileImportPanel
title="Upload zip"
body="Choose a .zip archive containing open-design.json, SKILL.md, or .claude-plugin/plugin.json."
title={zhCN(locale, 'Upload zip', '上传 zip')}
body={zhCN(
locale,
'Choose a .zip archive containing open-design.json, SKILL.md, or .claude-plugin/plugin.json.',
'选择一个包含 open-design.json、SKILL.md 或 .claude-plugin/plugin.json 的 .zip 压缩包。',
)}
accept=".zip,application/zip"
working={working}
fileLabel={zipFile?.name ?? 'No zip selected'}
fileLabel={zipFile?.name ?? zhCN(locale, 'No zip selected', '尚未选择 zip')}
onChange={(files) => setZipFile(files[0] ?? null)}
onImport={runImport}
canSubmit={canSubmit}
@ -1576,13 +1656,21 @@ function PluginImportModal({
{kind === 'folder' ? (
<FileImportPanel
title="Upload folder"
body="Choose a plugin folder. Relative paths are preserved and installed into your user plugin registry."
title={zhCN(locale, 'Upload folder', '上传文件夹')}
body={zhCN(
locale,
'Choose a plugin folder. Relative paths are preserved and installed into your user plugin registry.',
'选择一个插件文件夹。相对路径会保留,并安装到你的用户插件注册表中。',
)}
working={working}
fileLabel={
folderFiles.length > 0
? `${folderFiles.length} file${folderFiles.length === 1 ? '' : 's'} selected`
: 'No folder selected'
? zhCN(
locale,
`${folderFiles.length} file${folderFiles.length === 1 ? '' : 's'} selected`,
`已选择 ${folderFiles.length} 个文件`,
)
: zhCN(locale, 'No folder selected', '尚未选择文件夹')
}
folder
onChange={setFolderFiles}
@ -1595,15 +1683,18 @@ function PluginImportModal({
<footer className="plugins-import-modal__foot">
<p>
Imported plugins are user plugins and are stored separately from
bundled official plugins.
{zhCN(
locale,
'Imported plugins are user plugins and are stored separately from bundled official plugins.',
'导入的插件会作为用户插件保存,并与内置官方插件分开存放。',
)}
</p>
<button
type="button"
className="plugins-view__secondary"
onClick={onClose}
>
Cancel
{zhCN(locale, 'Cancel', '取消')}
</button>
</footer>
</section>
@ -1624,6 +1715,7 @@ function ImportChoice({
body: string;
onClick: () => void;
}) {
const { locale } = useI18n();
return (
<button
type="button"
@ -1662,6 +1754,7 @@ function FileImportPanel({
onChange: (files: File[]) => void;
onImport: () => void;
}) {
const { locale } = useI18n();
return (
<section className="plugins-view__install-card">
<div>
@ -1686,7 +1779,9 @@ function FileImportPanel({
onClick={onImport}
disabled={working || !canSubmit}
>
{working ? 'Importing…' : 'Import'}
{working
? zhCN(locale, 'Importing…', '导入中…')
: zhCN(locale, 'Import', '导入')}
</button>
</section>
);

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,8 @@ import type {
MarketplaceTrust,
TrustTier,
} from '@open-design/contracts';
import { useT } from '../i18n';
import type { Dict } from '../i18n/types';
type TrustBadgeTrust = TrustTier | MarketplaceTrust;
type NormalizedTrustTier = 'official' | 'trusted' | 'restricted';
@ -15,19 +17,19 @@ interface Props {
const TRUST_META: Record<
NormalizedTrustTier,
{ label: string; description: string }
{ labelKey: keyof Dict; descriptionKey: keyof Dict }
> = {
official: {
label: 'Official',
description: 'Open Design official',
labelKey: 'trust.official',
descriptionKey: 'trust.officialDescription',
},
trusted: {
label: 'Trusted',
description: 'Community trusted',
labelKey: 'trust.trusted',
descriptionKey: 'trust.trustedDescription',
},
restricted: {
label: 'Restricted',
description: 'Restricted source',
labelKey: 'trust.restricted',
descriptionKey: 'trust.restrictedDescription',
},
};
@ -37,9 +39,11 @@ export function TrustBadge({
className,
variant = 'default',
}: Props) {
const t = useT();
const tier = normalizeTrustTier(trust);
const meta = TRUST_META[tier];
const text = label ?? meta.label;
const description = t(meta.descriptionKey);
const text = label ?? t(meta.labelKey);
const classes = [
'plugin-trust-badge',
`plugin-trust-badge--${tier}`,
@ -54,8 +58,8 @@ export function TrustBadge({
className={classes}
data-trust-tier={tier}
data-trust-source={trust}
title={meta.description}
aria-label={`${meta.description}: ${text}`}
title={description}
aria-label={`${description}: ${text}`}
>
<span className="plugin-trust-badge__dot" aria-hidden />
<span>{text}</span>

View file

@ -539,12 +539,12 @@ export function WorkspaceTabsBar({ route, projects }: Props) {
}
return (
<header className="app-chrome-header workspace-tabs-chrome" aria-label="Workspace tabs">
<header className="app-chrome-header workspace-tabs-chrome" aria-label={t('entry.workspaceTabsAria')}>
<div className="app-chrome-traffic-space workspace-tabs-traffic" aria-hidden />
<div
className="workspace-tabs-strip"
role="tablist"
aria-label="Open workspaces"
aria-label={t('entry.openWorkspacesAria')}
ref={stripRef}
>
{/* Render every open tab the strip itself scrolls horizontally
@ -596,8 +596,8 @@ export function WorkspaceTabsBar({ route, projects }: Props) {
type="button"
className="workspace-tabs-new-btn"
onClick={createNewTab}
title="New tab"
aria-label="New tab"
title={t('entry.newTab')}
aria-label={t('entry.newTab')}
>
<Icon name="plus" size={14} />
</button>
@ -605,8 +605,8 @@ export function WorkspaceTabsBar({ route, projects }: Props) {
type="button"
className={`workspace-tabs-icon-btn${tabsMenuOpen ? ' is-active' : ''}`}
onClick={() => setTabsMenuOpen((open) => !open)}
title="Search tabs"
aria-label="Search tabs"
title={t('entry.searchTabs')}
aria-label={t('entry.searchTabs')}
aria-haspopup="dialog"
aria-expanded={tabsMenuOpen}
>
@ -617,7 +617,7 @@ export function WorkspaceTabsBar({ route, projects }: Props) {
<div
className="workspace-tabs-popover"
role="dialog"
aria-label="Search tabs"
aria-label={t('entry.searchTabs')}
ref={popoverRef}
>
<div className="workspace-tabs-search">
@ -626,15 +626,15 @@ export function WorkspaceTabsBar({ route, projects }: Props) {
ref={searchInputRef}
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search tabs"
aria-label="Search tabs"
placeholder={t('entry.searchTabs')}
aria-label={t('entry.searchTabs')}
/>
</div>
<div className="workspace-tabs-popover__section">
<span>Open tabs</span>
<span>{t('entry.openTabs')}</span>
<span>{state.tabs.length}</span>
</div>
<div className="workspace-tabs-list" role="listbox" aria-label="Open tabs">
<div className="workspace-tabs-list" role="listbox" aria-label={t('entry.openTabs')}>
{filteredTabs.length > 0 ? (
filteredTabs.map((display) => {
const active = display.id === state.activeTabId;
@ -671,7 +671,7 @@ export function WorkspaceTabsBar({ route, projects }: Props) {
);
})
) : (
<div className="workspace-tabs-empty">No tabs found</div>
<div className="workspace-tabs-empty">{t('entry.noTabsFound')}</div>
)}
</div>
</div>,
@ -785,7 +785,7 @@ function displayTabFor(
return {
id: tab.id,
title: entryTitle[tab.view],
meta: tab.view === 'home' ? 'Start a new project' : 'Workspace',
meta: tab.view === 'home' ? t('entry.metaStartProject') : t('entry.metaWorkspace'),
icon: entryIcon[tab.view],
tab,
};

View file

@ -11,16 +11,15 @@
//
// The catalog stays a pure data table:
// - `id` — stable React key + test selector.
// - `label` — English copy. Localisation can layer on later by
// swapping this for a Dict lookup; keeping it inline lets the
// rail ship without burning through 17 locale files for two
// new strings (see plan §B / open questions).
// - `label` — English fallback for tests and non-UI consumers.
// - `labelKey` — localized UI label used by the Home rail.
// - `icon` — name from the shared Icon registry.
// - `action` — discriminated union the HomeView dispatcher matches
// on. The rail component itself stays presentational.
import type { ProjectKind, ProjectMetadata } from '@open-design/contracts';
import type { DefaultScenarioPluginId } from '@open-design/contracts';
import type { Dict } from '../../i18n/types';
import type { IconName } from '../Icon';
// Plugin ids the chip rail can dispatch to. Most chips route to a
@ -33,7 +32,8 @@ import type { IconName } from '../Icon';
// independently of the default-binding mapping.
export type ChipScenarioPluginId =
| DefaultScenarioPluginId
| 'example-hyperframes';
| 'example-hyperframes'
| 'example-live-artifact';
export type ChipAction =
| {
@ -51,6 +51,7 @@ export type ChipAction =
projectMetadata?: ProjectMetadata;
}
| { kind: 'create-plugin' }
| { kind: 'import-folder' }
| { kind: 'open-template-picker' };
// Two intent groups: "create" = produce a design artifact, "migrate" =
@ -63,9 +64,11 @@ export type ChipGroup = 'create' | 'migrate';
export interface HomeHeroChip {
id: string;
label: string;
labelKey: keyof Dict;
icon: IconName;
group: ChipGroup;
hint?: string;
hintKey?: keyof Dict;
action: ChipAction;
}
@ -73,6 +76,7 @@ export const HOME_HERO_CHIPS: ReadonlyArray<HomeHeroChip> = [
{
id: 'prototype',
label: 'Prototype',
labelKey: 'homeHero.chip.prototype',
icon: 'palette',
group: 'create',
// Prototype now binds to the bundled `example-web-prototype` plugin,
@ -93,9 +97,11 @@ export const HOME_HERO_CHIPS: ReadonlyArray<HomeHeroChip> = [
{
id: 'live-artifact',
label: 'Live artifact',
labelKey: 'homeHero.chip.liveArtifact',
icon: 'refresh',
group: 'create',
hint: 'Build a refreshable artifact backed by connector or local data.',
hintKey: 'homeHero.chip.liveArtifactHint',
action: {
kind: 'apply-scenario',
pluginId: 'example-live-artifact',
@ -110,6 +116,7 @@ export const HOME_HERO_CHIPS: ReadonlyArray<HomeHeroChip> = [
{
id: 'deck',
label: 'Slide deck',
labelKey: 'homeHero.chip.deck',
icon: 'present',
group: 'create',
// Slide deck binds to `example-simple-deck`, which ships a 353-line
@ -131,6 +138,7 @@ export const HOME_HERO_CHIPS: ReadonlyArray<HomeHeroChip> = [
{
id: 'image',
label: 'Image',
labelKey: 'homeHero.chip.image',
icon: 'image',
group: 'create',
action: {
@ -148,6 +156,7 @@ export const HOME_HERO_CHIPS: ReadonlyArray<HomeHeroChip> = [
{
id: 'video',
label: 'Video',
labelKey: 'homeHero.chip.video',
icon: 'play',
group: 'create',
action: {
@ -165,9 +174,11 @@ export const HOME_HERO_CHIPS: ReadonlyArray<HomeHeroChip> = [
{
id: 'hyperframes',
label: 'HyperFrames',
labelKey: 'homeHero.chip.hyperframes',
icon: 'orbit',
group: 'create',
hint: 'Author HTML-based motion: captions, audio-reactive visuals, scene transitions.',
hintKey: 'homeHero.chip.hyperframesHint',
// HyperFrames is its own bundled scenario (motion-graphics
// specialisation of Video). It surfaces in PluginsHomeSection's
// primary category list, so the rail picks it up too rather than
@ -177,6 +188,7 @@ export const HOME_HERO_CHIPS: ReadonlyArray<HomeHeroChip> = [
{
id: 'audio',
label: 'Audio',
labelKey: 'homeHero.chip.audio',
icon: 'mic',
group: 'create',
action: {
@ -194,17 +206,21 @@ export const HOME_HERO_CHIPS: ReadonlyArray<HomeHeroChip> = [
{
id: 'create-plugin',
label: 'Create plugin',
labelKey: 'homeHero.chip.createPlugin',
icon: 'edit',
group: 'migrate',
hint: 'Author a reusable Open Design plugin and add it to My plugins.',
hintKey: 'homeHero.chip.createPluginHint',
action: { kind: 'create-plugin' },
},
{
id: 'figma',
label: 'From Figma',
labelKey: 'homeHero.chip.figma',
icon: 'import',
group: 'migrate',
hint: 'Migrate a Figma frame into the active design system.',
hintKey: 'homeHero.chip.figmaHint',
action: {
kind: 'apply-figma-migration',
pluginId: 'od-figma-migration',
@ -215,12 +231,24 @@ export const HOME_HERO_CHIPS: ReadonlyArray<HomeHeroChip> = [
},
},
},
{
id: 'folder',
label: 'From folder',
labelKey: 'homeHero.chip.folder',
icon: 'folder',
group: 'migrate',
hint: 'Import an existing local folder and continue editing.',
hintKey: 'homeHero.chip.folderHint',
action: { kind: 'import-folder' },
},
{
id: 'template',
label: 'From template',
labelKey: 'homeHero.chip.template',
icon: 'file-code',
group: 'migrate',
hint: 'Start from a bundled template.',
hintKey: 'homeHero.chip.templateHint',
action: { kind: 'open-template-picker' },
},
];

View file

@ -365,6 +365,34 @@ const DE_DESIGN_SYSTEM_CATEGORIES: Record<string, string> = {
Uncategorized: 'Nicht kategorisiert',
};
const ZH_CN_DESIGN_SYSTEM_SUMMARIES: Record<string, string> = {};
const ZH_CN_DESIGN_SYSTEM_CATEGORIES: Record<string, string> = {
Starter: '入门',
'AI & LLM': 'AI 与大模型',
'Bold & Expressive': '大胆表现',
'Creative & Artistic': '创意与艺术',
'Developer Tools': '开发者工具',
'Layout & Structure': '布局与结构',
'Modern & Minimal': '现代极简',
'Morphism & Effects': '拟态与特效',
'Productivity & SaaS': '效率与 SaaS',
'Professional & Corporate': '专业与企业',
'Backend & Data': '后端与数据',
'Design & Creative': '设计与创意',
'Fintech & Crypto': '金融科技与加密',
'E-Commerce & Retail': '电商与零售',
'Media & Consumer': '媒体与消费',
'Social & Messaging': '社交与消息',
Automotive: '汽车',
'Editorial & Print': '编辑与印刷',
'Editorial · Studio': '编辑 · 工作室',
'Retro & Nostalgic': '复古怀旧',
'Themed & Unique': '主题与特色',
'Editorial / Personal / Publication': '编辑 / 个人 / 出版',
Uncategorized: '未分类',
};
const DE_PROMPT_TEMPLATE_CATEGORIES: Record<string, string> = {
Infographic: 'Infografik',
'Anime / Manga': 'Anime / Manga',
@ -957,6 +985,14 @@ const DE_PROMPT_TEMPLATE_COPY: Record<string, LocalizedPromptTemplateCopy> = {
};
const LOCALIZED_CONTENT: Partial<Record<Locale, LocalizedContentBundle>> = {
'zh-CN': {
skillCopy: {},
designSystemSummaries: ZH_CN_DESIGN_SYSTEM_SUMMARIES,
designSystemCategories: ZH_CN_DESIGN_SYSTEM_CATEGORIES,
promptTemplateCategories: {},
promptTemplateTags: {},
promptTemplateCopy: {},
},
de: {
skillCopy: DE_SKILL_COPY,
designSystemSummaries: DE_DESIGN_SYSTEM_SUMMARIES,
@ -995,11 +1031,13 @@ function buildLocalizedContentIds(content: LocalizedContentBundle): LocalizedCon
}
export const LOCALIZED_CONTENT_IDS = {
'zh-CN': buildLocalizedContentIds(LOCALIZED_CONTENT['zh-CN']!),
de: buildLocalizedContentIds(LOCALIZED_CONTENT.de!),
ru: buildLocalizedContentIds(LOCALIZED_CONTENT.ru!),
fr: buildLocalizedContentIds(LOCALIZED_CONTENT.fr!),
} satisfies Record<'de' | 'ru' | 'fr', LocalizedContentIds>;
} satisfies Record<'zh-CN' | 'de' | 'ru' | 'fr', LocalizedContentIds>;
export const SIMPLIFIED_CHINESE_CONTENT_IDS = LOCALIZED_CONTENT_IDS['zh-CN'];
export const GERMAN_CONTENT_IDS = LOCALIZED_CONTENT_IDS.de;
export const RUSSIAN_CONTENT_IDS = LOCALIZED_CONTENT_IDS.ru;
export const FRENCH_CONTENT_IDS = LOCALIZED_CONTENT_IDS.fr;

View file

@ -0,0 +1,5 @@
import type { Locale } from './types';
export function zhCN(locale: Locale, english: string, simplifiedChinese: string): string {
return locale === 'zh-CN' ? simplifiedChinese : english;
}

View file

@ -409,6 +409,8 @@ export const ar: Dict = {
'entry.helpSubmitFeature': 'اقترح ميزة',
'entry.helpWhatsNew': 'الجديد',
'entry.helpDownloadDesktop': 'تنزيل تطبيق سطح المكتب',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'نجمة',
'entry.githubStarTitle': 'انقر لمنحنا نجمة على GitHub',
'entry.githubStarAria': 'منح Open Design نجمة على GitHub',
@ -733,6 +735,103 @@ export const ar: Dict = {
'examples.tagAudio': 'صوت',
'examples.previewLabel': 'معاينة',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'السطح',
'ds.surfaceWeb': 'ويب',
'ds.surfaceImage': 'صورة',
@ -1831,4 +1930,99 @@ export const ar: Dict = {
'generationPreview.awaitingLead': 'أجب عن بعض الأسئلة في المحادثة للمتابعة.',
'generationPreview.stoppedTitle': 'تم إيقاف الإنشاء مؤقتًا',
'generationPreview.stoppedLead': 'تابع الخطوات المتبقية من المحادثة على اليسار.',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -408,6 +408,8 @@ export const de: Dict = {
'entry.helpSubmitFeature': 'Feature vorschlagen',
'entry.helpWhatsNew': 'Neuigkeiten',
'entry.helpDownloadDesktop': 'Desktop-App herunterladen',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': 'Klicken, um uns auf GitHub einen Stern zu geben',
'entry.githubStarAria': 'Open Design auf GitHub einen Stern geben',
@ -621,6 +623,103 @@ export const de: Dict = {
'examples.tagAudio': 'Audio',
'examples.previewLabel': 'Vorschau',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'Oberfläche',
'ds.surfaceWeb': 'Web',
'ds.surfaceImage': 'Bild',
@ -1768,4 +1867,99 @@ export const de: Dict = {
'generationPreview.awaitingLead': 'Beantworte ein paar kurze Fragen im Chat, um fortzufahren.',
'generationPreview.stoppedTitle': 'Generierung pausiert',
'generationPreview.stoppedLead': 'Setze die verbleibenden Schritte im Chat links fort.',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -479,6 +479,8 @@ export const en: Dict = {
'homeHero.placeholderActive': 'Edit the example query or write your own…',
'homeHero.skills': 'Skills',
'homeHero.applying': 'Applying…',
'homeHero.official': 'Official',
'homeHero.myPlugin': 'My plugin',
'homeHero.pluginTitle': 'Plugin: {title}',
'homeHero.pluginPrefix': 'Plugin: {title}',
'homeHero.skillPrefix': 'Skill: {title}',
@ -973,6 +975,8 @@ export const en: Dict = {
'entry.helpSubmitFeature': 'Submit a feature request',
'entry.helpWhatsNew': 'What\u2019s new',
'entry.helpDownloadDesktop': 'Download desktop app',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': 'Click to star us on GitHub',
'entry.githubStarAria': 'Star Open Design on GitHub',
@ -1335,6 +1339,103 @@ export const en: Dict = {
'examples.tagAudio': 'Audio',
'examples.previewLabel': 'Preview',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Generate',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'Surface',
'ds.surfaceWeb': 'Web',
'ds.surfaceImage': 'Image',
@ -1467,8 +1568,20 @@ export const en: Dict = {
'chat.inspect.noCommentTargets': 'No commentable text or visual targets found.',
'chat.inspect.editHint': 'Select a text or style target in the preview to edit it.',
'chat.inspect.commentHint': 'Select text or an area in the preview to comment on it.',
'chat.composerPlaceholder': "Describe what you want to generate…",
'chat.composerHint': "⌘/Ctrl + Enter to send · include goals, content, style, and format",
'chat.composerPlaceholder':
'Describe the design you want — paste images, @ files or skills, or / for commands…',
'chat.composerHint':
'⌘/Ctrl + Enter to send · paste images · @ files or skills · / for commands',
'chat.mention.surfacesAria': 'Mention surfaces',
'chat.mention.all': 'All',
'chat.mention.plugins': 'Plugins',
'chat.mention.skills': 'Skills',
'chat.mention.mcp': 'MCP',
'chat.mention.connectors': 'Connectors',
'chat.mention.files': 'Design files',
'chat.mention.noResults': 'No results for “{query}”.',
'chat.mention.emptyHint': 'Search plugins, skills, MCP servers, connectors, and Design Files.',
'chat.mention.useConnector': 'Use {name}',
'chat.cliSettingsTitle': 'CLI & model settings',
'chat.cliSettingsAria': 'Open CLI and model settings',
'chat.attachTitle': 'Attach files (or paste / drop)',
@ -2603,4 +2716,99 @@ export const en: Dict = {
'diagnostics.exporting': 'Exporting…',
'diagnostics.exportSuccess': 'Saved diagnostics to {path}',
'diagnostics.exportFailed': 'Could not export diagnostics: {message}',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -408,6 +408,8 @@ export const esES: Dict = {
'entry.helpSubmitFeature': 'Enviar una sugerencia',
'entry.helpWhatsNew': 'Novedades',
'entry.helpDownloadDesktop': 'Descargar app de escritorio',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': 'Haz clic para darnos una estrella en GitHub',
'entry.githubStarAria': 'Dar una estrella a Open Design en GitHub',
@ -622,6 +624,103 @@ export const esES: Dict = {
'examples.tagAudio': 'Audio',
'examples.previewLabel': 'Vista previa',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'Superficie',
'ds.surfaceWeb': 'Web',
'ds.surfaceImage': 'Imagen',
@ -1719,4 +1818,99 @@ export const esES: Dict = {
'generationPreview.awaitingLead': 'Responde unas preguntas en el chat para continuar.',
'generationPreview.stoppedTitle': 'Generación en pausa',
'generationPreview.stoppedLead': 'Reanuda los pasos restantes desde el chat de la izquierda.',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -411,6 +411,8 @@ export const fa: Dict = {
'entry.helpSubmitFeature': 'پیشنهاد قابلیت',
'entry.helpWhatsNew': 'تازه‌ها',
'entry.helpDownloadDesktop': 'دانلود اپلیکیشن دسکتاپ',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'ستاره',
'entry.githubStarTitle': 'برای ما در GitHub ستاره بگذارید',
'entry.githubStarAria': 'به Open Design در GitHub ستاره بدهید',
@ -755,6 +757,103 @@ export const fa: Dict = {
'examples.tagAudio': 'صدا',
'examples.previewLabel': 'پیش‌نمایش',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'سطح',
'ds.surfaceWeb': 'وب',
'ds.surfaceImage': 'تصویر',
@ -1873,4 +1972,99 @@ export const fa: Dict = {
'generationPreview.awaitingLead': 'برای ادامه، به چند پرسش در گفتگو پاسخ دهید.',
'generationPreview.stoppedTitle': 'ساخت متوقف شد',
'generationPreview.stoppedLead': 'مراحل باقی‌مانده را از گفتگوی سمت چپ ادامه دهید.',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -926,6 +926,8 @@ export const fr: Dict = {
'entry.helpSubmitFeature': 'Proposer une fonctionnalité',
'entry.helpWhatsNew': 'Nouveautés',
'entry.helpDownloadDesktop': 'Télécharger l\'app de bureau',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': 'Cliquez pour nous mettre une étoile sur GitHub',
'entry.githubStarAria': 'Mettre une étoile à Open Design sur GitHub',
@ -1271,6 +1273,105 @@ export const fr: Dict = {
'examples.tagVideo': 'Vidéo',
'examples.tagAudio': 'Audio',
'examples.previewLabel': 'Aperçu',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'Surface',
'ds.surfaceWeb': 'Web',
'ds.surfaceImage': 'Image',
@ -2480,4 +2581,99 @@ export const fr: Dict = {
'generationPreview.awaitingLead': 'Répondez à quelques questions dans le chat pour continuer.',
'generationPreview.stoppedTitle': 'Génération en pause',
'generationPreview.stoppedLead': 'Reprenez les étapes restantes depuis le chat à gauche.',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -409,6 +409,8 @@ export const hu: Dict = {
'entry.helpSubmitFeature': 'Funkció javaslása',
'entry.helpWhatsNew': 'Újdonságok',
'entry.helpDownloadDesktop': 'Asztali alkalmazás letöltése',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': 'Csillagozz meg minket a GitHubon',
'entry.githubStarAria': 'Csillagozd meg az Open Design projektet a GitHubon',
@ -733,6 +735,103 @@ export const hu: Dict = {
'examples.tagAudio': 'Hang',
'examples.previewLabel': 'Előnézet',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'Felület',
'ds.surfaceWeb': 'Web',
'ds.surfaceImage': 'Kép',
@ -1840,4 +1939,99 @@ export const hu: Dict = {
'generationPreview.awaitingLead': 'Válaszolj néhány kérdésre a csevegésben a folytatáshoz.',
'generationPreview.stoppedTitle': 'Generálás szüneteltetve',
'generationPreview.stoppedLead': 'Folytasd a hátralévő lépéseket a bal oldali csevegésből.',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -501,6 +501,8 @@ export const id: Dict = {
'entry.helpSubmitFeature': 'Ajukan fitur',
'entry.helpWhatsNew': 'Apa yang baru',
'entry.helpDownloadDesktop': 'Unduh aplikasi desktop',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': 'Klik untuk memberi star di GitHub',
'entry.githubStarAria': 'Beri star Open Design di GitHub',
@ -846,6 +848,103 @@ export const id: Dict = {
'examples.tagAudio': 'Audio',
'examples.previewLabel': 'Pratinjau',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'Surface',
'ds.surfaceWeb': 'Web',
'ds.surfaceImage': 'Gambar',
@ -1873,4 +1972,99 @@ export const id: Dict = {
'generationPreview.awaitingLead': 'Jawab beberapa pertanyaan di obrolan untuk melanjutkan.',
'generationPreview.stoppedTitle': 'Pembuatan dijeda',
'generationPreview.stoppedLead': 'Lanjutkan langkah yang tersisa dari obrolan di kiri.',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -683,6 +683,103 @@ export const it: Dict = {
'examples.tagAudio': 'Audio',
'examples.previewLabel': 'Anteprima',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'Superficie',
'ds.surfaceWeb': 'Web',
'ds.surfaceImage': 'Immagine',
@ -1699,4 +1796,99 @@ export const it: Dict = {
'generationPreview.awaitingLead': 'Rispondi ad alcune domande nella chat per continuare.',
'generationPreview.stoppedTitle': 'Generazione in pausa',
'generationPreview.stoppedLead': 'Riprendi i passaggi rimanenti dalla chat a sinistra.',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -408,6 +408,8 @@ export const ja: Dict = {
'entry.helpSubmitFeature': '機能をリクエスト',
'entry.helpWhatsNew': '最新情報',
'entry.helpDownloadDesktop': 'デスクトップアプリをダウンロード',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': 'GitHub でスターを付ける',
'entry.githubStarAria': 'GitHub で Open Design にスターを付ける',
@ -620,6 +622,103 @@ export const ja: Dict = {
'examples.tagAudio': '音声',
'examples.previewLabel': 'プレビュー',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'サーフェス',
'ds.surfaceWeb': 'Web',
'ds.surfaceImage': '画像',
@ -1767,4 +1866,99 @@ export const ja: Dict = {
'generationPreview.awaitingLead': 'チャットでいくつかの質問に答えると続行します。',
'generationPreview.stoppedTitle': '生成を一時停止しました',
'generationPreview.stoppedLead': '左側のチャットから残りのステップを再開できます。',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -409,6 +409,8 @@ export const ko: Dict = {
'entry.helpSubmitFeature': '기능 제안하기',
'entry.helpWhatsNew': '새로운 소식',
'entry.helpDownloadDesktop': '데스크탑 앱 다운로드',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': 'GitHub 에서 별을 눌러주세요',
'entry.githubStarAria': 'GitHub 에서 Open Design 에 별 누르기',
@ -733,6 +735,103 @@ export const ko: Dict = {
'examples.tagAudio': '오디오',
'examples.previewLabel': '미리보기',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': '대상 표면 (Surface)',
'ds.surfaceWeb': '웹',
'ds.surfaceImage': '이미지',
@ -1880,4 +1979,99 @@ export const ko: Dict = {
'generationPreview.awaitingLead': '왼쪽 채팅에서 몇 가지 질문에 답하면 계속됩니다.',
'generationPreview.stoppedTitle': '생성이 일시중지됨',
'generationPreview.stoppedLead': '왼쪽 채팅에서 남은 단계를 다시 시작할 수 있습니다.',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -409,6 +409,8 @@ export const pl: Dict = {
'entry.helpSubmitFeature': 'Zaproponuj funkcję',
'entry.helpWhatsNew': 'Co nowego',
'entry.helpDownloadDesktop': 'Pobierz aplikację na komputer',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': 'Daj nam gwiazdkę na GitHubie',
'entry.githubStarAria': 'Daj Open Design gwiazdkę na GitHubie',
@ -733,6 +735,103 @@ export const pl: Dict = {
'examples.tagAudio': 'Dźwięk',
'examples.previewLabel': 'Podgląd',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'Powierzchnia',
'ds.surfaceWeb': 'Web',
'ds.surfaceImage': 'Obraz',
@ -1830,4 +1929,99 @@ export const pl: Dict = {
'generationPreview.awaitingLead': 'Odpowiedz na kilka pytań na czacie, aby kontynuować.',
'generationPreview.stoppedTitle': 'Generowanie wstrzymane',
'generationPreview.stoppedLead': 'Wznów pozostałe kroki z czatu po lewej stronie.',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -408,6 +408,8 @@ export const ptBR: Dict = {
'entry.helpSubmitFeature': 'Sugerir um recurso',
'entry.helpWhatsNew': 'Novidades',
'entry.helpDownloadDesktop': 'Baixar app para desktop',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': 'Clique para nos dar uma estrela no GitHub',
'entry.githubStarAria': 'Dar uma estrela ao Open Design no GitHub',
@ -754,6 +756,103 @@ export const ptBR: Dict = {
'examples.tagAudio': 'Áudio',
'examples.previewLabel': 'Prévia',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'Superfície',
'ds.surfaceWeb': 'Web',
'ds.surfaceImage': 'Imagem',
@ -1871,4 +1970,99 @@ export const ptBR: Dict = {
'generationPreview.awaitingLead': 'Responda algumas perguntas no chat para continuar.',
'generationPreview.stoppedTitle': 'Geração pausada',
'generationPreview.stoppedLead': 'Retome as etapas restantes pelo chat à esquerda.',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -408,6 +408,8 @@ export const ru: Dict = {
'entry.helpSubmitFeature': 'Предложить функцию',
'entry.helpWhatsNew': 'Что нового',
'entry.helpDownloadDesktop': 'Скачать настольное приложение',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': 'Поставьте нам звезду на GitHub',
'entry.githubStarAria': 'Поставить Open Design звезду на GitHub',
@ -754,6 +756,103 @@ export const ru: Dict = {
'examples.tagAudio': 'Аудио',
'examples.previewLabel': 'Предпросмотр',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'Поверхность',
'ds.surfaceWeb': 'Веб',
'ds.surfaceImage': 'Изображение',
@ -1871,4 +1970,99 @@ export const ru: Dict = {
'generationPreview.awaitingLead': 'Ответьте на несколько вопросов в чате, чтобы продолжить.',
'generationPreview.stoppedTitle': 'Генерация приостановлена',
'generationPreview.stoppedLead': 'Продолжите оставшиеся шаги в чате слева.',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -707,6 +707,103 @@ export const th: Dict = {
'examples.tagAudio': 'เสียง',
'examples.previewLabel': 'ดูตัวอย่าง',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'พื้นที่',
'ds.surfaceWeb': 'เว็บไซต์',
'ds.surfaceImage': 'รูปภาพ',
@ -1660,4 +1757,99 @@ export const th: Dict = {
'generationPreview.awaitingLead': 'ตอบคำถามสองสามข้อในแชทเพื่อดำเนินการต่อ',
'generationPreview.stoppedTitle': 'หยุดการสร้างชั่วคราว',
'generationPreview.stoppedLead': 'ดำเนินการขั้นตอนที่เหลือต่อจากแชทด้านซ้าย',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -399,6 +399,8 @@ export const tr: Dict = {
'entry.helpSubmitFeature': 'Özellik öner',
'entry.helpWhatsNew': 'Yenilikler',
'entry.helpDownloadDesktop': 'Masaüstü uygulamasını indir',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': 'GitHub üzerinde bize yıldız verin',
'entry.githubStarAria': 'Open Design\u2019a GitHub üzerinde yıldız ver',
@ -722,6 +724,103 @@ export const tr: Dict = {
'examples.tagAudio': 'Ses',
'examples.previewLabel': 'Önizleme',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'Yüzey',
'ds.surfaceWeb': 'Web',
'ds.surfaceImage': 'Image',
@ -1817,4 +1916,99 @@ export const tr: Dict = {
'generationPreview.awaitingLead': 'Devam etmek için sohbette birkaç soruyu yanıtlayın.',
'generationPreview.stoppedTitle': 'Oluşturma duraklatıldı',
'generationPreview.stoppedLead': 'Kalan adımları soldaki sohbetten sürdürün.',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -410,6 +410,8 @@ export const uk: Dict = {
'entry.helpSubmitFeature': 'Запропонувати функцію',
'entry.helpWhatsNew': 'Що нового',
'entry.helpDownloadDesktop': 'Завантажити настільний застосунок',
'entry.helpFollowX': 'Follow @nexudotio on X',
'entry.helpJoinDiscord': 'Join Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': 'Поставте нам зірку на GitHub',
'entry.githubStarAria': 'Поставити Open Design зірку на GitHub',
@ -755,6 +757,103 @@ export const uk: Dict = {
'examples.tagAudio': 'Аудіо',
'examples.previewLabel': 'Попередній перегляд',
'ds.dateJustNow': 'just now',
'ds.userSystemsAria': 'Design Systems',
'ds.userSystemsEyebrow': 'Design Systems',
'ds.userSystemsTitle': 'Your systems',
'ds.userFilterAria': 'Filter design systems',
'ds.statusPublished': 'Published',
'ds.statusDraft': 'Draft',
'ds.createTitle': 'Create new design system',
'ds.createDescription': 'Teach Open Design your brand, product, code, assets, and design references.',
'ds.createAction': 'Create',
'ds.userEmpty': 'No design systems yet. Create one from real product context, review the draft, then publish it for future projects.',
'ds.badgeDefaultInline': 'Default',
'ds.userUpdatedMeta': 'You · updated {date}',
'ds.actionEdit': 'Edit',
'ds.actionMakeDefault': 'Make default',
'ds.actionOpen': 'Open',
'ds.actionOpenNamed': 'Open {title}',
'ds.actionDeleteNamed': 'Delete {title}',
'ds.deleteConfirm': 'Delete "{title}"? This removes the draft design system from this device.',
'ds.templatesAria': 'Templates',
'ds.templatesEyebrow': 'Templates',
'ds.templatesTitle': 'Templates',
'ds.templatesEmpty': 'No templates yet. Create one from any generated project via Share once template publishing is enabled.',
'ds.privateNote': 'Only you can view these settings.',
'ds.libraryAria': 'Built-in design systems',
'ds.libraryEyebrow': 'Library',
'ds.libraryTitle': 'Built-in library',
'ds.previewFrameTitle': '{title} preview',
'ds.setupBack': 'Back',
'ds.setupConfirmTitle': 'It will take about 5 minutes to generate your design system.',
'ds.setupConfirmBody': 'You can step away. Keep the tab open in the background.',
'ds.setupGenerate': 'Generate',
'ds.setupOpeningProject': 'Opening project...',
'ds.setupRequiredError': 'Tell Open Design about the company or design system first.',
'ds.setupGenerateError': 'Could not generate this design system.',
'ds.setupWorkspaceError': 'Could not open the design system workspace.',
'ds.setupPrepareError': 'Could not prepare the design system project.',
'ds.setupContinue': 'Continue to generation',
'ds.setupTitle': 'Set up your design system',
'ds.setupBody': 'Tell us about your company and attach any design resources you have.',
'ds.setupCompanyLabel': 'Company name and blurb (or name of design system)',
'ds.setupCompanyPlaceholder': 'e.g. Mission Impastabowl: fast-casual pasta restaurant with in-store touchscreen kiosk, mobile app and website',
'ds.setupExamplesTitle': 'Provide examples of your design system and products',
'ds.setupExamplesOptional': '(all optional)',
'ds.setupExamplesBody': 'What works best: code and designs for your design system and your code products.',
'ds.setupGithubLabel': 'Link code on GitHub',
'ds.setupGithubAdd': 'Add',
'ds.setupAddedGithubReposAria': 'Added GitHub repositories',
'ds.setupRemoveGithubRepo': 'Remove {repo}',
'ds.setupLinkLocalLabel': 'Link code from your computer',
'ds.setupLinkLocalHelper': 'Open Design can link a local folder for the agent to read, or copy a focused browser-selected folder snapshot into this design-system project.',
'ds.setupLinkLocalPrompt': 'Drag a folder here or browse',
'ds.setupUploadFigLabel': 'Upload a .fig file',
'ds.setupUploadFigHelper': 'The .fig source is parsed locally in your browser; only an extracted summary enters this project.',
'ds.setupUploadFigPrompt': 'Drop .fig here or browse',
'ds.setupAssetsLabel': 'Add fonts, logos and assets',
'ds.setupAssetsPrompt': 'Drag files here or browse',
'ds.setupNotesLabel': 'Any other notes?',
'ds.setupNotesPlaceholder': 'e.g. We use a warm, earthy color palette with rounded corners. Our brand voice is playful but professional...',
'ds.dropZoneBrowseFolder': 'Browse folder',
'ds.dropZoneSelectionsAria': '{label} selections',
'ds.dropZoneRemoveSelection': 'Remove {name}',
'ds.githubStatusCheckTimeout': 'Could not finish checking GitHub connector. You can still add repository URLs or connect GitHub manually.',
'ds.githubStatusCheckError': 'Could not check the GitHub connector.',
'ds.githubAuthorizationStartError': 'Could not start GitHub authorization.',
'ds.githubDisconnectError': 'Could not disconnect GitHub.',
'ds.githubAccessBadgeOptional': 'Optional',
'ds.githubAccessDescOptional': 'Composio GitHub connector access for agent tools; repo URLs still work with local git or GitHub CLI.',
'ds.githubAccessBadgeNotConfigured': 'Not configured',
'ds.githubAccessDescNotConfigured': 'Add a Composio API key only if this project needs connector-backed GitHub tools.',
'ds.githubAccessBadgeConnected': 'Connected',
'ds.githubAccessDescConnectedAs': 'Composio GitHub connector connected as {account}; it is available as fallback when this device cannot read the repository.',
'ds.githubAccessDescConnected': 'Composio GitHub connector is available as fallback when this device cannot read the repository.',
'ds.githubAccessBadgePending': 'Pending',
'ds.githubAccessDescPending': 'Finish the Composio authorization window; local GitHub intake remains available.',
'ds.githubAccessBadgeChecking': 'Checking',
'ds.githubAccessDescChecking': 'Checking connector status in the background; URL intake is not blocked.',
'ds.githubAccessBadgeNeedsAttention': 'Needs attention',
'ds.githubAccessDescReconnect': 'Reconnect the Composio GitHub connector, or continue with local git/GitHub CLI.',
'ds.githubAccessConfigureComposio': 'Configure Composio',
'ds.githubAccessOpenAuthorization': 'Open authorization',
'ds.githubAccessDisconnecting': 'Disconnecting...',
'ds.githubAccessDisconnect': 'Disconnect',
'ds.githubAccessConnecting': 'Connecting...',
'ds.githubAccessConnectComposio': 'Connect via Composio',
'ds.githubAccessLocalTitle': 'This device',
'ds.githubAccessLocalBadge': 'Automatic',
'ds.githubAccessLocalDesc': 'Uses public git clone, local git credentials, or GitHub CLI auth available on this machine.',
'ds.githubAccessNativeTitle': 'Open Design account',
'ds.githubAccessNativeBadge': 'Coming soon',
'ds.githubAccessNativeDesc': 'Native GitHub sign-in managed by Open Design; this build does not use an OD-managed GitHub token yet.',
'ds.githubAccessConnectorTitle': 'Connector platform',
'ds.githubAccessHeaderTitle': 'Repository access: Auto',
'ds.githubAccessHeaderBody': 'Paste a GitHub URL. Open Design will use the first working access method.',
'ds.githubAccessHideMethods': 'Hide access methods',
'ds.githubAccessShowMethods': 'Show access methods',
'ds.githubAccessMethodsAria': 'GitHub repository access methods',
'ds.surfaceLabel': 'Приклади',
'ds.surfaceWeb': 'Веб',
'ds.surfaceImage': 'Зображення',
@ -1873,4 +1972,99 @@ export const uk: Dict = {
'generationPreview.awaitingLead': 'Дайте відповідь на кілька запитань у чаті, щоб продовжити.',
'generationPreview.stoppedTitle': 'Генерацію призупинено',
'generationPreview.stoppedLead': 'Продовжте решту кроків у чаті ліворуч.',
// Additional localized action and workspace labels
'trust.official': 'Official',
'trust.officialDescription': 'Open Design official',
'trust.trusted': 'Trusted',
'trust.trustedDescription': 'Community trusted',
'trust.restricted': 'Restricted',
'trust.restrictedDescription': 'Restricted source',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
'home.error.applyFailed': 'Failed to apply {title}. Make sure the daemon is reachable.',
'home.error.pluginNotInstalled': 'Plugin "{id}" is not installed. Refresh Plugins and try again.',
'home.error.bundledScenarioMissing': 'Bundled scenario "{id}" is not installed. Reinstall the daemon to restore the default plugin set.',
'home.error.folderImportUnavailable': 'Folder import is not available in this shell.',
'home.error.templatePickerUnavailable': 'Template picker is not available in this shell.',
'home.error.fillRequiredParams': 'Fill the required plugin parameters before running.',
'home.error.applyFailedWithParams': 'Failed to apply {title}. Check the plugin parameters and try again.',
'homeHero.mentionSkills': 'Skills',
'pluginsHome.filtersAria': 'Plugin filters',
'pluginsHome.details': 'Details',
'pluginsHome.detailsAria': 'View details for {title}',
'pluginsHome.use': 'Use',
'pluginsHome.applying': 'Applying…',
'pluginsHome.chooseUseAria': 'Choose how to use {title}',
'pluginsHome.useOptionsAria': 'Use options for {title}',
'pluginsHome.useWithQuery': 'Use with query',
'pluginsHome.shareAria': 'Share {title}',
'pluginsHome.publishGithubAria': 'Publish {title} as a GitHub repository',
'pluginsHome.publishGithubTitle': 'Publish plugin as a GitHub repository',
'pluginsHome.contributeAria': 'Contribute {title} to Open Design',
'pluginsHome.contributeTitle': 'Contribute plugin to Open Design with a pull request',
'pluginsHome.starting': 'Starting…',
'pluginsHome.publish': 'Publish',
'pluginsHome.contribute': 'Contribute',
'newproj.scrollTypesLeft': 'Scroll project types left',
'newproj.scrollTypesRight': 'Scroll project types right',
'newproj.betaFeature': 'Beta feature',
'newproj.pickPlatform': 'Pick a platform',
'newproj.deleteTemplate': 'Delete template',
'newproj.deleteTemplateAria': 'Delete template {name}',
'chat.tools.pluginSourceAria': 'Plugin source',
'chat.tools.official': 'Official',
'chat.tools.myPlugins': 'My plugins',
'chat.tools.officialTitle': '{count} installed official plugins',
'chat.tools.myPluginsTitle': '{count} installed user plugins',
'chat.tools.searchPlugins': 'Search plugins…',
'chat.tools.noPluginsInstalled': 'No plugins installed yet. Browse Official or add your own with',
'chat.tools.noPluginResults': 'No {source} results for “{query}”.',
'chat.tools.noPluginsAvailable': 'No {source} plugins available.',
'chat.tools.applying': 'Applying…',
'chat.tools.viewDetails': 'View details for {title}',
'chat.tools.searchMcp': 'Search MCP…',
'chat.tools.searchMcpAria': 'Search MCP servers and templates',
'chat.tools.noMcpServers': 'No enabled MCP servers configured yet.',
'chat.tools.noMcpResults': 'No configured MCP results for “{query}”.',
'chat.tools.configured': 'Configured',
'chat.tools.templates': 'Templates',
'chat.tools.insertMcpHint': 'Insert a hint that nudges the model to use {name}',
'chat.tools.addMcpTemplate': 'Add {name} from Settings',
'chat.tools.manageMcp': 'Manage MCP servers…',
'chat.tools.searchSkills': 'Search skills…',
'chat.tools.noSkills': 'No skills available yet.',
'chat.tools.noSkillResults': 'No skills found for “{query}”.',
'chat.mention.active': 'Active',
'chat.mention.useMcp': 'Use {name}',
'designFiles.pluginFilesReady': '{count} files · ready to add to My plugins',
'designFiles.pluginSending': 'Sending…',
'designFiles.addToMyPlugins': 'Add to My plugins',
'designFiles.publishRepo': 'Publish repo',
'designFiles.openDesignPr': 'Open Design PR',
'fileViewer.drawMode': 'Draw',
'fileViewer.clickMode': 'Click',
'fileViewer.clearInk': 'Clear',
'fileViewer.notePlaceholder': 'Type anywhere to add a note',
'fileViewer.queueComment': 'Queue',
'fileViewer.queueing': 'Queueing...',
'fileViewer.sending': 'Sending...',
'assistant.pluginAction.aria': 'Plugin next actions',
'assistant.pluginAction.title': 'Plugin ready',
'assistant.pluginAction.subtitle': 'Send the next step to the agent so it can run the od CLI.',
'assistant.pluginAction.sent': 'Sent to the agent. The CLI run will continue in chat.',
'assistant.pluginAction.filesReady': '{count} files ready for My plugins',
'assistant.pluginAction.sending': 'Sending...',
'assistant.pluginAction.addToMyPlugins': 'Add to My plugins',
'assistant.pluginAction.publishRepo': 'Publish repo',
'assistant.pluginAction.openDesignPr': 'Open Design PR',
'assistant.pluginAction.openManifest': 'Open manifest',
};

View file

@ -479,6 +479,8 @@ export const zhCN: Dict = {
'homeHero.placeholderActive': '编辑示例请求,或写下你自己的需求…',
'homeHero.skills': '技能',
'homeHero.applying': '正在应用…',
'homeHero.official': '官方',
'homeHero.myPlugin': '我的插件',
'homeHero.pluginTitle': '插件:{title}',
'homeHero.pluginPrefix': '插件:{title}',
'homeHero.skillPrefix': '技能:{title}',
@ -970,6 +972,8 @@ export const zhCN: Dict = {
'entry.helpSubmitFeature': '提交功能建议',
'entry.helpWhatsNew': '最新动态',
'entry.helpDownloadDesktop': '下载桌面端',
'entry.helpFollowX': '在 X 上关注 @nexudotio',
'entry.helpJoinDiscord': '加入 Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': '为我们在 GitHub 点亮 Star',
'entry.githubStarAria': '在 GitHub 上为 Open Design 点亮 Star',
@ -1327,6 +1331,103 @@ export const zhCN: Dict = {
'examples.tagAudio': '音频',
'examples.previewLabel': '预览',
'ds.dateJustNow': '刚刚',
'ds.userSystemsAria': '设计体系',
'ds.userSystemsEyebrow': '设计体系',
'ds.userSystemsTitle': '你的设计体系',
'ds.userFilterAria': '筛选设计体系',
'ds.statusPublished': '已发布',
'ds.statusDraft': '草稿',
'ds.createTitle': '创建新设计体系',
'ds.createDescription': '把你的品牌、产品、代码、资产和设计参考交给 Open Design 学习。',
'ds.createAction': '创建',
'ds.userEmpty': '还没有设计体系。基于真实产品上下文创建一个,审核草稿后发布,供未来项目使用。',
'ds.badgeDefaultInline': '默认',
'ds.userUpdatedMeta': '你 · 更新于 {date}',
'ds.actionEdit': '编辑',
'ds.actionMakeDefault': '设为默认',
'ds.actionOpen': '打开',
'ds.actionOpenNamed': '打开 {title}',
'ds.actionDeleteNamed': '删除 {title}',
'ds.deleteConfirm': '删除“{title}”?这会从本设备移除这个设计体系草稿。',
'ds.templatesAria': '模板',
'ds.templatesEyebrow': '模板',
'ds.templatesTitle': '模板',
'ds.templatesEmpty': '还没有模板。模板发布启用后,可以从任意已生成项目的 Share 菜单创建模板。',
'ds.privateNote': '只有你可以查看这些设置。',
'ds.libraryAria': '内置设计体系',
'ds.libraryEyebrow': '库',
'ds.libraryTitle': '内置库',
'ds.previewFrameTitle': '{title} 预览',
'ds.setupBack': '返回',
'ds.setupConfirmTitle': '生成设计体系大约需要 5 分钟。',
'ds.setupConfirmBody': '你可以暂时离开。请让这个标签页在后台保持打开。',
'ds.setupGenerate': '生成',
'ds.setupOpeningProject': '正在打开项目...',
'ds.setupRequiredError': '先告诉 Open Design 这家公司或设计体系是什么。',
'ds.setupGenerateError': '无法生成这个设计体系。',
'ds.setupWorkspaceError': '无法打开设计体系工作区。',
'ds.setupPrepareError': '无法准备设计体系项目。',
'ds.setupContinue': '继续生成',
'ds.setupTitle': '设置你的设计体系',
'ds.setupBody': '告诉我们你的公司,并附上已有的设计资源。',
'ds.setupCompanyLabel': '公司名称和简介(或设计体系名称)',
'ds.setupCompanyPlaceholder': '例如Mission Impastabowl一家快休闲意面餐厅拥有店内触屏点餐、自有移动应用和网站',
'ds.setupExamplesTitle': '提供设计体系和产品示例',
'ds.setupExamplesOptional': '(全部可选)',
'ds.setupExamplesBody': '最有帮助的是:你的设计体系代码、设计稿,以及产品代码。',
'ds.setupGithubLabel': '从 GitHub 链接代码',
'ds.setupGithubAdd': '添加',
'ds.setupAddedGithubReposAria': '已添加的 GitHub 仓库',
'ds.setupRemoveGithubRepo': '移除 {repo}',
'ds.setupLinkLocalLabel': '从你的电脑链接代码',
'ds.setupLinkLocalHelper': 'Open Design 可以链接本地文件夹供代理读取,也可以把浏览器中选定文件夹的聚焦快照复制到这个设计体系项目中。',
'ds.setupLinkLocalPrompt': '将文件夹拖到这里,或浏览',
'ds.setupUploadFigLabel': '上传 .fig 文件',
'ds.setupUploadFigHelper': '.fig 源文件会在浏览器本地解析;只有提取出的摘要会进入这个项目。',
'ds.setupUploadFigPrompt': '将 .fig 拖到这里,或浏览',
'ds.setupAssetsLabel': '添加字体、标志和资产',
'ds.setupAssetsPrompt': '将文件拖到这里,或浏览',
'ds.setupNotesLabel': '还有其他备注吗?',
'ds.setupNotesPlaceholder': '例如:我们使用温暖、朴实的配色和圆角。品牌语气俏皮但专业...',
'ds.dropZoneBrowseFolder': '浏览文件夹',
'ds.dropZoneSelectionsAria': '{label} 选择项',
'ds.dropZoneRemoveSelection': '移除 {name}',
'ds.githubStatusCheckTimeout': '无法完成 GitHub 连接器检查。你仍然可以添加仓库 URL或手动连接 GitHub。',
'ds.githubStatusCheckError': '无法检查 GitHub 连接器。',
'ds.githubAuthorizationStartError': '无法启动 GitHub 授权。',
'ds.githubDisconnectError': '无法断开 GitHub。',
'ds.githubAccessBadgeOptional': '可选',
'ds.githubAccessDescOptional': 'Composio GitHub 连接器可供代理工具访问;仓库 URL 仍可通过本地 git 或 GitHub CLI 工作。',
'ds.githubAccessBadgeNotConfigured': '未配置',
'ds.githubAccessDescNotConfigured': '只有当这个项目需要由连接器提供的 GitHub 工具时,才需要添加 Composio API key。',
'ds.githubAccessBadgeConnected': '已连接',
'ds.githubAccessDescConnectedAs': 'Composio GitHub 连接器已连接为 {account};当本机无法读取仓库时,它可作为备用方式。',
'ds.githubAccessDescConnected': 'Composio GitHub 连接器已可作为本机无法读取仓库时的备用方式。',
'ds.githubAccessBadgePending': '待授权',
'ds.githubAccessDescPending': '完成 Composio 授权窗口;本地 GitHub 读取仍可使用。',
'ds.githubAccessBadgeChecking': '检查中',
'ds.githubAccessDescChecking': '正在后台检查连接器状态URL 读取不会被阻塞。',
'ds.githubAccessBadgeNeedsAttention': '需要处理',
'ds.githubAccessDescReconnect': '重新连接 Composio GitHub 连接器,或继续使用本地 git/GitHub CLI。',
'ds.githubAccessConfigureComposio': '配置 Composio',
'ds.githubAccessOpenAuthorization': '打开授权',
'ds.githubAccessDisconnecting': '正在断开...',
'ds.githubAccessDisconnect': '断开连接',
'ds.githubAccessConnecting': '正在连接...',
'ds.githubAccessConnectComposio': '通过 Composio 连接',
'ds.githubAccessLocalTitle': '本机',
'ds.githubAccessLocalBadge': '自动',
'ds.githubAccessLocalDesc': '使用公开 git clone、本地 git 凭据,或这台机器上可用的 GitHub CLI 授权。',
'ds.githubAccessNativeTitle': 'Open Design 账号',
'ds.githubAccessNativeBadge': '即将推出',
'ds.githubAccessNativeDesc': '由 Open Design 管理的原生 GitHub 登录;此构建尚未使用 OD 管理的 GitHub token。',
'ds.githubAccessConnectorTitle': '连接器平台',
'ds.githubAccessHeaderTitle': '仓库访问:自动',
'ds.githubAccessHeaderBody': '粘贴 GitHub URL。Open Design 会使用第一个可用的访问方式。',
'ds.githubAccessHideMethods': '隐藏访问方式',
'ds.githubAccessShowMethods': '显示访问方式',
'ds.githubAccessMethodsAria': 'GitHub 仓库访问方式',
'ds.surfaceLabel': '类型',
'ds.surfaceWeb': '网页',
'ds.surfaceImage': '图片',
@ -1458,8 +1559,18 @@ export const zhCN: Dict = {
'chat.inspect.noCommentTargets': '未找到可评论的文本或视觉目标。',
'chat.inspect.editHint': '在预览中选择文本或样式目标进行编辑。',
'chat.inspect.commentHint': '在预览中选择文本或区域进行评论。',
'chat.composerPlaceholder': "描述你想生成的内容…",
'chat.composerHint': "⌘/Ctrl + Enter 发送 · 说清目标、内容、风格和格式",
'chat.composerPlaceholder': '描述你想要的设计 — 粘贴图片、@ 文件或技能、/ 调出命令…',
'chat.composerHint': '⌘/Ctrl + Enter 发送 · 可粘贴图片 · @ 引用文件或技能 · / 调出命令',
'chat.mention.surfacesAria': '提及来源',
'chat.mention.all': '全部',
'chat.mention.plugins': '插件',
'chat.mention.skills': '技能',
'chat.mention.mcp': 'MCP',
'chat.mention.connectors': '连接器',
'chat.mention.files': '设计文件',
'chat.mention.noResults': '没有找到“{query}”的结果。',
'chat.mention.emptyHint': '搜索插件、技能、MCP 服务器、连接器和设计文件。',
'chat.mention.useConnector': '使用 {name}',
'chat.cliSettingsTitle': 'CLI 与模型设置',
'chat.cliSettingsAria': '打开 CLI 与模型设置',
'chat.attachTitle': '附加文件(也可以粘贴/拖入)',
@ -1705,16 +1816,16 @@ export const zhCN: Dict = {
'fileViewer.boxSelect': '框选',
'fileViewer.screenshot': '截图',
'manualEdit.layers': '图层',
'manualEdit.editableCount': '{count} 个可编辑元素',
'manualEdit.editableCount': '{count} 个可编辑',
'manualEdit.hiddenBadge': '隐藏',
'manualEdit.title': '手动编辑器',
'manualEdit.fallbackTitle': '编辑',
'manualEdit.movePanel': '移动编辑面板',
'manualEdit.closePanel': '关闭编辑面板',
'manualEdit.selectLayer': '选择图层',
'manualEdit.empty': '在预览中点击元素,或选择一个图层。',
'manualEdit.selectLayer': '选择一个图层',
'manualEdit.empty': '点击预览中的元素,或从图层列表中选择。',
'manualEdit.noEditableLayers': '未找到可编辑图层。',
'manualEdit.noClass': '无类名',
'manualEdit.noClass': '无 class',
'manualEdit.tabsAria': '手动编辑选项卡',
'manualEdit.tabContent': '内容',
'manualEdit.tabStyle': '样式',
@ -1722,28 +1833,28 @@ export const zhCN: Dict = {
'manualEdit.tabHtml': 'HTML',
'manualEdit.tabSource': '源码',
'manualEdit.attributesJson': '属性 JSON',
'manualEdit.selectedHtml': '元素 HTML',
'manualEdit.fullSource': '完整品源码',
'manualEdit.selectedHtml': '选元素 HTML',
'manualEdit.fullSource': '完整品源码',
'manualEdit.applyContent': '应用内容',
'manualEdit.applyStyle': '应用样式',
'manualEdit.applyAttributes': '应用属性',
'manualEdit.applyHtml': '应用 HTML',
'manualEdit.applySource': '应用源码',
'manualEdit.invalidAttributes': '属性 JSON 格式无效。',
'manualEdit.changes': '修改记录',
'manualEdit.invalidAttributes': '属性 JSON 无效。',
'manualEdit.changes': '更改',
'manualEdit.undo': '撤销',
'manualEdit.redo': '重做',
'manualEdit.noChanges': '尚未进行手动修改。',
'manualEdit.noChanges': '还没有手动编辑。',
'manualEdit.imageUrl': '图片 URL',
'manualEdit.altText': '替代文本',
'manualEdit.label': '标签',
'manualEdit.text': '文本',
'manualEdit.href': '链接地址',
'manualEdit.textColor': '文颜色',
'manualEdit.textColor': '文颜色',
'manualEdit.background': '背景',
'manualEdit.fontSize': '字号',
'manualEdit.weight': '字重',
'manualEdit.align': '对齐方式',
'manualEdit.align': '对齐',
'manualEdit.padding': '内边距',
'manualEdit.margin': '外边距',
'manualEdit.radius': '圆角',
@ -2582,4 +2693,99 @@ export const zhCN: Dict = {
'generationPreview.awaitingLead': '在左侧聊天里回答几个问题即可继续生成。',
'generationPreview.stoppedTitle': '生成已暂停',
'generationPreview.stoppedLead': '可在左侧聊天里继续未完成的步骤。',
// Additional localized action and workspace labels
'trust.official': '官方',
'trust.officialDescription': 'Open Design 官方',
'trust.trusted': '可信',
'trust.trustedDescription': '社区可信来源',
'trust.restricted': '受限',
'trust.restrictedDescription': '受限来源',
'entry.metaWorkspace': '工作区',
'entry.metaStartProject': '开始新项目',
'entry.noTabsFound': '没有找到标签页',
'entry.workspaceTabsAria': '工作区标签页',
'entry.openWorkspacesAria': '已打开的工作区',
'entry.showHiddenTabs': '显示隐藏标签页',
'entry.moreTabs': '还有 {count} 个',
'entry.newTab': '新标签页',
'entry.searchTabs': '搜索标签页',
'entry.openTabs': '已打开标签页',
'home.error.applyFailed': '未能应用 {title}。请确认守护进程可以连接。',
'home.error.pluginNotInstalled': '插件“{id}”尚未安装。请刷新插件列表后重试。',
'home.error.bundledScenarioMissing': '内置场景“{id}”尚未安装。请重新安装守护进程以恢复默认插件集。',
'home.error.folderImportUnavailable': '当前壳层不支持文件夹导入。',
'home.error.templatePickerUnavailable': '当前壳层不支持模板选择器。',
'home.error.fillRequiredParams': '运行前请先填写必填的插件参数。',
'home.error.applyFailedWithParams': '未能应用 {title}。请检查插件参数后重试。',
'homeHero.mentionSkills': '技能',
'pluginsHome.filtersAria': '插件筛选',
'pluginsHome.details': '详情',
'pluginsHome.detailsAria': '查看 {title} 的详情',
'pluginsHome.use': '使用',
'pluginsHome.applying': '应用中…',
'pluginsHome.chooseUseAria': '选择如何使用 {title}',
'pluginsHome.useOptionsAria': '{title} 的使用选项',
'pluginsHome.useWithQuery': '带示例问题使用',
'pluginsHome.shareAria': '分享 {title}',
'pluginsHome.publishGithubAria': '将 {title} 发布为 GitHub 仓库',
'pluginsHome.publishGithubTitle': '将插件发布为 GitHub 仓库',
'pluginsHome.contributeAria': '将 {title} 贡献给 Open Design',
'pluginsHome.contributeTitle': '通过拉取请求将插件贡献给 Open Design',
'pluginsHome.starting': '启动中…',
'pluginsHome.publish': '发布',
'pluginsHome.contribute': '贡献',
'newproj.scrollTypesLeft': '向左滚动项目类型',
'newproj.scrollTypesRight': '向右滚动项目类型',
'newproj.betaFeature': 'Beta 功能',
'newproj.pickPlatform': '选择平台',
'newproj.deleteTemplate': '删除模板',
'newproj.deleteTemplateAria': '删除模板 {name}',
'chat.tools.pluginSourceAria': '插件来源',
'chat.tools.official': '官方',
'chat.tools.myPlugins': '我的插件',
'chat.tools.officialTitle': '已安装 {count} 个官方插件',
'chat.tools.myPluginsTitle': '已安装 {count} 个用户插件',
'chat.tools.searchPlugins': '搜索插件…',
'chat.tools.noPluginsInstalled': '还没有安装插件。你可以浏览官方插件,或使用以下命令添加自己的插件:',
'chat.tools.noPluginResults': '没有匹配“{query}”的{source}结果。',
'chat.tools.noPluginsAvailable': '暂无可用的{source}插件。',
'chat.tools.applying': '应用中…',
'chat.tools.viewDetails': '查看 {title} 详情',
'chat.tools.searchMcp': '搜索 MCP…',
'chat.tools.searchMcpAria': '搜索 MCP 服务器和模板',
'chat.tools.noMcpServers': '还没有配置已启用的 MCP 服务器。',
'chat.tools.noMcpResults': '没有匹配“{query}”的已配置 MCP 结果。',
'chat.tools.configured': '已配置',
'chat.tools.templates': '模板',
'chat.tools.insertMcpHint': '插入提示,引导模型使用 {name}',
'chat.tools.addMcpTemplate': '从设置中添加 {name}',
'chat.tools.manageMcp': '管理 MCP 服务器…',
'chat.tools.searchSkills': '搜索技能…',
'chat.tools.noSkills': '暂无可用技能。',
'chat.tools.noSkillResults': '没有找到“{query}”相关技能。',
'chat.mention.active': '使用中',
'chat.mention.useMcp': '使用 {name}',
'designFiles.pluginFilesReady': '{count} 个文件 · 可添加到“我的插件”',
'designFiles.pluginSending': '发送中…',
'designFiles.addToMyPlugins': '添加到我的插件',
'designFiles.publishRepo': '发布仓库',
'designFiles.openDesignPr': '提交 Open Design PR',
'fileViewer.drawMode': '绘制',
'fileViewer.clickMode': '点击',
'fileViewer.clearInk': '清除',
'fileViewer.notePlaceholder': '输入批注内容',
'fileViewer.queueComment': '加入队列',
'fileViewer.queueing': '加入队列中...',
'fileViewer.sending': '发送中...',
'assistant.pluginAction.aria': '插件后续操作',
'assistant.pluginAction.title': '插件已准备好',
'assistant.pluginAction.subtitle': '把下一步发送给智能体,让它继续执行 od CLI。',
'assistant.pluginAction.sent': '已发送给智能体。CLI 运行会在聊天中继续。',
'assistant.pluginAction.filesReady': '{count} 个文件可添加到“我的插件”',
'assistant.pluginAction.sending': '发送中...',
'assistant.pluginAction.addToMyPlugins': '添加到我的插件',
'assistant.pluginAction.publishRepo': '发布仓库',
'assistant.pluginAction.openDesignPr': '提交 Open Design PR',
'assistant.pluginAction.openManifest': '打开清单文件',
};

View file

@ -475,6 +475,8 @@ export const zhTW: Dict = {
'entry.helpSubmitFeature': '提交功能建議',
'entry.helpWhatsNew': '最新動態',
'entry.helpDownloadDesktop': '下載桌面端',
'entry.helpFollowX': '在 X 上追蹤 @nexudotio',
'entry.helpJoinDiscord': '加入 Discord',
'entry.githubStarLabel': 'Star',
'entry.githubStarTitle': '在 GitHub 為我們點亮 Star',
'entry.githubStarAria': '在 GitHub 為 Open Design 點亮 Star',
@ -929,6 +931,103 @@ export const zhTW: Dict = {
'examples.tagAudio': '音訊',
'examples.previewLabel': '預覽',
'ds.dateJustNow': '剛剛',
'ds.userSystemsAria': '設計系統',
'ds.userSystemsEyebrow': '設計系統',
'ds.userSystemsTitle': '你的設計系統',
'ds.userFilterAria': '篩選設計系統',
'ds.statusPublished': '已發布',
'ds.statusDraft': '草稿',
'ds.createTitle': '建立新設計系統',
'ds.createDescription': '把你的品牌、產品、程式碼、素材和設計參考交給 Open Design 學習。',
'ds.createAction': '建立',
'ds.userEmpty': '還沒有設計系統。根據真實產品脈絡建立一個,審核草稿後發布,供未來專案使用。',
'ds.badgeDefaultInline': '預設',
'ds.userUpdatedMeta': '你 · 更新於 {date}',
'ds.actionEdit': '編輯',
'ds.actionMakeDefault': '設為預設',
'ds.actionOpen': '開啟',
'ds.actionOpenNamed': '開啟 {title}',
'ds.actionDeleteNamed': '刪除 {title}',
'ds.deleteConfirm': '刪除「{title}」?這會從本裝置移除此設計系統草稿。',
'ds.templatesAria': '範本',
'ds.templatesEyebrow': '範本',
'ds.templatesTitle': '範本',
'ds.templatesEmpty': '還沒有範本。範本發布啟用後,可以從任意已生成專案的 Share 選單建立範本。',
'ds.privateNote': '只有你可以查看這些設定。',
'ds.libraryAria': '內建設計系統',
'ds.libraryEyebrow': '庫',
'ds.libraryTitle': '內建庫',
'ds.previewFrameTitle': '{title} 預覽',
'ds.setupBack': '返回',
'ds.setupConfirmTitle': '生成設計系統大約需要 5 分鐘。',
'ds.setupConfirmBody': '你可以暫時離開。請讓這個分頁在背景保持開啟。',
'ds.setupGenerate': '生成',
'ds.setupOpeningProject': '正在開啟專案...',
'ds.setupRequiredError': '先告訴 Open Design 這家公司或設計系統是什麼。',
'ds.setupGenerateError': '無法生成這個設計系統。',
'ds.setupWorkspaceError': '無法開啟設計系統工作區。',
'ds.setupPrepareError': '無法準備設計系統專案。',
'ds.setupContinue': '繼續生成',
'ds.setupTitle': '設定你的設計系統',
'ds.setupBody': '告訴我們你的公司,並附上已有的設計資源。',
'ds.setupCompanyLabel': '公司名稱和簡介(或設計系統名稱)',
'ds.setupCompanyPlaceholder': '例如Mission Impastabowl一家快休閒義大利麵餐廳擁有店內觸控點餐、自有行動應用和網站',
'ds.setupExamplesTitle': '提供設計系統和產品示例',
'ds.setupExamplesOptional': '(全部可選)',
'ds.setupExamplesBody': '最有幫助的是:你的設計系統程式碼、設計稿,以及產品程式碼。',
'ds.setupGithubLabel': '從 GitHub 連結程式碼',
'ds.setupGithubAdd': '新增',
'ds.setupAddedGithubReposAria': '已新增的 GitHub 儲存庫',
'ds.setupRemoveGithubRepo': '移除 {repo}',
'ds.setupLinkLocalLabel': '從你的電腦連結程式碼',
'ds.setupLinkLocalHelper': 'Open Design 可以連結本機資料夾供代理讀取,也可以把瀏覽器中選定資料夾的聚焦快照複製到這個設計系統專案中。',
'ds.setupLinkLocalPrompt': '將資料夾拖到這裡,或瀏覽',
'ds.setupUploadFigLabel': '上傳 .fig 檔案',
'ds.setupUploadFigHelper': '.fig 原始檔會在瀏覽器本機解析;只有提取出的摘要會進入這個專案。',
'ds.setupUploadFigPrompt': '將 .fig 拖到這裡,或瀏覽',
'ds.setupAssetsLabel': '新增字型、標誌和素材',
'ds.setupAssetsPrompt': '將檔案拖到這裡,或瀏覽',
'ds.setupNotesLabel': '還有其他備註嗎?',
'ds.setupNotesPlaceholder': '例如:我們使用溫暖、樸實的配色和圓角。品牌語氣俏皮但專業...',
'ds.dropZoneBrowseFolder': '瀏覽資料夾',
'ds.dropZoneSelectionsAria': '{label} 選取項',
'ds.dropZoneRemoveSelection': '移除 {name}',
'ds.githubStatusCheckTimeout': '無法完成 GitHub 連接器檢查。你仍然可以新增儲存庫 URL或手動連接 GitHub。',
'ds.githubStatusCheckError': '無法檢查 GitHub 連接器。',
'ds.githubAuthorizationStartError': '無法啟動 GitHub 授權。',
'ds.githubDisconnectError': '無法斷開 GitHub。',
'ds.githubAccessBadgeOptional': '可選',
'ds.githubAccessDescOptional': 'Composio GitHub 連接器可供代理工具存取;儲存庫 URL 仍可透過本機 git 或 GitHub CLI 工作。',
'ds.githubAccessBadgeNotConfigured': '未設定',
'ds.githubAccessDescNotConfigured': '只有當這個專案需要由連接器提供的 GitHub 工具時,才需要新增 Composio API key。',
'ds.githubAccessBadgeConnected': '已連接',
'ds.githubAccessDescConnectedAs': 'Composio GitHub 連接器已連接為 {account};當本機無法讀取儲存庫時,它可作為備用方式。',
'ds.githubAccessDescConnected': 'Composio GitHub 連接器已可作為本機無法讀取儲存庫時的備用方式。',
'ds.githubAccessBadgePending': '待授權',
'ds.githubAccessDescPending': '完成 Composio 授權視窗;本機 GitHub 讀取仍可使用。',
'ds.githubAccessBadgeChecking': '檢查中',
'ds.githubAccessDescChecking': '正在背景檢查連接器狀態URL 讀取不會被阻塞。',
'ds.githubAccessBadgeNeedsAttention': '需要處理',
'ds.githubAccessDescReconnect': '重新連接 Composio GitHub 連接器,或繼續使用本機 git/GitHub CLI。',
'ds.githubAccessConfigureComposio': '設定 Composio',
'ds.githubAccessOpenAuthorization': '開啟授權',
'ds.githubAccessDisconnecting': '正在斷開...',
'ds.githubAccessDisconnect': '斷開連接',
'ds.githubAccessConnecting': '正在連接...',
'ds.githubAccessConnectComposio': '透過 Composio 連接',
'ds.githubAccessLocalTitle': '本機',
'ds.githubAccessLocalBadge': '自動',
'ds.githubAccessLocalDesc': '使用公開 git clone、本機 git 憑證,或這台機器上可用的 GitHub CLI 授權。',
'ds.githubAccessNativeTitle': 'Open Design 帳號',
'ds.githubAccessNativeBadge': '即將推出',
'ds.githubAccessNativeDesc': '由 Open Design 管理的原生 GitHub 登入;此建置尚未使用 OD 管理的 GitHub token。',
'ds.githubAccessConnectorTitle': '連接器平台',
'ds.githubAccessHeaderTitle': '儲存庫存取:自動',
'ds.githubAccessHeaderBody': '貼上 GitHub URL。Open Design 會使用第一個可用的存取方式。',
'ds.githubAccessHideMethods': '隱藏存取方式',
'ds.githubAccessShowMethods': '顯示存取方式',
'ds.githubAccessMethodsAria': 'GitHub 儲存庫存取方式',
'ds.surfaceLabel': '類型',
'ds.surfaceWeb': '網頁',
'ds.surfaceImage': '圖片',
@ -2114,6 +2213,7 @@ export const zhTW: Dict = {
'diagnostics.exporting': '匯出中…',
'diagnostics.exportSuccess': '診斷日誌已儲存至 {path}',
'diagnostics.exportFailed': '匯出診斷日誌失敗:{message}',
// Plugin card actions (issue #2079)
'pluginCard.details': '詳情',
'pluginCard.use': '使用',
@ -2150,4 +2250,99 @@ export const zhTW: Dict = {
'generationPreview.awaitingLead': '在左側聊天裡回答幾個問題即可繼續生成。',
'generationPreview.stoppedTitle': '生成已暫停',
'generationPreview.stoppedLead': '可在左側聊天裡繼續未完成的步驟。',
// Additional localized action and workspace labels
'trust.official': '官方',
'trust.officialDescription': 'Open Design 官方',
'trust.trusted': '可信',
'trust.trustedDescription': '社群可信來源',
'trust.restricted': '受限',
'trust.restrictedDescription': '受限來源',
'entry.metaWorkspace': '工作區',
'entry.metaStartProject': '開始新專案',
'entry.noTabsFound': '沒有找到標籤頁',
'entry.workspaceTabsAria': '工作區標籤頁',
'entry.openWorkspacesAria': '已開啟的工作區',
'entry.showHiddenTabs': '顯示隱藏標籤頁',
'entry.moreTabs': '還有 {count} 個',
'entry.newTab': '新標籤頁',
'entry.searchTabs': '搜尋標籤頁',
'entry.openTabs': '已開啟標籤頁',
'home.error.applyFailed': '無法套用 {title}。請確認 daemon 可以連線。',
'home.error.pluginNotInstalled': '外掛「{id}」尚未安裝。請重新整理外掛後再試。',
'home.error.bundledScenarioMissing': '內建情境「{id}」尚未安裝。請重新安裝 daemon 以恢復預設外掛集。',
'home.error.folderImportUnavailable': '目前殼層不支援資料夾匯入。',
'home.error.templatePickerUnavailable': '目前殼層不支援範本選擇器。',
'home.error.fillRequiredParams': '執行前請先填寫必填的外掛參數。',
'home.error.applyFailedWithParams': '無法套用 {title}。請檢查外掛參數後再試。',
'homeHero.mentionSkills': '技能',
'pluginsHome.filtersAria': '外掛篩選',
'pluginsHome.details': '詳情',
'pluginsHome.detailsAria': '檢視 {title} 的詳情',
'pluginsHome.use': '使用',
'pluginsHome.applying': '套用中…',
'pluginsHome.chooseUseAria': '選擇如何使用 {title}',
'pluginsHome.useOptionsAria': '{title} 的使用選項',
'pluginsHome.useWithQuery': '帶範例問題使用',
'pluginsHome.shareAria': '分享 {title}',
'pluginsHome.publishGithubAria': '將 {title} 發布為 GitHub 儲存庫',
'pluginsHome.publishGithubTitle': '將外掛發布為 GitHub 儲存庫',
'pluginsHome.contributeAria': '將 {title} 貢獻至 Open Design',
'pluginsHome.contributeTitle': '透過 pull request 將外掛貢獻至 Open Design',
'pluginsHome.starting': '啟動中…',
'pluginsHome.publish': '發布',
'pluginsHome.contribute': '貢獻',
'newproj.scrollTypesLeft': '向左捲動專案類型',
'newproj.scrollTypesRight': '向右捲動專案類型',
'newproj.betaFeature': 'Beta 功能',
'newproj.pickPlatform': '選擇平台',
'newproj.deleteTemplate': '刪除範本',
'newproj.deleteTemplateAria': '刪除範本 {name}',
'chat.tools.pluginSourceAria': '外掛來源',
'chat.tools.official': '官方',
'chat.tools.myPlugins': '我的外掛',
'chat.tools.officialTitle': '已安裝 {count} 個官方外掛',
'chat.tools.myPluginsTitle': '已安裝 {count} 個使用者外掛',
'chat.tools.searchPlugins': '搜尋外掛…',
'chat.tools.noPluginsInstalled': '尚未安裝外掛。你可以瀏覽官方外掛,或使用以下命令加入自己的外掛:',
'chat.tools.noPluginResults': '沒有符合「{query}」的{source}結果。',
'chat.tools.noPluginsAvailable': '沒有可用的{source}外掛。',
'chat.tools.applying': '套用中…',
'chat.tools.viewDetails': '檢視 {title} 詳情',
'chat.tools.searchMcp': '搜尋 MCP…',
'chat.tools.searchMcpAria': '搜尋 MCP 伺服器與範本',
'chat.tools.noMcpServers': '尚未設定已啟用的 MCP 伺服器。',
'chat.tools.noMcpResults': '沒有符合「{query}」的已設定 MCP 結果。',
'chat.tools.configured': '已設定',
'chat.tools.templates': '範本',
'chat.tools.insertMcpHint': '插入提示,引導模型使用 {name}',
'chat.tools.addMcpTemplate': '從設定加入 {name}',
'chat.tools.manageMcp': '管理 MCP 伺服器…',
'chat.tools.searchSkills': '搜尋技能…',
'chat.tools.noSkills': '沒有可用技能。',
'chat.tools.noSkillResults': '沒有找到「{query}」相關技能。',
'chat.mention.active': '使用中',
'chat.mention.useMcp': '使用 {name}',
'designFiles.pluginFilesReady': '{count} 個檔案 · 可加入「我的外掛」',
'designFiles.pluginSending': '傳送中…',
'designFiles.addToMyPlugins': '加入我的外掛',
'designFiles.publishRepo': '發布儲存庫',
'designFiles.openDesignPr': '提交 Open Design PR',
'fileViewer.drawMode': '繪製',
'fileViewer.clickMode': '點擊',
'fileViewer.clearInk': '清除',
'fileViewer.notePlaceholder': '輸入批註內容',
'fileViewer.queueComment': '加入佇列',
'fileViewer.queueing': '加入佇列中...',
'fileViewer.sending': '傳送中...',
'assistant.pluginAction.aria': '外掛後續操作',
'assistant.pluginAction.title': '外掛已準備好',
'assistant.pluginAction.subtitle': '把下一步傳送給代理,讓它繼續執行 od CLI。',
'assistant.pluginAction.sent': '已傳送給代理。CLI 執行會在聊天中繼續。',
'assistant.pluginAction.filesReady': '{count} 個檔案可加入「我的外掛」',
'assistant.pluginAction.sending': '傳送中...',
'assistant.pluginAction.addToMyPlugins': '加入我的外掛',
'assistant.pluginAction.publishRepo': '發布儲存庫',
'assistant.pluginAction.openDesignPr': '提交 Open Design PR',
'assistant.pluginAction.openManifest': '開啟清單檔',
};

View file

@ -0,0 +1,371 @@
import type { InputFieldSpec } from '@open-design/contracts';
import type { Locale } from './types';
const ZH_INPUT_LABELS: Record<string, string> = {
'Artifact kind': '产物类型',
Fidelity: '保真度',
Audience: '目标受众',
'Design system': '设计体系',
Template: '模板',
'Deck type': '幻灯片类型',
Topic: '主题',
'Slide count': '页数',
'Speaker notes': '演讲者备注',
'Media kind': '媒体类型',
Model: '模型',
Ratio: '比例',
Subject: '主体',
Style: '风格',
Aspect: '画幅比例',
'Aspect ratio': '画幅比例',
Format: '格式',
Duration: '时长',
Prompt: '提示词',
Text: '文本',
'Audio type': '音频类型',
Voice: '声音',
'Audio/captions': '音频 / 字幕',
Product: '产品',
'Motion style': '运动风格',
};
const ZH_DISPLAY_VALUES: Record<string, string> = {
'Select…': '请选择…',
'Choose file…': '选择文件…',
image: '图片',
video: '视频',
audio: '音频',
'web prototype': '网页原型',
wireframe: '线框稿',
'high-fidelity': '高保真',
'product evaluators': '产品评估者',
'the active project design system': '当前项目的设计体系',
'the bundled web prototype seed': '内置网页原型种子',
'pitch deck': '路演幻灯片',
'product overview': '产品概览幻灯片',
'study deck': '学习型幻灯片',
'strategy deck': '策略幻灯片',
'sales deck': '销售幻灯片',
"the user's brief": '用户的需求说明',
'decision makers': '决策者',
'include speaker notes': '包含演讲者备注',
'no speaker notes': '不包含演讲者备注',
'a polished product concept': '一个精致的产品概念',
'a short product reveal': '一支简短的产品揭幕短片',
'an HTML-driven motion composition': '一段由 HTML 驱动的动态构图',
'a concise audio identity for a product': '一段简洁的产品音频识别',
'a crisp product notification sound': '清脆的产品提示音',
'cinematic, high-quality, on-brand': '电影感、高质量、符合品牌调性',
'polished, kinetic, on-brand': '精致、有动势、符合品牌调性',
'clear, polished, modern': '清晰、精致、现代',
speech: '语音',
sfx: '音效',
Speech: '语音',
'Sound effect': '音效',
'No template': '无模板',
'3d-stone-staircase-evolution-infographic': '3D 石阶演化信息图',
'3D Stone Staircase Evolution Infographic': '3D 石阶演化信息图',
'anime-martial-arts-battle-illustration': '动漫武术对决插画',
'Anime Martial Arts Battle Illustration': '动漫武术对决插画',
'e-commerce-live-stream-ui-mockup': '电商直播界面样机',
'E-commerce Live Stream UI Mockup': '电商直播界面样机',
'game-screenshot-anime-fighting-game-captain-ryuuga-vs-kaze-renshin': '动漫格斗游戏截图:龙牙队长对战风炼心',
'Game Screenshot - Anime Fighting Game: Captain Ryuuga vs Kaze Renshin': '动漫格斗游戏截图:龙牙队长对战风炼心',
'game-screenshot-three-kingdoms-guanyu-slaying-yanliang': '三国 ARPG 截图:关羽斩颜良',
'Game Screenshot - Three Kingdoms ARPG: Guan Yu Slaying Yan Liang': '三国 ARPG 截图:关羽斩颜良',
'game-screenshot-three-kingdoms-lyubu-yuanmen-archery': '三国 ARPG 截图:吕布辕门射戟',
"Game Screenshot - Three Kingdoms ARPG: Lü Bu's Yuanmen Archery": '三国 ARPG 截图:吕布辕门射戟',
'game-screenshot-three-kingdoms-zhaoyun-cradle-escape': '三国 ARPG 截图:赵云长坂坡救主',
"Game Screenshot - Three Kingdoms ARPG: Zhao Yun's Cradle Escape at Changbanpo": '三国 ARPG 截图:赵云长坂坡救主',
'game-ui-ancient-china-open-world-mmo-hud': '古风开放世界 MMO 游戏 HUD',
'Game UI - Ancient China Open-World MMO HUD': '古风开放世界 MMO 游戏 HUD',
'illustrated-city-food-map': '城市美食插画地图',
'Illustrated City Food Map': '城市美食插画地图',
'illustration-crayon-kid-drawing-rework': '蜡笔童画重绘插画',
'Illustration - Crayon Kid-Drawing Rework': '蜡笔童画重绘插画',
'infographic-otaku-dance-choreography-breakdown-gokurakujodo-16-panels': '宅舞编舞拆解信息图(极乐净土 16 格)',
'Infographic - Otaku Dance Choreography Breakdown (Gokuraku Jodo, 16 Panels)': '宅舞编舞拆解信息图(极乐净土 16 格)',
'momotaro-explainer-slide-in-hybrid-style': '混合风桃太郎讲解幻灯片',
'Momotaro Explainer Slide in Hybrid Style': '混合风桃太郎讲解幻灯片',
'3d-animated-boy-building-lego': '3D 动画男孩搭乐高',
'3D Animated Boy Building Lego': '3D 动画男孩搭乐高',
'a-decade-of-refinement-glow-up': '十年精修焕变',
'A Decade of Refinement Glow-Up': '十年精修焕变',
'ancient-guardian-dragon-rescue': '古代守护龙救援',
'Ancient Guardian Dragon Rescue': '古代守护龙救援',
'ancient-indian-kingdom-fpv-video': '古印度王国 FPV 视频',
'Ancient Indian Kingdom FPV Video': '古印度王国 FPV 视频',
'animation-transfer-and-camera-tracking-prompt': '动画迁移与镜头跟踪提示词',
'Animation transfer and camera tracking prompt': '动画迁移与镜头跟踪提示词',
'beat-synced-outfit-transformation-dance': '卡点换装舞蹈',
'Beat-Synced Outfit Transformation Dance': '卡点换装舞蹈',
'character-intro-motion-graphics-sequence': '角色介绍动态图形序列',
'Character Intro Motion Graphics Sequence': '角色介绍动态图形序列',
'cinematic-birthday-celebration-sequence': '电影感生日庆祝序列',
'Cinematic Birthday Celebration Sequence': '电影感生日庆祝序列',
'cinematic-dragon-interaction-flight': '电影感飞龙互动与飞行',
'Cinematic Dragon Interaction & Flight': '电影感飞龙互动与飞行',
'cinematic-east-asian-woman-hand-dance': '电影感东亚女性手势舞',
'Cinematic East Asian Woman Hand Dance': '电影感东亚女性手势舞',
'cinematic-emotional-face-close-up': '电影感情绪面部特写',
'Cinematic Emotional Face Close-up': '电影感情绪面部特写',
'cinematic-marine-biologist-exploration': '电影感海洋生物学家探索',
'Cinematic Marine Biologist Exploration': '电影感海洋生物学家探索',
'hyperframes-html-in-canvas-iphone-device': 'HyperFrames HTML 画布3D iPhone 与 MacBook 产品演示',
'HyperFrames HTML-in-Canvas: 3D iPhone + MacBook Product Demo': 'HyperFrames HTML 画布3D iPhone 与 MacBook 产品演示',
'hyperframes-html-in-canvas-text-cursor': 'HyperFrames HTML 画布:电影感文字光标揭示',
'HyperFrames HTML-in-Canvas: Cinematic Text Cursor Reveal': 'HyperFrames HTML 画布:电影感文字光标揭示',
'hyperframes-html-in-canvas-shatter': 'HyperFrames HTML 画布:玻璃碎裂片尾',
'HyperFrames HTML-in-Canvas: Glass Shatter Outro': 'HyperFrames HTML 画布:玻璃碎裂片尾',
'hyperframes-html-in-canvas-liquid-background': 'HyperFrames HTML 画布:液态背景主视觉',
'HyperFrames HTML-in-Canvas: Liquid Background Hero': 'HyperFrames HTML 画布:液态背景主视觉',
'hyperframes-html-in-canvas-liquid-glass': 'HyperFrames HTML 画布:液态玻璃落地页揭示',
'HyperFrames HTML-in-Canvas: Liquid Glass Landing Reveal': 'HyperFrames HTML 画布:液态玻璃落地页揭示',
'hyperframes-html-in-canvas-magnetic': 'HyperFrames HTML 画布:磁场可视化',
'HyperFrames HTML-in-Canvas: Magnetic Field Visualisation': 'HyperFrames HTML 画布:磁场可视化',
'hyperframes-html-in-canvas-portal-reveal': 'HyperFrames HTML 画布:门户揭示仪表盘',
'HyperFrames HTML-in-Canvas: Portal Reveal Dashboard': 'HyperFrames HTML 画布:门户揭示仪表盘',
'hyperframes-money-counter-hype': 'HyperFrames0 到 1 万美元金额计数动效9:16',
'HyperFrames: $0 → $10K Money Counter Hype (9:16)': 'HyperFrames0 到 1 万美元金额计数动效9:16',
'hyperframes-app-showcase-three-phones': 'HyperFrames12 秒 App 展示,三台悬浮手机',
'HyperFrames: 12-Second App Showcase — Three Floating Phones': 'HyperFrames12 秒 App 展示,三台悬浮手机',
'hyperframes-brand-sizzle-reel': 'HyperFrames30 秒品牌高燃短片',
'HyperFrames: 30-Second Brand Sizzle Reel': 'HyperFrames30 秒品牌高燃短片',
'hyperframes-saas-product-promo-30s': 'HyperFrames30 秒 SaaS 产品宣传片Linear 风格)',
'HyperFrames: 30-Second SaaS Product Promo (Linear-style)': 'HyperFrames30 秒 SaaS 产品宣传片Linear 风格)',
'hyperframes-logo-outro-cinematic': 'HyperFrames4 秒电影感标志片尾',
'HyperFrames: 4-Second Cinematic Logo Outro': 'HyperFrames4 秒电影感标志片尾',
'product reveal': '产品揭幕',
'captioned short': '带字幕短片',
'logo outro': '标志片尾',
'audio-reactive visual': '音频响应视觉',
'scene transition sequence': '场景转场序列',
'minimal premium motion': '极简高级动效',
'no audio or captions unless requested': '除非特别要求,否则不添加音频或字幕',
'5 seconds': '5 秒',
'3s': '3 秒',
'5s': '5 秒',
'8s': '8 秒',
'10s': '10 秒',
'15s': '15 秒',
'30s': '30 秒',
'60s': '60 秒',
'120s': '120 秒',
'minimal reveal': '极简揭幕',
'kinetic typography': '动态字体',
'data pulse': '数据脉冲',
};
const ZH_PLACEHOLDERS: Record<string, string> = {
'SaaS landing page': 'SaaS 落地页',
'startup founders evaluating an AI CRM': '正在评估 AI CRM 的创业者',
'OpenAI, Linear, shadcn, or custom brand notes': 'OpenAI、Linear、shadcn 或自定义品牌说明',
'marketing homepage, dashboard, docs page': '营销首页、仪表盘、文档页',
'AI operations platform for modern support teams': '面向现代客服团队的 AI 运营平台',
'Series A investors': 'A 轮投资人',
'Swiss, Linear, editorial, or active project design system': '瑞士风、Linear、编辑风或当前项目设计体系',
'A neon-lit dashboard with floating glass cards': '一块霓虹灯照亮、漂浮玻璃卡片组成的仪表盘',
'cinematic, soft volumetric light': '电影感、柔和体积光',
'a premium AI note-taking app': '一款高端 AI 笔记应用',
'minimal premium, soft side light, restrained motion': '极简高级、柔和侧光、克制动效',
'muted, TTS narration, captions from transcript': '静音、TTS 旁白,或根据转写生成字幕',
'Describe the sound effect': '描述这个音效',
'Text to turn into audio': '要转成音频的文本',
'Loading configured ElevenLabs voices...': '正在加载已配置的 ElevenLabs 声音...',
};
const ZH_TEMPLATE_REPLACEMENTS: ReadonlyArray<readonly [string, string]> = [
['使用这个插件完成以下任务:', ''],
['HTML deck', 'HTML 幻灯片'],
[
'Build a {{fidelity}} {{artifactKind}} for {{audience}}. Use {{designSystem}} as the design-system direction and start from {{template}}. Single self-contained HTML file built by copying the seed `assets/template.html` and pasting section layouts from `references/layouts.md`.',
'为 {{audience}} 构建一个{{fidelity}}{{artifactKind}}。设计系统方向使用 {{designSystem}},从 {{template}} 开始。复制 `assets/template.html` 种子并从 `references/layouts.md` 粘贴版面,输出单文件 HTML。',
],
[
'Create a {{slideCount}}-slide {{deckType}} for {{audience}} about {{topic}}. Speaker notes: {{speakerNotes}}. Use {{designSystem}} as the design-system direction. Build a single-file horizontal-swipe HTML deck by copying `assets/template.html` and pasting slide layouts from `references/layouts.md`.',
'为 {{audience}} 创建 {{slideCount}} 页{{deckType}},主题是 {{topic}}。演讲者备注:{{speakerNotes}}。设计系统方向使用 {{designSystem}}。复制 `assets/template.html` 并从 `references/layouts.md` 粘贴版面,构建单文件横向滑动 HTML 幻灯片。',
],
[
'Generate a {{mediaKind}} of {{subject}}. Style: {{style}}. Aspect: {{aspect}}. Use the media-* atom that matches the project kind, then wrap the result in a live artifact for preview. Iterate on the critique signal until it converges.',
'生成{{mediaKind}}{{subject}}。风格:{{style}}。画幅:{{aspect}}。使用与项目类型匹配的 media-* 原子能力,并把结果包装成实时制品用于预览。根据评审信号持续迭代,直到结果收敛。',
],
[
'Create a {{duration}}-second {{format}} HyperFrames composition for {{subject}}. Aspect: {{aspect}}. Visual style: {{style}}. Audio/captions: {{audioPlan}}. Use the HyperFrames HTML workflow, deterministic timelines, and the referenced motion guides.',
'为 {{subject}} 创建一个 {{duration}}的 {{format}} HyperFrames 作品。画幅:{{aspect}}。视觉风格:{{style}}。音频/字幕:{{audioPlan}}。使用 HyperFrames HTML 工作流、确定性时间线和引用的运动指南。',
],
[
'Create a {{duration}} HyperFrames launch composition for {{product}} with {{motionStyle}} motion.',
'为 {{product}} 创建一个 {{duration}} 的 HyperFrames 发布动效,运动风格为 {{motionStyle}}。',
],
[
'Create a premium product-studio image using {{designSystem}}: elegant composition, refined lighting, restrained color, rich material detail, and commercial campaign-level polish. Render with {{model}} at {{ratio}} in {{resolution}} resolution.',
'使用 {{designSystem}} 创建高级产品工作室图片:优雅构图、精致光影、克制色彩、丰富材质细节,并达到商业广告级质感。使用 {{model}},以 {{ratio}}、{{resolution}} 分辨率渲染。',
],
[
'Create a premium product-studio video using {{designSystem}}: cinematic product pacing, elegant motion, refined lighting, and a polished launch-film feel. Render with {{model}} at {{ratio}} for {{duration}} seconds in {{resolution}} resolution.',
'使用 {{designSystem}} 创建高级产品工作室视频:电影感产品节奏、优雅动效、精致光影和发布片质感。使用 {{model}},以 {{ratio}}、时长 {{duration}}、{{resolution}} 分辨率渲染。',
],
[
'Create a premium product-studio HyperFrames video at {{ratio}} for {{duration}} seconds: refined kinetic typography, elegant transitions, restrained motion language, and studio-grade timing.',
'创建 {{ratio}}、时长 {{duration}} 的高级产品工作室 HyperFrames 视频:精致动态字体、优雅转场、克制动效语言和工作室级节奏。',
],
[
'Create premium product-studio audio from {{prompt}} using {{model}} for {{duration}} seconds: crisp, elegant, memorable, and brand-ready.',
'使用 {{model}},根据 {{prompt}} 创建时长 {{duration}}的高级产品工作室音频:清脆、优雅、易记,适合品牌使用。',
],
[
'Create premium product-studio audio from {{text}} using {{model}} for {{duration}} seconds with {{voice}}: polished, restrained, clear, and brand-ready.',
'使用 {{model}} 和 {{voice}},把 {{text}} 转成时长 {{duration}}的高级产品工作室音频:精致、克制、清晰,适合品牌使用。',
],
[
'Create premium product-studio audio from {{text}} using {{model}} for {{duration}} seconds: polished, restrained, clear, and brand-ready.',
'使用 {{model}},把 {{text}} 转成时长 {{duration}}的高级产品工作室音频:精致、克制、清晰,适合品牌使用。',
],
[
'Create an image using {{template}}, with {{model}} at {{ratio}}.',
'使用 {{template}} 和 {{model}},以 {{ratio}} 生成图片。',
],
[
'Create a video using {{template}}, with {{model}} at {{ratio}} for {{duration}} seconds.',
'使用 {{template}} 和 {{model}},以 {{ratio}} 生成时长 {{duration}}的视频。',
],
[
'Create a HyperFrames video using {{template}} at {{ratio}} for {{duration}} seconds.',
'使用 {{template}},以 {{ratio}} 创建时长 {{duration}}的 HyperFrames 视频。',
],
[
'Create {{audioType}} audio from {{prompt}} using {{model}} for {{duration}} seconds.',
'使用 {{model}},根据 {{prompt}} 创建时长 {{duration}}的{{audioType}}音频。',
],
[
'Create {{audioType}} audio from {{text}} using {{model}} for {{duration}} seconds with {{voice}}.',
'使用 {{model}} 和 {{voice}},把 {{text}} 转成时长 {{duration}}的{{audioType}}音频。',
],
[
'Create {{audioType}} audio from {{text}} using {{model}} for {{duration}} seconds.',
'使用 {{model}},把 {{text}} 转成时长 {{duration}}的{{audioType}}音频。',
],
[
'Generate a {{artifactKind}} for {{audience}} on {{topic}}. Use the discovery → plan → generate → critique loop and stop when the critique score converges or the iteration ceiling is hit.',
'为 {{audience}} 生成一个{{artifactKind}},主题是 {{topic}}。使用“发现 → 规划 → 生成 → 评审”循环,在评审分数收敛或达到迭代上限时停止。',
],
];
export function localizePluginInputLabel(locale: Locale, field: InputFieldSpec): string {
const label = field.label ?? field.name;
return locale === 'zh-CN' ? ZH_INPUT_LABELS[label] ?? label : label;
}
export function localizePluginPlaceholder(
locale: Locale,
value: string | undefined,
fallback: string = '',
): string {
const placeholder = value ?? fallback;
if (locale !== 'zh-CN') return placeholder;
return ZH_PLACEHOLDERS[placeholder] ?? ZH_DISPLAY_VALUES[placeholder] ?? placeholder;
}
export function localizePluginDisplayValue(locale: Locale, value: unknown): string {
if (value === undefined || value === null) return '';
const text = String(value);
return locale === 'zh-CN' ? ZH_DISPLAY_VALUES[text] ?? text : text;
}
function localizePluginTemplateValue(locale: Locale, key: string, value: unknown): string {
if (locale === 'zh-CN' && key === 'duration') {
const text = String(value);
if (/^\d+(?:\.\d+)?$/.test(text)) return `${text}`;
}
return localizePluginDisplayValue(locale, value);
}
export function localizePluginInputValues(
locale: Locale,
values: Record<string, unknown>,
fields: InputFieldSpec[] = [],
): Record<string, unknown> {
if (locale !== 'zh-CN') return values;
const fieldByName = new Map(fields.map((field) => [field.name, field]));
return Object.fromEntries(
Object.entries(values).map(([key, value]) => [
key,
typeof value === 'string' || typeof value === 'number'
? localizePluginTemplateInputValue(locale, key, value, fieldByName.get(key))
: value,
]),
);
}
export function localizePluginBriefTemplate(locale: Locale, template: string | null): string | null {
if (template === null || locale !== 'zh-CN') return template;
let next = template;
for (const [from, to] of ZH_TEMPLATE_REPLACEMENTS) {
next = next.split(from).join(to);
}
return next;
}
export function renderLocalizedPluginBriefTemplate(
locale: Locale,
template: string,
inputs: Record<string, unknown>,
fields: InputFieldSpec[] = [],
): string {
const localizedTemplate = localizePluginBriefTemplate(locale, template) ?? template;
const fieldByName = new Map(fields.map((field) => [field.name, field]));
return localizedTemplate.replace(/\{\{\s*([a-zA-Z_][\w-]*)\s*\}\}/g, (full, key) => {
if (key in inputs) {
const value = inputs[key];
if (value === undefined || value === null || value === '') return full;
return localizePluginTemplateInputValue(locale, key, value, fieldByName.get(key));
}
return full;
});
}
function localizePluginTemplateInputValue(
locale: Locale,
key: string,
value: unknown,
field: InputFieldSpec | undefined,
): string {
if (locale === 'zh-CN' && field?.type === 'select' && Array.isArray(field.options)) {
const raw = String(value);
const label = optionLabelMap(field)[raw];
if (label) return localizePluginDisplayValue(locale, label);
}
return localizePluginTemplateValue(locale, key, value);
}
export function rawPluginValueFromLocalizedDisplay(
locale: Locale,
field: InputFieldSpec,
value: string,
): string | null {
if (locale !== 'zh-CN' || !Array.isArray(field.options)) return null;
const trimmed = value.trim();
const optionLabels = optionLabelMap(field);
for (const option of field.options) {
const label = optionLabels[option];
const candidates = [
option,
label,
localizePluginDisplayValue(locale, option),
label ? localizePluginDisplayValue(locale, label) : undefined,
localizePluginTemplateValue(locale, field.name, option),
label ? localizePluginTemplateValue(locale, field.name, label) : undefined,
];
if (candidates.some((candidate) => candidate !== undefined && String(candidate).trim() === trimmed)) {
return option;
}
}
return null;
}
function optionLabelMap(field: InputFieldSpec): Record<string, string> {
const labels = (field as { optionLabels?: unknown }).optionLabels;
return labels && typeof labels === 'object' && !Array.isArray(labels)
? labels as Record<string, string>
: {};
}

View file

@ -791,6 +791,8 @@ export interface Dict {
'homeHero.placeholderActive': string;
'homeHero.skills': string;
'homeHero.applying': string;
'homeHero.official': string;
'homeHero.myPlugin': string;
'homeHero.pluginTitle': string;
'homeHero.pluginPrefix': string;
'homeHero.skillPrefix': string;
@ -1286,6 +1288,8 @@ export interface Dict {
'entry.helpSubmitFeature': string;
'entry.helpWhatsNew': string;
'entry.helpDownloadDesktop': string;
'entry.helpFollowX': string;
'entry.helpJoinDiscord': string;
// GitHub star pill in the top bar
'entry.githubStarLabel': string;
'entry.githubStarTitle': string;
@ -1646,6 +1650,103 @@ export interface Dict {
'examples.previewLabel': string;
// Design systems tab
'ds.dateJustNow': string;
'ds.userSystemsAria': string;
'ds.userSystemsEyebrow': string;
'ds.userSystemsTitle': string;
'ds.userFilterAria': string;
'ds.statusPublished': string;
'ds.statusDraft': string;
'ds.createTitle': string;
'ds.createDescription': string;
'ds.createAction': string;
'ds.userEmpty': string;
'ds.badgeDefaultInline': string;
'ds.userUpdatedMeta': string;
'ds.actionEdit': string;
'ds.actionMakeDefault': string;
'ds.actionOpen': string;
'ds.actionOpenNamed': string;
'ds.actionDeleteNamed': string;
'ds.deleteConfirm': string;
'ds.templatesAria': string;
'ds.templatesEyebrow': string;
'ds.templatesTitle': string;
'ds.templatesEmpty': string;
'ds.privateNote': string;
'ds.libraryAria': string;
'ds.libraryEyebrow': string;
'ds.libraryTitle': string;
'ds.previewFrameTitle': string;
'ds.setupBack': string;
'ds.setupConfirmTitle': string;
'ds.setupConfirmBody': string;
'ds.setupGenerate': string;
'ds.setupOpeningProject': string;
'ds.setupRequiredError': string;
'ds.setupGenerateError': string;
'ds.setupWorkspaceError': string;
'ds.setupPrepareError': string;
'ds.setupContinue': string;
'ds.setupTitle': string;
'ds.setupBody': string;
'ds.setupCompanyLabel': string;
'ds.setupCompanyPlaceholder': string;
'ds.setupExamplesTitle': string;
'ds.setupExamplesOptional': string;
'ds.setupExamplesBody': string;
'ds.setupGithubLabel': string;
'ds.setupGithubAdd': string;
'ds.setupAddedGithubReposAria': string;
'ds.setupRemoveGithubRepo': string;
'ds.setupLinkLocalLabel': string;
'ds.setupLinkLocalHelper': string;
'ds.setupLinkLocalPrompt': string;
'ds.setupUploadFigLabel': string;
'ds.setupUploadFigHelper': string;
'ds.setupUploadFigPrompt': string;
'ds.setupAssetsLabel': string;
'ds.setupAssetsPrompt': string;
'ds.setupNotesLabel': string;
'ds.setupNotesPlaceholder': string;
'ds.dropZoneBrowseFolder': string;
'ds.dropZoneSelectionsAria': string;
'ds.dropZoneRemoveSelection': string;
'ds.githubStatusCheckTimeout': string;
'ds.githubStatusCheckError': string;
'ds.githubAuthorizationStartError': string;
'ds.githubDisconnectError': string;
'ds.githubAccessBadgeOptional': string;
'ds.githubAccessDescOptional': string;
'ds.githubAccessBadgeNotConfigured': string;
'ds.githubAccessDescNotConfigured': string;
'ds.githubAccessBadgeConnected': string;
'ds.githubAccessDescConnectedAs': string;
'ds.githubAccessDescConnected': string;
'ds.githubAccessBadgePending': string;
'ds.githubAccessDescPending': string;
'ds.githubAccessBadgeChecking': string;
'ds.githubAccessDescChecking': string;
'ds.githubAccessBadgeNeedsAttention': string;
'ds.githubAccessDescReconnect': string;
'ds.githubAccessConfigureComposio': string;
'ds.githubAccessOpenAuthorization': string;
'ds.githubAccessDisconnecting': string;
'ds.githubAccessDisconnect': string;
'ds.githubAccessConnecting': string;
'ds.githubAccessConnectComposio': string;
'ds.githubAccessLocalTitle': string;
'ds.githubAccessLocalBadge': string;
'ds.githubAccessLocalDesc': string;
'ds.githubAccessNativeTitle': string;
'ds.githubAccessNativeBadge': string;
'ds.githubAccessNativeDesc': string;
'ds.githubAccessConnectorTitle': string;
'ds.githubAccessHeaderTitle': string;
'ds.githubAccessHeaderBody': string;
'ds.githubAccessHideMethods': string;
'ds.githubAccessShowMethods': string;
'ds.githubAccessMethodsAria': string;
'ds.surfaceLabel': string;
'ds.surfaceWeb': string;
'ds.surfaceImage': string;
@ -1794,6 +1895,16 @@ export interface Dict {
'chat.copyDone': string;
'chat.composerPlaceholder': string;
'chat.composerHint': string;
'chat.mention.surfacesAria': string;
'chat.mention.all': string;
'chat.mention.plugins': string;
'chat.mention.skills': string;
'chat.mention.mcp': string;
'chat.mention.connectors': string;
'chat.mention.files': string;
'chat.mention.noResults': string;
'chat.mention.emptyHint': string;
'chat.mention.useConnector': string;
'chat.cliSettingsTitle': string;
'chat.cliSettingsAria': string;
'chat.attachTitle': string;
@ -2631,4 +2742,98 @@ export interface Dict {
'diagnostics.exporting': string;
'diagnostics.exportSuccess': string;
'diagnostics.exportFailed': string;
// Additional localized action and workspace labels
'trust.official': string;
'trust.officialDescription': string;
'trust.trusted': string;
'trust.trustedDescription': string;
'trust.restricted': string;
'trust.restrictedDescription': string;
'entry.metaWorkspace': string;
'entry.metaStartProject': string;
'entry.noTabsFound': string;
'entry.workspaceTabsAria': string;
'entry.openWorkspacesAria': string;
'entry.showHiddenTabs': string;
'entry.moreTabs': string;
'entry.newTab': string;
'entry.searchTabs': string;
'entry.openTabs': string;
'home.error.applyFailed': string;
'home.error.pluginNotInstalled': string;
'home.error.bundledScenarioMissing': string;
'home.error.folderImportUnavailable': string;
'home.error.templatePickerUnavailable': string;
'home.error.fillRequiredParams': string;
'home.error.applyFailedWithParams': string;
'homeHero.mentionSkills': string;
'pluginsHome.filtersAria': string;
'pluginsHome.details': string;
'pluginsHome.detailsAria': string;
'pluginsHome.use': string;
'pluginsHome.applying': string;
'pluginsHome.chooseUseAria': string;
'pluginsHome.useOptionsAria': string;
'pluginsHome.useWithQuery': string;
'pluginsHome.shareAria': string;
'pluginsHome.publishGithubAria': string;
'pluginsHome.publishGithubTitle': string;
'pluginsHome.contributeAria': string;
'pluginsHome.contributeTitle': string;
'pluginsHome.starting': string;
'pluginsHome.publish': string;
'pluginsHome.contribute': string;
'newproj.scrollTypesLeft': string;
'newproj.scrollTypesRight': string;
'newproj.betaFeature': string;
'newproj.pickPlatform': string;
'newproj.deleteTemplate': string;
'newproj.deleteTemplateAria': string;
'chat.tools.pluginSourceAria': string;
'chat.tools.official': string;
'chat.tools.myPlugins': string;
'chat.tools.officialTitle': string;
'chat.tools.myPluginsTitle': string;
'chat.tools.searchPlugins': string;
'chat.tools.noPluginsInstalled': string;
'chat.tools.noPluginResults': string;
'chat.tools.noPluginsAvailable': string;
'chat.tools.applying': string;
'chat.tools.viewDetails': string;
'chat.tools.searchMcp': string;
'chat.tools.searchMcpAria': string;
'chat.tools.noMcpServers': string;
'chat.tools.noMcpResults': string;
'chat.tools.configured': string;
'chat.tools.templates': string;
'chat.tools.insertMcpHint': string;
'chat.tools.addMcpTemplate': string;
'chat.tools.manageMcp': string;
'chat.tools.searchSkills': string;
'chat.tools.noSkills': string;
'chat.tools.noSkillResults': string;
'chat.mention.active': string;
'chat.mention.useMcp': string;
'designFiles.pluginFilesReady': string;
'designFiles.pluginSending': string;
'designFiles.addToMyPlugins': string;
'designFiles.publishRepo': string;
'designFiles.openDesignPr': string;
'fileViewer.drawMode': string;
'fileViewer.clickMode': string;
'fileViewer.clearInk': string;
'fileViewer.notePlaceholder': string;
'fileViewer.queueComment': string;
'fileViewer.queueing': string;
'fileViewer.sending': string;
'assistant.pluginAction.aria': string;
'assistant.pluginAction.title': string;
'assistant.pluginAction.subtitle': string;
'assistant.pluginAction.sent': string;
'assistant.pluginAction.filesReady': string;
'assistant.pluginAction.sending': string;
'assistant.pluginAction.addToMyPlugins': string;
'assistant.pluginAction.publishRepo': string;
'assistant.pluginAction.openDesignPr': string;
'assistant.pluginAction.openManifest': string;
}

View file

@ -8,6 +8,16 @@ import { ChatPane } from '../../src/components/ChatPane';
import type { ChatMessage, Conversation } from '../../src/types';
vi.mock('../../src/i18n', () => ({
useI18n: () => ({
locale: 'en',
setLocale: () => undefined,
t: (key: string, vars?: Record<string, string | number>) => {
if (key === 'chat.renameConversationLabel') {
return `chat.renameConversationLabel ${vars?.title ?? ''}`;
}
return key;
},
}),
useT: () => (key: string, vars?: Record<string, string | number>) => {
if (key === 'chat.renameConversationLabel') {
return `chat.renameConversationLabel ${vars?.title ?? ''}`;

View file

@ -252,6 +252,28 @@ describe('design system package audit helpers', () => {
});
describe('DesignSystemCreationFlow', () => {
it('localizes the setup form in Simplified Chinese', () => {
render(
<I18nProvider initial="zh-CN">
<DesignSystemCreationFlow
onBack={() => {}}
onCreated={() => {}}
/>
</I18nProvider>,
);
expect(screen.getByRole('heading', { name: '设置你的设计体系' })).toBeTruthy();
expect(screen.getByText('公司名称和简介(或设计体系名称)')).toBeTruthy();
expect(screen.getByText('从 GitHub 链接代码')).toBeTruthy();
expect(screen.getByText('仓库访问:自动')).toBeTruthy();
expect(screen.getByText('从你的电脑链接代码')).toBeTruthy();
expect(screen.getByText('浏览文件夹')).toBeTruthy();
expect(screen.getByText('上传 .fig 文件')).toBeTruthy();
expect(screen.getByText('还有其他备注吗?')).toBeTruthy();
expect(screen.queryByText('Set up your design system')).toBeNull();
expect(screen.queryByText('Repository access: Auto')).toBeNull();
});
it('opens the project as soon as the workspace exists and prepares the first chat task afterward', async () => {
const system: DesignSystemDetail = {
id: 'user:acme-design-system',

View file

@ -5,6 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { DesignSystemSummary } from '@open-design/contracts';
import { DesignSystemsTab } from '../../src/components/DesignSystemsTab';
import { I18nProvider } from '../../src/i18n';
vi.mock('../../src/providers/registry', async () => {
const actual = await vi.importActual<typeof import('../../src/providers/registry')>(
@ -128,6 +129,65 @@ describe('DesignSystemsTab', () => {
expect(onPreview).toHaveBeenCalledWith('linear');
expect(onOpenSystem).not.toHaveBeenCalledWith('linear');
});
it('localizes the design systems manager chrome in Simplified Chinese', () => {
render(
<I18nProvider initial="zh-CN">
<DesignSystemsTab
systems={systems}
selectedId={null}
onSelect={() => {}}
onPreview={() => {}}
onCreate={() => {}}
onOpenSystem={() => {}}
/>
</I18nProvider>,
);
expect(screen.getByRole('heading', { name: '你的设计体系' })).toBeTruthy();
expect(screen.getByText('创建新设计体系')).toBeTruthy();
expect(screen.getByText('内置库')).toBeTruthy();
expect(screen.getByText('只有你可以查看这些设置。')).toBeTruthy();
expect(screen.getByText('编辑')).toBeTruthy();
expect(screen.queryByText('Your systems')).toBeNull();
expect(screen.queryByText('Built-in library')).toBeNull();
});
it('keeps untranslated design system summaries visible and searchable in Simplified Chinese', () => {
const englishOnlySystem: DesignSystemSummary = {
id: 'reviewer-only-system',
title: 'Reviewer Only System',
category: 'Productivity & SaaS',
summary: 'Specific compliance dashboard primitives for enterprise workflows.',
surface: 'web',
source: 'built-in',
status: 'published',
isEditable: false,
};
render(
<I18nProvider initial="zh-CN">
<DesignSystemsTab
systems={[englishOnlySystem]}
selectedId={null}
onSelect={() => {}}
onPreview={() => {}}
onCreate={() => {}}
onOpenSystem={() => {}}
/>
</I18nProvider>,
);
expect(screen.getByText('Specific compliance dashboard primitives for enterprise workflows.')).toBeTruthy();
expect(screen.queryByText('Reviewer Only System 风格设计体系,适合效率与 SaaS 相关界面。')).toBeNull();
fireEvent.change(screen.getByTestId('design-systems-search'), {
target: { value: 'compliance dashboard' },
});
expect(screen.getByText('Reviewer Only System')).toBeTruthy();
expect(screen.getByText('Specific compliance dashboard primitives for enterprise workflows.')).toBeTruthy();
});
});
// --- #2062: built-in library surface-chip filtering -----------------------

View file

@ -0,0 +1,30 @@
// @vitest-environment jsdom
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it } from 'vitest';
import { EntryHelpMenu } from '../../src/components/EntryHelpMenu';
import { I18nProvider } from '../../src/i18n';
afterEach(() => {
cleanup();
});
describe('EntryHelpMenu', () => {
it('localizes help links in Simplified Chinese', () => {
render(
<I18nProvider initial="zh-CN">
<EntryHelpMenu />
</I18nProvider>,
);
fireEvent.click(screen.getByTestId('entry-help-trigger'));
expect(screen.getByRole('menuitem', { name: '在 GitHub 上获取帮助' })).toBeTruthy();
expect(screen.getByRole('menuitem', { name: '提交功能建议' })).toBeTruthy();
expect(screen.getByRole('menuitem', { name: '最新动态' })).toBeTruthy();
expect(screen.getByRole('menuitem', { name: '下载桌面端' })).toBeTruthy();
expect(screen.queryByText('Get help on GitHub')).toBeNull();
expect(screen.queryByText('Submit a feature request')).toBeNull();
});
});

View file

@ -18,6 +18,7 @@ import {
HOME_HERO_CHIPS,
findChip,
} from '../../src/components/home-hero/chips';
import { I18nProvider, type Locale } from '../../src/i18n';
afterEach(() => {
cleanup();
@ -68,31 +69,36 @@ function makePlugin(
};
}
function renderHero(overrides: Partial<React.ComponentProps<typeof HomeHero>> = {}) {
function renderHero(
overrides: Partial<React.ComponentProps<typeof HomeHero>> = {},
locale: Locale = 'en',
) {
const onPickChip = vi.fn();
const onPickPlugin = vi.fn();
const onPickExamplePlugin = vi.fn();
const onClearActiveChip = vi.fn();
render(
<HomeHero
prompt=""
onPromptChange={() => undefined}
onSubmit={() => undefined}
activePluginTitle={null}
activeChipId={null}
onClearActivePlugin={() => undefined}
pluginOptions={[]}
pluginsLoading={false}
pendingPluginId={null}
pendingChipId={null}
onPickPlugin={onPickPlugin}
onPickExamplePlugin={onPickExamplePlugin}
onPickChip={onPickChip}
onClearActiveChip={onClearActiveChip}
contextItemCount={0}
error={null}
{...overrides}
/>,
<I18nProvider initial={locale}>
<HomeHero
prompt=""
onPromptChange={() => undefined}
onSubmit={() => undefined}
activePluginTitle={null}
activeChipId={null}
onClearActivePlugin={() => undefined}
pluginOptions={[]}
pluginsLoading={false}
pendingPluginId={null}
pendingChipId={null}
onPickPlugin={onPickPlugin}
onPickExamplePlugin={onPickExamplePlugin}
onPickChip={onPickChip}
onClearActiveChip={onClearActiveChip}
contextItemCount={0}
error={null}
{...overrides}
/>
</I18nProvider>,
);
return { onPickChip, onPickPlugin, onPickExamplePlugin, onClearActiveChip };
}
@ -400,11 +406,20 @@ describe('HomeHero intent rail', () => {
.closest('[data-rail-group]');
expect(createPluginGroup?.getAttribute('data-rail-group')).toBe('migrate');
for (const id of ['figma', 'template']) {
for (const id of ['figma', 'folder', 'template']) {
expect(screen.getByTestId(`home-hero-rail-${id}`).closest('[data-rail-group]'))
.toBe(createPluginGroup);
}
expect(screen.queryByTestId('home-hero-rail-folder')).toBeNull();
});
it('localizes the folder shortcut label and hint in Simplified Chinese', () => {
renderHero({ activeChipId: 'folder' }, 'zh-CN');
fireEvent.click(screen.getByTestId('home-hero-shortcuts-trigger'));
const folder = screen.getByTestId('home-hero-rail-folder');
expect(folder.textContent).toContain('来自文件夹');
expect(folder.getAttribute('title')).toBe('导入本地文件夹并继续编辑。');
expect(folder.className).toContain('is-active');
});
it('keeps the generic fallback in the free-form prompt instead of an Other chip', () => {
@ -417,7 +432,7 @@ describe('HomeHero intent rail', () => {
it('migration chips carry the right action discriminator', () => {
expect(findChip('create-plugin')?.action).toMatchObject({ kind: 'create-plugin' });
expect(findChip('figma')?.action).toMatchObject({ kind: 'apply-figma-migration' });
expect(findChip('folder')).toBeUndefined();
expect(findChip('folder')?.action).toMatchObject({ kind: 'import-folder' });
expect(findChip('template')?.action).toMatchObject({ kind: 'open-template-picker' });
});

View file

@ -3,6 +3,7 @@
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { HomeView } from '../../src/components/HomeView';
import { I18nProvider } from '../../src/i18n';
import type { DesignSystemSummary, PromptTemplateSummary } from '../../src/types';
const MEDIA_PLUGIN = pluginRecord('od-media-generation', 'Media generation');
@ -42,6 +43,39 @@ const PROMPT_TEMPLATES: PromptTemplateSummary[] = [
},
];
const ZH_MEDIA_PROMPT_TEMPLATES: PromptTemplateSummary[] = [
{
id: '3d-stone-staircase-evolution-infographic',
surface: 'image',
title: '3D Stone Staircase Evolution Infographic',
summary: 'A polished product image prompt.',
category: 'product',
model: 'gpt-image-2',
aspect: '16:9',
source: { repo: 'open-design/image-prompts', license: 'MIT' },
},
{
id: '3d-animated-boy-building-lego',
surface: 'video',
title: '3D Animated Boy Building Lego',
summary: 'A short reveal video prompt.',
category: 'product',
model: 'doubao-seedance-2-0-260128',
aspect: '16:9',
source: { repo: 'open-design/video-prompts', license: 'MIT' },
},
{
id: 'hyperframes-html-in-canvas-iphone-device',
surface: 'video',
title: 'HyperFrames HTML-in-Canvas: 3D iPhone + MacBook Product Demo',
summary: 'A caption-led HyperFrames prompt.',
category: 'motion',
model: 'hyperframes-html',
aspect: '16:9',
source: { repo: 'heygen-com/hyperframes', license: 'MIT' },
},
];
afterEach(() => {
vi.unstubAllGlobals();
cleanup();
@ -131,6 +165,63 @@ describe('HomeView media composer options', () => {
expect(screen.queryByRole('dialog', { name: /replace current prompt/i })).toBeNull();
});
it('localizes media composer prompt templates in Simplified Chinese without auto-filling the textarea', async () => {
stubFetch();
renderHomeZh({ promptTemplates: ZH_MEDIA_PROMPT_TEMPLATES });
const input = screen.getByTestId('home-hero-input') as HTMLTextAreaElement;
await clickHomeRailChip('image');
await waitFor(() => expect(screen.getByTestId('home-hero-footer-option-model')).toBeTruthy());
expect(input.value).toBe('');
fireEvent.change(input, {
target: {
value:
'使用 自动 创建高级产品工作室图片:优雅构图、精致光影、克制色彩、丰富材质细节,并达到商业广告级质感。使用 gpt-image-2以 16:9、2K 分辨率渲染。',
},
});
await waitFor(() => expect(screen.getByTestId('home-hero-prompt-highlight').textContent).toContain(
'高级产品工作室图片',
));
await clickHomeRailChip('video');
await waitFor(() => expect(screen.getByTestId('home-hero-footer-option-duration')).toBeTruthy());
fireEvent.change(input, {
target: {
value:
'使用 自动 创建高级产品工作室视频:电影感产品节奏、优雅动效、精致光影和发布片质感。使用 seedance-2.0,以 16:9、时长 5 秒、2K 分辨率渲染。',
},
});
await waitFor(() => expect(screen.getByTestId('home-hero-prompt-highlight').textContent).toContain(
'高级产品工作室视频',
));
await clickHomeRailChip('hyperframes');
await waitFor(() => expect(screen.getByTestId('home-hero-footer-option-duration')).toBeTruthy());
fireEvent.change(input, {
target: {
value:
'创建 16:9、时长 10 秒 的高级产品工作室 HyperFrames 视频:精致动态字体、优雅转场、克制动效语言和工作室级节奏。',
},
});
await waitFor(() => expect(screen.getByTestId('home-hero-prompt-highlight').textContent).toContain(
'高级产品工作室 HyperFrames 视频',
));
await clickHomeRailChip('audio');
await waitFor(() => expect(screen.getByTestId('home-hero-footer-option-audioType')).toBeTruthy());
fireEvent.change(input, {
target: {
value:
'使用 minimax-tts把 用户的需求说明 转成时长 10 秒的高级产品工作室音频:精致、克制、清晰,适合品牌使用。',
},
});
await waitFor(() => expect(screen.getByTestId('home-hero-prompt-slot-text').textContent).toBe('用户的需求说明'));
expect(input.value).toContain(
'把 用户的需求说明 转成时长 10 秒的高级产品工作室音频',
);
expect(input.value).not.toContain('seconds');
});
it('exposes only Speech and Sound effect in the Home Audio workflow', async () => {
stubFetch();
renderHome();
@ -359,6 +450,56 @@ describe('HomeView media composer options', () => {
});
});
it('round-trips localized Audio option labels after textarea edits in Simplified Chinese', async () => {
stubFetch({
elevenLabsVoices: [
{ voiceId: 'voice-rachel', name: 'Rachel', category: 'premade' },
{ voiceId: 'voice-adam', name: 'Adam', category: 'premade' },
],
});
const onSubmit = vi.fn();
renderHomeZh({ onSubmit });
await clickHomeRailChip('audio');
await chooseOption('model', 'elevenlabs-v3');
const input = screen.getByTestId('home-hero-input') as HTMLTextAreaElement;
await waitFor(() => expect(screen.getByTestId('home-hero-footer-option-voice').textContent).toContain('Rachel'));
expect(input.value).toBe('');
await chooseOption('duration', '30');
await chooseOption('voice', 'voice-adam', 'Adam');
await waitFor(() => {
expect(screen.getByTestId('home-hero-footer-option-voice').textContent).toContain('Adam');
expect(screen.getByTestId('home-hero-footer-option-duration').textContent).toContain('30s');
});
fireEvent.change(input, {
target: {
value:
'使用 elevenlabs-v3 和 Adam把 欢迎来到 Open Design 转成时长 30 秒的高级产品工作室音频:精致、克制、清晰,适合品牌使用。',
},
});
await waitFor(() => {
expect(screen.getByTestId('home-hero-prompt-highlight').textContent).toContain('Adam');
expect(screen.getByTestId('home-hero-prompt-slot-text').textContent).toBe('欢迎来到 Open Design');
});
fireEvent.click(screen.getByTestId('home-hero-submit'));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith(expect.objectContaining({
pluginInputs: expect.objectContaining({
duration: 30,
voice: 'voice-adam',
text: '欢迎来到 Open Design',
}),
projectMetadata: expect.objectContaining({
audioDuration: 30,
voice: 'voice-adam',
}),
}));
});
});
it('uses the Audio text input as the audio source and plugin subject', async () => {
stubFetch();
const onSubmit = vi.fn();
@ -415,6 +556,14 @@ function renderHome(overrides: Partial<React.ComponentProps<typeof HomeView>> =
return render(<HomeView {...homeProps(overrides)} />);
}
function renderHomeZh(overrides: Partial<React.ComponentProps<typeof HomeView>> = {}) {
return render(
<I18nProvider initial="zh-CN">
<HomeView {...homeProps(overrides)} />
</I18nProvider>,
);
}
function homeProps(overrides: Partial<React.ComponentProps<typeof HomeView>> = {}): React.ComponentProps<typeof HomeView> {
return {
projects: [],

View file

@ -3,6 +3,7 @@
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { HomeView } from '../../src/components/HomeView';
import { I18nProvider } from '../../src/i18n';
import {
createPluginAuthoringHandoff,
createPluginUseHandoff,
@ -376,6 +377,66 @@ function stubAnimationFrame() {
});
}
const NUMBER_DURATION_PLUGIN = {
...DEFAULT_PLUGIN,
id: 'example-simple-deck',
title: 'Duration parser test',
source: '/tmp/duration-parser',
fsPath: '/tmp/duration-parser',
manifest: {
...DEFAULT_PLUGIN.manifest,
name: 'example-simple-deck',
title: 'Duration parser test',
description: 'Exercise localized number parsing.',
od: {
kind: 'scenario',
taskKind: 'new-generation',
useCase: {
query: 'Create a {{duration}} HyperFrames launch composition for {{product}} with {{motionStyle}} motion.',
},
inputs: [
{
name: 'duration',
label: 'Duration',
type: 'number',
required: true,
default: 10,
},
{
name: 'product',
label: 'Product',
type: 'string',
required: true,
default: 'AI note app',
},
{
name: 'motionStyle',
label: 'Motion style',
type: 'string',
required: true,
default: 'minimal reveal',
},
],
},
},
};
const NUMBER_DURATION_APPLY_RESULT = {
...AUTHORING_APPLY_RESULT,
query: NUMBER_DURATION_PLUGIN.manifest.od.useCase.query,
inputs: NUMBER_DURATION_PLUGIN.manifest.od.inputs,
appliedPlugin: {
...AUTHORING_APPLY_RESULT.appliedPlugin,
snapshotId: 'snap-duration-parser',
pluginId: 'example-simple-deck',
inputs: {
duration: 10,
product: 'AI note app',
motionStyle: 'minimal reveal',
},
},
};
describe('HomeView prompt handoff', () => {
afterEach(() => {
vi.unstubAllGlobals();
@ -1006,6 +1067,72 @@ describe('HomeView prompt handoff', () => {
})));
});
it('keeps localized number inputs numeric when a zh-CN prompt is edited', async () => {
const fetchMock = vi.fn<typeof fetch>(async (url) => {
if (typeof url === 'string' && url === '/api/plugins') {
return new Response(JSON.stringify({ plugins: [NUMBER_DURATION_PLUGIN] }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
if (typeof url === 'string' && url === '/api/mcp/servers') {
return new Response(JSON.stringify({ servers: [], templates: [] }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
if (typeof url === 'string' && url.includes('/apply')) {
return new Response(JSON.stringify(NUMBER_DURATION_APPLY_RESULT), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
throw new Error(`unexpected fetch ${url}`);
});
vi.stubGlobal('fetch', fetchMock);
stubAnimationFrame();
const onSubmit = vi.fn();
render(
<I18nProvider initial="zh-CN">
<HomeView
projects={[]}
onSubmit={onSubmit}
onOpenProject={() => undefined}
onViewAllProjects={() => undefined}
/>
</I18nProvider>,
);
fireEvent.click(await screen.findByTestId('home-hero-rail-deck'));
await waitFor(() => {
expect(screen.getByTestId('home-hero-active-type-chip').textContent).toContain('幻灯片');
});
const input = screen.getByTestId('home-hero-input') as HTMLTextAreaElement;
fireEvent.change(input, {
target: {
value: '为 AI 客服平台 创建一个 10 秒 的 HyperFrames 发布动效,运动风格为 minimal reveal。',
},
});
fireEvent.click(screen.getByTestId('home-hero-submit'));
await waitFor(() => {
expect(fetchMock).toHaveBeenCalledWith(
'/api/plugins/example-simple-deck/apply',
expect.anything(),
);
expect(onSubmit).toHaveBeenCalledWith(expect.objectContaining({
pluginId: 'example-simple-deck',
pluginInputs: expect.objectContaining({
duration: 10,
product: 'AI 客服平台',
}),
}));
});
const submitted = onSubmit.mock.calls[0]?.[0] as { pluginInputs?: Record<string, unknown> } | undefined;
expect(typeof submitted?.pluginInputs?.duration).toBe('number');
});
it('switches output-type chips without replacing an existing prompt', async () => {
const fetchMock = vi.fn<typeof fetch>(async (url) => {
if (typeof url === 'string' && url === '/api/plugins') {

View file

@ -12,6 +12,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { useState } from 'react';
import { PluginInputsForm } from '../../src/components/PluginInputsForm';
import { I18nProvider } from '../../src/i18n';
type OnChange = (values: Record<string, unknown>) => void;
type OnValidityChange = (valid: boolean) => void;
@ -122,6 +123,38 @@ describe('PluginInputsForm', () => {
expect(onChange).toHaveBeenCalledWith({ audioType: 'music' });
});
it('localizes optionLabels-backed select labels in Simplified Chinese', () => {
render(
<I18nProvider initial="zh-CN">
<PluginInputsForm
fields={[
{
name: 'audioType',
label: 'Audio type',
type: 'select',
options: ['speech', 'sfx'],
optionLabels: { speech: 'Speech', sfx: 'Sound effect' },
},
]}
values={{ audioType: 'speech' }}
onChange={onChange}
onValidityChange={onValidityChange}
/>
</I18nProvider>,
);
const select = screen.getByLabelText('音频类型') as HTMLSelectElement;
expect(select.value).toBe('speech');
expect(screen.getByText('语音')).toBeTruthy();
expect(screen.getByText('音效')).toBeTruthy();
expect(screen.queryByText('Speech')).toBeNull();
expect(screen.queryByText('Sound effect')).toBeNull();
fireEvent.change(select, { target: { value: 'sfx' } });
expect(onChange).toHaveBeenCalledWith({ audioType: 'sfx' });
});
it('renders file inputs as upload slots with serializable metadata', () => {
render(
<PluginInputsForm

View file

@ -8,6 +8,7 @@ import type {
} from '@open-design/contracts';
import { TasksView } from '../../src/components/TasksView';
import { I18nProvider } from '../../src/i18n';
const originalFetch = globalThis.fetch;
@ -162,6 +163,171 @@ describe('TasksView automation templates', () => {
});
});
it('localizes zh-CN proposal review policy labels for contract values', async () => {
const proposals: AutomationEvolutionProposal[] = [
{
...memoryProposal,
id: 'proposal-trusted-source-1',
title: 'Trusted connector update',
reviewPolicy: 'trusted-source',
},
{
...memoryProposal,
id: 'proposal-auto-apply-1',
title: 'Auto-apply release note',
reviewPolicy: 'auto-apply',
},
];
globalThis.fetch = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
const url = input.toString();
if (url === '/api/routines' && (!init || init.method === undefined)) {
return new Response(JSON.stringify({ routines: [] }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
if (url === '/api/projects' && (!init || init.method === undefined)) {
return new Response(JSON.stringify({ projects: [] }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
if (url === '/api/automation-templates' && (!init || init.method === undefined)) {
return new Response(JSON.stringify({ templates: [] }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
if (url === '/api/automation-proposals?status=pending-review' && (!init || init.method === undefined)) {
return new Response(JSON.stringify({ proposals }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
return new Response(JSON.stringify({}), { status: 404 });
}) as typeof fetch;
render(
<I18nProvider initial="zh-CN">
<TasksView />
</I18nProvider>,
);
expect(await screen.findByText('Trusted connector update')).toBeTruthy();
expect(screen.getByText('Auto-apply release note')).toBeTruthy();
expect(screen.getByText('可信来源')).toBeTruthy();
expect(screen.getByText('自动应用')).toBeTruthy();
expect(screen.queryByText('trusted-source')).toBeNull();
expect(screen.queryByText('auto-apply')).toBeNull();
});
it('ingests pasted source content into source packets and proposals', async () => {
const postBodies: unknown[] = [];
let proposals: AutomationEvolutionProposal[] = [];
let packets = [] as Array<{
id: string;
sourceKind: string;
title: string;
capturedAt: string;
tokenStats: { originalTokens: number };
}>;
globalThis.fetch = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
const url = input.toString();
if (url === '/api/routines' && (!init || init.method === undefined)) {
return new Response(JSON.stringify({ routines: [] }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
if (url === '/api/projects' && (!init || init.method === undefined)) {
return new Response(JSON.stringify({ projects: [] }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
if (url === '/api/automation-templates' && (!init || init.method === undefined)) {
return new Response(JSON.stringify({ templates: [daemonTemplate] }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
if (url === '/api/automation-proposals?status=pending-review' && (!init || init.method === undefined)) {
return new Response(JSON.stringify({ proposals }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
if (url === '/api/automation-source-packets?limit=3' && (!init || init.method === undefined)) {
return new Response(JSON.stringify({ packets }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
if (url === '/api/automation-ingestions' && init?.method === 'POST') {
postBodies.push(JSON.parse(String(init.body)));
const packet = {
id: 'packet-1',
sourceKind: 'repo',
sourceRef: 'https://github.com/acme/design',
title: 'Acme source',
capturedAt: '2026-05-18T00:00:00.000Z',
bodyMarkdown: 'Primary color #335CFF',
provenance: [],
attachments: [],
sensitivity: 'workspace',
capabilityHints: [],
tokenStats: { originalTokens: 6 },
candidateSinks: ['memory', 'design-system'],
};
proposals = [{ ...memoryProposal, id: 'proposal-ingested-1', title: 'Memory: Acme source' }];
packets = [packet];
return new Response(JSON.stringify({
packet,
compressionReport: {
mode: 'balanced',
status: 'skipped',
beforeTokens: 6,
afterTokens: 6,
summary: 'Already compact',
preservedSourcePacketId: 'packet-1',
},
proposals,
}), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
return new Response(JSON.stringify({}), { status: 404 });
}) as typeof fetch;
render(<TasksView />);
await screen.findByText('Ingest source');
fireEvent.change(screen.getByLabelText('Title'), {
target: { value: 'Acme source' },
});
fireEvent.change(screen.getByLabelText('Source ref'), {
target: { value: 'https://github.com/acme/design' },
});
fireEvent.change(screen.getByLabelText('Content'), {
target: { value: 'Primary color #335CFF' },
});
fireEvent.click(screen.getByRole('button', { name: /^Ingest$/i }));
await waitFor(() => {
expect(postBodies).toHaveLength(1);
expect(postBodies[0]).toMatchObject({
templateId: 'ingest-source-memory-tree',
sourceKind: 'connector',
title: 'Acme source',
sourceRef: 'https://github.com/acme/design',
bodyMarkdown: 'Primary color #335CFF',
});
expect(screen.getByText('Memory: Acme source')).toBeTruthy();
expect(screen.getByText('Acme source')).toBeTruthy();
});
});
it('crystallizes a successful automation run into reviewable proposals', async () => {
const crystallizeCalls: string[] = [];
let proposals: AutomationEvolutionProposal[] = [];

View file

@ -24,6 +24,23 @@ vi.mock('../../src/i18n', () => ({
'entry.navDesignSystems': 'Design systems',
'entry.navHome': 'Home',
'entry.navProjects': 'Projects',
'entry.navAutomations': 'Automations',
'entry.navPlugins': 'Plugins',
'entry.navIntegrations': 'Integrations',
'entry.metaProject': 'Project',
'entry.metaPlugins': 'Plugins',
'entry.metaWorkspace': 'Workspace',
'entry.metaStartProject': 'Start a new project',
'entry.noTabsFound': 'No tabs found',
'entry.pluginDetailsTitle': 'Plugin details',
'entry.marketplaceTitle': 'Marketplace',
'entry.workspaceTabsAria': 'Workspace tabs',
'entry.openWorkspacesAria': 'Open workspaces',
'entry.showHiddenTabs': 'Show hidden tabs',
'entry.moreTabs': '{count} more',
'entry.newTab': 'New tab',
'entry.searchTabs': 'Search tabs',
'entry.openTabs': 'Open tabs',
};
return labels[key] ?? key;
},

View file

@ -73,6 +73,19 @@ describe('localized resource content', () => {
expect(localizeDesignSystemSummary('fr', englishOnlySystem)).toBe(' English summary from source. ');
});
it('keeps source design system summaries for zh-CN until per-system copy exists', () => {
const englishOnlySystem = {
id: 'reviewer-only-system',
title: 'Reviewer Only',
summary: 'Specific dashboard terms from the source summary.',
category: 'Productivity & SaaS',
} as DesignSystemSummary;
expect(localizeDesignSystemSummary('zh-CN', englishOnlySystem)).toBe(
'Specific dashboard terms from the source summary.',
);
});
it('prefers localized prompt template fields and falls back to english fields and tags', () => {
const translatedTemplate = {
id: '3d-stone-staircase-evolution-infographic',

View file

@ -231,4 +231,13 @@ describe('i18n locales', () => {
'If you need to add new keys, declare them with their Chinese values directly.',
).not.toMatch(/\.\.\.en\b/);
});
it('keeps Simplified Chinese manual editor copy translated', () => {
const manualEditKeys = Object.keys(en)
.filter((key): key is keyof Dict => key.startsWith('manualEdit.'));
for (const key of manualEditKeys) {
expect(zhCN[key], key).not.toBe(en[key]);
}
});
});