diff --git a/apps/web/src/components/HomeHero.tsx b/apps/web/src/components/HomeHero.tsx index 8023d67b68..09a348c4d2 100644 --- a/apps/web/src/components/HomeHero.tsx +++ b/apps/web/src/components/HomeHero.tsx @@ -1511,6 +1511,7 @@ function TypeTabBar({ onPickChip, }: TypeTabBarProps) { const chips = useMemo(() => chipsForGroup('create'), []); + const t = useT(); return (
{chips.map((chip) => { @@ -1530,9 +1531,9 @@ function TypeTabBar({ onClick={() => onPickChip(chip)} disabled={pluginsLoading || isPending || pendingPluginId !== null} aria-selected={isActive} - title={chip.hint ?? chip.label} + title={homeHeroChipTitle(chip, t)} > - {chip.label} + {chip.group === 'create' ? t(chip.labelKey) : homeHeroChipLabel(chip.id, t)} ); })} diff --git a/apps/web/src/components/home-hero/chips.ts b/apps/web/src/components/home-hero/chips.ts index f847ab18bc..c896cf9193 100644 --- a/apps/web/src/components/home-hero/chips.ts +++ b/apps/web/src/components/home-hero/chips.ts @@ -22,6 +22,7 @@ import type { ProjectKind } from '@open-design/contracts'; import type { DefaultScenarioPluginId } from '@open-design/contracts'; import type { IconName } from '../Icon'; +import type { Dict } from '../../i18n/types'; // Plugin ids the chip rail can dispatch to. Most chips route to a // `DefaultScenarioPluginId` so the same fallback table the daemon @@ -59,19 +60,28 @@ export type ChipAction = // narrow viewports without horizontal scrolling. export type ChipGroup = 'create' | 'migrate'; -export interface HomeHeroChip { +export interface CreateChip { id: string; - label: string; + labelKey: keyof Dict; icon: IconName; - group: ChipGroup; - hint?: string; + group: 'create'; action: ChipAction; } +export interface MigrateChip { + id: string; + icon: IconName; + group: 'migrate'; + hint: string; + action: ChipAction; +} + +export type HomeHeroChip = CreateChip | MigrateChip; + export const HOME_HERO_CHIPS: ReadonlyArray = [ { id: 'prototype', - label: 'Prototype', + labelKey: 'homeHero.chip.prototype', icon: 'palette', group: 'create', // Prototype now binds to the bundled `example-web-prototype` plugin, @@ -91,7 +101,7 @@ export const HOME_HERO_CHIPS: ReadonlyArray = [ }, { 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 @@ -112,7 +122,7 @@ export const HOME_HERO_CHIPS: ReadonlyArray = [ }, { id: 'image', - label: 'Image', + labelKey: 'homeHero.chip.image', icon: 'image', group: 'create', action: { @@ -129,7 +139,7 @@ export const HOME_HERO_CHIPS: ReadonlyArray = [ }, { id: 'video', - label: 'Video', + labelKey: 'homeHero.chip.video', icon: 'play', group: 'create', action: { @@ -146,7 +156,7 @@ export const HOME_HERO_CHIPS: ReadonlyArray = [ }, { id: 'hyperframes', - label: 'HyperFrames', + labelKey: 'homeHero.chip.hyperframes', icon: 'orbit', group: 'create', hint: 'Author HTML-based motion: captions, audio-reactive visuals, scene transitions.', @@ -158,7 +168,7 @@ export const HOME_HERO_CHIPS: ReadonlyArray = [ }, { id: 'audio', - label: 'Audio', + labelKey: 'homeHero.chip.audio', icon: 'mic', group: 'create', action: { @@ -175,7 +185,6 @@ export const HOME_HERO_CHIPS: ReadonlyArray = [ }, { id: 'create-plugin', - label: 'Create plugin', icon: 'edit', group: 'migrate', hint: 'Author a reusable Open Design plugin and add it to My plugins.', @@ -183,7 +192,6 @@ export const HOME_HERO_CHIPS: ReadonlyArray = [ }, { id: 'figma', - label: 'From Figma', icon: 'import', group: 'migrate', hint: 'Migrate a Figma frame into the active design system.', @@ -199,7 +207,6 @@ export const HOME_HERO_CHIPS: ReadonlyArray = [ }, { id: 'folder', - label: 'From folder', icon: 'folder', group: 'migrate', hint: 'Import an existing local folder and continue editing.', @@ -207,7 +214,6 @@ export const HOME_HERO_CHIPS: ReadonlyArray = [ }, { id: 'template', - label: 'From template', icon: 'file-code', group: 'migrate', hint: 'Start from a bundled template.', diff --git a/apps/web/src/i18n/locales/zh-CN.ts b/apps/web/src/i18n/locales/zh-CN.ts index b668eed3ae..9e2d29d68e 100644 --- a/apps/web/src/i18n/locales/zh-CN.ts +++ b/apps/web/src/i18n/locales/zh-CN.ts @@ -352,7 +352,7 @@ export const zhCN: Dict = { 'recentProjects.title': '最近项目', 'recentProjects.viewAll': '查看全部', 'recentProjects.empty': '还没有项目 — 输入 Prompt 即可开始。', - 'pluginsHome.title': '官方 starter', + 'pluginsHome.title': '官方入门', 'pluginsHome.subtitle': '当前运行环境内置的 Open Design 工作流。选择一个加载 starter prompt,或浏览插件市场查看更多。', 'pluginsHome.browseRegistry': '浏览插件市场', 'pluginsHome.count': '{filtered} / {total}', diff --git a/apps/web/tests/components/HomeHero.rail.test.tsx b/apps/web/tests/components/HomeHero.rail.test.tsx index 9d87c37e03..ed7c6b71e8 100644 --- a/apps/web/tests/components/HomeHero.rail.test.tsx +++ b/apps/web/tests/components/HomeHero.rail.test.tsx @@ -11,6 +11,7 @@ import { cleanup, fireEvent, render, screen } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; +import { I18nProvider } from '../../src/i18n'; import { HomeHero } from '../../src/components/HomeHero'; import { HOME_HERO_CHIPS, @@ -135,4 +136,38 @@ describe('HomeHero intent rail', () => { }); expect(findChip('live-artifact')).toBeUndefined(); }); + + it('renders localized chip labels and tooltips in zh-CN', () => { + render( + + undefined} + onSubmit={() => undefined} + activePluginTitle={null} + activeChipId={null} + onClearActivePlugin={() => undefined} + pluginOptions={[]} + pluginsLoading={false} + pendingPluginId={null} + pendingChipId={null} + onPickPlugin={vi.fn()} + onPickChip={vi.fn()} + contextItemCount={0} + error={null} + /> + + ); + + // Prototype chip label should be zh-CN + const prototypeChip = screen.getByTestId('home-hero-rail-prototype'); + expect(prototypeChip.textContent).toContain('原型'); + + // HyperFrames tooltip should be the zh-CN string + const hyperframes = screen.getByTestId('home-hero-rail-hyperframes'); + expect(hyperframes.getAttribute('title')).toBe( + '创作基于 HTML 的动态内容:字幕、音频响应视觉和场景转场。' + ); +}); + });