Skip to content

Commit fa8753b

Browse files
baozhoutaoclaude
andauthored
feat(console): i18n the system-settings hub (objectui#2851 P2) (#2859)
SettingsHub rendered its title/subtitle, category section headers, card titles/descriptions and the "N settings" count as hardcoded English, so the 系统设置总览 page stayed English under zh. - Wire SettingsHub chrome to `console.settingsHub.*` i18n keys (en + zh). - Extract a <SettingCard> that resolves each manifest's translated title/description via the existing `useSettingsLabel` convention, so already-translated namespaces (mail/branding/…) light up immediately. - Translate category section headers by the manifest `category` value, falling back to the literal for plugin-authored categories. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7b35e4b commit fa8753b

3 files changed

Lines changed: 92 additions & 32 deletions

File tree

apps/console/src/pages/settings/SettingsHub.tsx

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,65 @@ import {
1616
Badge,
1717
Skeleton,
1818
} from '@object-ui/components';
19+
import { useObjectTranslation } from '@object-ui/i18n';
1920
import { Settings as SettingsIcon } from 'lucide-react';
2021
import { getIcon } from '../../utils/getIcon';
2122
import { listSettingsManifests } from './api';
2223
import { resolveLabel, type SettingsManifest } from './types';
24+
import { useSettingsLabel } from './useSettingsLabel';
25+
26+
/**
27+
* One manifest card. Extracted so it can resolve the manifest's own
28+
* translated title/description via {@link useSettingsLabel} (hooks can't be
29+
* called inside a `.map()` callback in the parent).
30+
*/
31+
function SettingCard({ m, onOpen }: { m: SettingsManifest; onOpen: () => void }) {
32+
const { t } = useObjectTranslation();
33+
const labels = useSettingsLabel(m.namespace);
34+
const Icon = m.icon ? getIcon(m.icon) : SettingsIcon;
35+
const literalLabel = resolveLabel(m.label);
36+
const title = labels.title(literalLabel);
37+
const description = labels.description(m.description ?? undefined);
38+
39+
return (
40+
<Card
41+
className="cursor-pointer hover:border-primary/50 hover:shadow-sm transition-all"
42+
onClick={onOpen}
43+
>
44+
<CardHeader className="pb-3">
45+
<div className="flex items-start justify-between">
46+
<Icon className="h-6 w-6 text-muted-foreground" />
47+
{m.beta ? (
48+
<Badge variant="secondary" className="text-[10px]">
49+
{t('console.settingsHub.beta')}
50+
</Badge>
51+
) : null}
52+
</div>
53+
<CardTitle className="text-base mt-2">{title}</CardTitle>
54+
{description ? <CardDescription className="text-xs">{description}</CardDescription> : null}
55+
</CardHeader>
56+
<CardContent className="pt-0">
57+
<div className="text-[11px] text-muted-foreground">
58+
{t('console.settingsHub.settingsCount', { n: m.specifiers.length })}
59+
</div>
60+
</CardContent>
61+
</Card>
62+
);
63+
}
2364

2465
export function SettingsHub() {
2566
const navigate = useNavigate();
67+
const { t } = useObjectTranslation();
2668
const [manifests, setManifests] = useState<SettingsManifest[] | null>(null);
2769
const [error, setError] = useState<string | null>(null);
2870

2971
useEffect(() => {
3072
listSettingsManifests()
3173
.then((r) => setManifests(r.manifests ?? []))
32-
.catch((err) => setError(err?.message ?? 'Failed to load settings'));
33-
}, []);
74+
.catch((err) =>
75+
setError(err?.message ?? t('console.settingsHub.loadError')),
76+
);
77+
}, [t]);
3478

