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 的动态内容:字幕、音频响应视觉和场景转场。'
+ );
+});
+
});