diff --git a/apps/web/src/components/AssistantMessage.tsx b/apps/web/src/components/AssistantMessage.tsx index 26bed5f74..e7086aadf 100644 --- a/apps/web/src/components/AssistantMessage.tsx +++ b/apps/web/src/components/AssistantMessage.tsx @@ -1421,19 +1421,20 @@ function PluginActionPanel({ ) => Promise<{ message?: string; url?: string } | void> | { message?: string; url?: string } | void; activePluginActionPaths?: Set; }) { + const t = useT(); const noticeByFolder = notices; const runAction = onRunAction; return ( -
+
-
Plugin ready
+
{t("assistant.pluginAction.title")}
- Send the next step to the agent so it can run the od CLI. + {t("assistant.pluginAction.subtitle")}
@@ -1441,20 +1442,20 @@ function PluginActionPanel({ {folders.map((folder) => { const actionBusy = activePluginActionPaths.has(folder.path); return ( -
-
- - - -
- {folder.path} - {folder.fileCount} files ready for My plugins +
+
+ + + +
+ {folder.path} + {t("assistant.pluginAction.filesReady", { count: folder.fileCount })} +
-
{onRequestOpenFile ? ( @@ -1511,17 +1516,18 @@ function PluginActionPanel({ onClick={() => onRequestOpenFile(folder.manifestPath)} > - Open manifest + {t("assistant.pluginAction.openManifest")} ) : null}
- {noticeByFolder[folder.path] ? ( -
- -
- ) : null} -
- )})} + {noticeByFolder[folder.path] ? ( +
+ +
+ ) : null} +
+ ); + })}
); diff --git a/apps/web/src/components/ChatComposer.tsx b/apps/web/src/components/ChatComposer.tsx index d63300c89..143acd1eb 100644 --- a/apps/web/src/components/ChatComposer.tsx +++ b/apps/web/src/components/ChatComposer.tsx @@ -2583,6 +2583,7 @@ function ToolsPluginsPanel({ onApply: (record: InstalledPluginRecord) => void | Promise; onShowDetails: (record: InstalledPluginRecord) => void; }) { + const t = useT(); const [pendingId, setPendingId] = useState(null); const [source, setSource] = useState<'community' | 'mine'>('community'); const [query, setQuery] = useState(''); @@ -2603,16 +2604,16 @@ function ToolsPluginsPanel({ return ( <>
-
+
setQuery(e.currentTarget.value)} - placeholder="Search plugins…" - aria-label="Search plugins" + placeholder={t('chat.tools.searchPlugins')} + aria-label={t('pluginsHome.searchAria')} />
{visiblePlugins.length === 0 ? (
{plugins.length === 0 ? ( <> - No plugins installed yet. Browse Official or add your own with{' '} + {t('chat.tools.noPluginsInstalled')}{' '} od plugin install <source>. ) : 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'), + })} + )}
) : ( @@ -2687,15 +2697,15 @@ function ToolsPluginsPanel({ )} {pendingId === p.id ? ( - Applying… + {t('chat.tools.applying')} ) : null} @@ -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')} />
{visibleServers.length === 0 ? (
{servers.length === 0 - ? 'No enabled MCP servers configured yet.' - : `No configured MCP results for “${query}”.`} + ? t('chat.tools.noMcpServers') + : t('chat.tools.noMcpResults', { query })}
) : (
-
Configured
+
{t('chat.tools.configured')}
{visibleServers.map((s) => ( ); @@ -2817,6 +2828,7 @@ function ToolsSkillsPanel({ onPick: (skill: SkillSummary) => void | Promise; }) { const { locale } = useI18n(); + const t = useT(); const [query, setQuery] = useState(''); const [pendingId, setPendingId] = useState(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')} />
{visibleSkills.length === 0 ? (
- {skills.length === 0 ? 'No skills available yet.' : `No skills found for “${query}”.`} + {skills.length === 0 ? t('chat.tools.noSkills') : t('chat.tools.noSkillResults', { query })}
) : (
@@ -2872,7 +2884,7 @@ function ToolsSkillsPanel({ {pendingId === skill.id ? ( - Applying… + {t('chat.tools.applying')} ) : null} ); @@ -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(null); const [tab, setTab] = useState('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 (
-
+
{tabs.map((item) => ( ); })} @@ -3209,7 +3222,7 @@ function MentionPopover({ ) : null} {showMcp && mcpServers.length > 0 ? ( <> -
{t('chat.mentionSectionMcp')}
+
{t('chat.mention.mcp')}
{mcpServers.map((server) => (
- {pickStarters(projectMetadata, t).map((ex, i) => ( + {pickStarters(projectMetadata, t, locale).map((ex, i) => ( @@ -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')}
) : null} diff --git a/apps/web/src/components/DesignSystemFlow.tsx b/apps/web/src/components/DesignSystemFlow.tsx index ee9a4e299..989b0ddcf 100644 --- a/apps/web/src/components/DesignSystemFlow.tsx +++ b/apps/web/src/components/DesignSystemFlow.tsx @@ -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('setup'); const [state, setState] = useState(EMPTY_SETUP); const [error, setError] = useState(null); @@ -311,6 +313,7 @@ export function DesignSystemCreationFlow({ const [githubAuthorizationUrl, setGithubAuthorizationUrl] = useState(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 (
-

It will take about 5 minutes to generate your design system.

-

You can step away. Keep the tab open in the background.

+

{t('ds.setupConfirmTitle')}

+

{t('ds.setupConfirmBody')}

@@ -689,7 +696,7 @@ export function DesignSystemCreationFlow({
@@ -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')}
)}
-

Generate from your material

-

Start with a short description, then add any source files you already have.

+

{t('ds.setupTitle')}

+

{t('ds.setupBody')}