3579
const byCategory = useMemo(() => {
3680
if (!manifests) return null;
@@ -49,8 +93,8 @@ export function SettingsHub() {
4993
<div className="flex items-center gap-3 mb-6">
5094
<SettingsIcon className="h-7 w-7 text-muted-foreground" />
5195
<div>
52-
<h1 className="text-2xl font-semibold tracking-tight">Settings</h1>
53-
<p className="text-sm text-muted-foreground">Configure your workspace, integrations, and feature flags.</p>
96+
<h1 className="text-2xl font-semibold tracking-tight">{t('console.settingsHub.title')}</h1>
97+
<p className="text-sm text-muted-foreground">{t('console.settingsHub.subtitle')}</p>
5498
</div>
5599
</div>
56100

@@ -72,42 +116,23 @@ export function SettingsHub() {
72116
) : manifests.length === 0 ? (
73117
<Card>
74118
<CardContent className="py-12 text-center text-sm text-muted-foreground">
75-
No settings registered. Plugins can register settings manifests via the SettingsService.
119+
{t('console.settingsHub.empty')}
76120
</CardContent>
77121
</Card>
78122
) : (
79123
Array.from(byCategory ?? []).map(([category, items]) => (
80124
<section key={category} className="mb-8">
81125
<h2 className="text-sm font-semibold tracking-wide uppercase text-muted-foreground mb-3">
82-
{category}
126+
{t(`console.settingsHub.categories.${category}`, { defaultValue: category })}
83127
</h2>
84128
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
85-
{items.map((m) => {
86-
const Icon = m.icon ? getIcon(m.icon) : SettingsIcon;
87-
return (
88-
<Card
89-
key={m.namespace}
90-
className="cursor-pointer hover:border-primary/50 hover:shadow-sm transition-all"
91-
onClick={() => navigate(`/system/settings/${m.namespace}`)}
92-
>
93-
<CardHeader className="pb-3">
94-
<div className="flex items-start justify-between">
95-
<Icon className="h-6 w-6 text-muted-foreground" />
96-
{m.beta ? <Badge variant="secondary" className="text-[10px]">Beta</Badge> : null}
97-
</div>
98-
<CardTitle className="text-base mt-2">{resolveLabel(m.label)}</CardTitle>
99-
{m.description ? (
100-
<CardDescription className="text-xs">{m.description}</CardDescription>
101-
) : null}
102-
</CardHeader>
103-
<CardContent className="pt-0">
104-
<div className="text-[11px] text-muted-foreground">
105-
{m.specifiers.length} setting{m.specifiers.length === 1 ? '' : 's'}
106-
</div>
107-
</CardContent>
108-
</Card>
109-
);
110-
})}
129+
{items.map((m) => (
130+
<SettingCard
131+
key={m.namespace}
132+
m={m}
133+
onOpen={() => navigate(`/system/settings/${m.namespace}`)}
134+
/>
135+
))}
111136
</div>
112137
</section>
113138
))

packages/i18n/src/locales/en.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,24 @@ const en = {
12261226
dragToReorder: 'Drag to reorder',
12271227
favorites: 'Favorites',
12281228
},
1229+
settingsHub: {
1230+
title: 'Settings',
1231+
subtitle: 'Configure your workspace, integrations, and feature flags.',
1232+
loadError: 'Failed to load settings',
1233+
empty: 'No settings registered. Plugins can register settings manifests via the SettingsService.',
1234+
settingsCount: '{{n}} settings',
1235+
beta: 'Beta',
1236+
// Section headers, keyed by the manifest `category` value. Unknown
1237+
// (plugin-authored) categories fall back to the literal category string.
1238+
categories: {
1239+
Workspace: 'Workspace',
1240+
Communication: 'Communication',
1241+
Security: 'Security',
1242+
Infrastructure: 'Infrastructure',
1243+
Beta: 'Beta',
1244+
Other: 'Other',
1245+
},
1246+
},
12291247
loadingSteps: {
12301248
connecting: 'Connecting to data source',
12311249
loadingConfig: 'Loading configuration',

packages/i18n/src/locales/zh.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,6 +1302,23 @@ const zh = {
13021302
dragToReorder: '拖动以重新排序',
13031303
favorites: '收藏',
13041304
},
1305+
settingsHub: {
1306+
title: '设置',
1307+
subtitle: '配置工作区、集成与功能开关。',
1308+
loadError: '加载设置失败',
1309+
empty: '尚未注册任何设置。插件可通过 SettingsService 注册设置清单。',
1310+
settingsCount: '{{n}} 项设置',
1311+
beta: 'Beta',
1312+
// 分类小标题,按 manifest 的 `category` 值取键;未知(插件自定义)分类回退到原始字符串。
1313+
categories: {
1314+
Workspace: '工作区',
1315+
Communication: '通讯',
1316+
Security: '安全',
1317+
Infrastructure: '基础设施',
1318+
Beta: 'Beta',
1319+
Other: '其他',
1320+
},
1321+
},
13051322
loadingSteps: {
13061323
connecting: '正在连接数据源',
13071324
loadingConfig: '正在加载配置',

0 commit comments

Comments
 (0)