Skip to content

Commit 90fa0e0

Browse files
ericyangpanclaude
andcommitted
refactor(components): implement i18n with consistent translation namespaces
Update all product and navigation components to use translation hooks: - Breadcrumb: Add home translation key - GitHubStarHistory: Move all hardcoded strings to translation namespace - PlatformLinks: Internalize links config and add translations - ProductHero: Remove labels prop, use translation hook, simplify type handling - VendorProducts: Remove title and locale props, use translation hook - VendorModels: Remove title and locale props, use translation hook 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 8076fdb commit 90fa0e0

File tree

6 files changed

+59
-67
lines changed

6 files changed

+59
-67
lines changed

src/components/navigation/Breadcrumb.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client'
22

3+
import { useTranslations } from 'next-intl'
34
import { useEffect, useRef } from 'react'
45
import { JsonLd } from '@/components/JsonLd'
56
import { Link } from '@/i18n/navigation'
@@ -17,6 +18,7 @@ export interface BreadcrumbItem {
1718
* - Sticky behavior is enabled when scrolling using CSS position: sticky.
1819
*/
1920
export function Breadcrumb({ items }: { items: BreadcrumbItem[] }) {
21+
const t = useTranslations()
2022
const sectionRef = useRef<HTMLElement>(null)
2123

2224
// Dynamically adjust sticky position based on header height
@@ -48,7 +50,7 @@ export function Breadcrumb({ items }: { items: BreadcrumbItem[] }) {
4850
{
4951
'@type': 'ListItem',
5052
position: 1,
51-
name: 'Home',
53+
name: t('components.breadcrumb.home'),
5254
item: SITE_CONFIG.url,
5355
},
5456
...items.map((item, index) => {

src/components/product/GitHubStarHistory.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client'
22

3+
import { useTranslations } from 'next-intl'
34
import { useEffect, useState } from 'react'
45
import {
56
CartesianGrid,
@@ -30,6 +31,7 @@ interface StarHistoryApiResponse {
3031
}
3132

3233
export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) {
34+
const t = useTranslations('components.githubStarHistory')
3335
const [data, setData] = useState<StarDataPoint[]>([])
3436
const [loading, setLoading] = useState(true)
3537
const [error, setError] = useState<string | null>(null)
@@ -110,9 +112,7 @@ export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) {
110112
<div className="max-w-8xl mx-auto px-[var(--spacing-md)]">
111113
<div className="border border-[var(--color-border)] p-[var(--spacing-md)]">
112114
<div className="flex items-center justify-center h-[300px]">
113-
<p className="text-sm text-[var(--color-text-muted)] animate-pulse">
114-
Loading star history...
115-
</p>
115+
<p className="text-sm text-[var(--color-text-muted)] animate-pulse">{t('loading')}</p>
116116
</div>
117117
</div>
118118
</div>
@@ -131,10 +131,10 @@ export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) {
131131
{/* Header */}
132132
<div className="mb-[var(--spacing-md)]">
133133
<h2 className="text-lg font-semibold tracking-tight mb-[var(--spacing-xs)]">
134-
GitHub Star History
134+
{t('title')}
135135
</h2>
136136
<p className="text-sm text-[var(--color-text-secondary)] font-light">
137-
Star growth trend over time
137+
{t('description')}
138138
</p>
139139
</div>
140140

@@ -187,8 +187,8 @@ export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) {
187187
color: 'var(--color-text-secondary)',
188188
}}
189189
formatter={(value: number | undefined) => [
190-
`${value?.toLocaleString() ?? '0'} stars`,
191-
'Stars',
190+
`${value?.toLocaleString() ?? '0'} ${t('stars')}`,
191+
t('stars'),
192192
]}
193193
/>
194194
<Line
@@ -212,7 +212,7 @@ export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) {
212212
{error && (
213213
<div className="mt-[var(--spacing-sm)] pt-[var(--spacing-sm)] border-t border-[var(--color-border)]">
214214
<p className="text-xs text-[var(--color-text-muted)] font-light">
215-
Note: Displaying sample data. Star history API temporarily unavailable.
215+
{t('fallbackNote')}
216216
</p>
217217
</div>
218218
)}

src/components/product/PlatformLinks.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1+
import { useTranslations } from 'next-intl'
12
import { LinkCardGrid } from '@/components/product/LinkCard'
23
import type { ManifestPlatformUrls } from '@/types/manifests'
34

45
export interface PlatformLinksProps {
56
platformUrls: ManifestPlatformUrls | null | undefined
6-
title: string
7-
links: Array<{
8-
key: string
9-
title: string
10-
description: string
11-
}>
127
layout?: 'horizontal' | 'vertical'
138
gridCols?: string
149
}
@@ -22,18 +17,36 @@ export interface PlatformLinksProps {
2217
*/
2318
export function PlatformLinks({
2419
platformUrls,
25-
title,
26-
links,
2720
layout = 'horizontal',
2821
gridCols = 'grid-cols-1 md:grid-cols-3',
2922
}: PlatformLinksProps) {
30-
if (!platformUrls || links.length === 0) {
23+
const t = useTranslations('components.platformLinks')
24+
25+
if (!platformUrls) {
3126
return null
3227
}
3328

29+
const links = [
30+
{
31+
key: 'huggingface',
32+
title: t('aiPlatforms.huggingface.title'),
33+
description: t('aiPlatforms.huggingface.description'),
34+
},
35+
{
36+
key: 'artificialAnalysis',
37+
title: t('aiPlatforms.artificialAnalysis.title'),
38+
description: t('aiPlatforms.artificialAnalysis.description'),
39+
},
40+
{
41+
key: 'openrouter',
42+
title: t('aiPlatforms.openrouter.title'),
43+
description: t('aiPlatforms.openrouter.description'),
44+
},
45+
]
46+
3447
return (
3548
<LinkCardGrid
36-
title={title}
49+
title={t('title')}
3750
links={links}
3851
urls={platformUrls}
3952
layout={layout}

src/components/product/ProductHero.tsx

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface ProductHeroProps {
3636

3737
// Type (for Providers)
3838
type?: string
39+
typeValue?: string // Translated type value (e.g., "Foundation Model Provider")
3940

4041
// Links
4142
websiteUrl?: string
@@ -50,21 +51,6 @@ export interface ProductHeroProps {
5051
url: string
5152
icon?: string
5253
}[]
53-
54-
// i18n labels
55-
labels?: {
56-
vendor?: string
57-
version?: string
58-
license?: string
59-
stars?: string
60-
platforms?: string
61-
type?: string
62-
typeValue?: string // Translated type value (e.g., "Foundation Model Provider")
63-
visitWebsite?: string
64-
documentation?: string
65-
download?: string
66-
getApiKey?: string
67-
}
6854
}
6955

7056
export function ProductHero({
@@ -81,15 +67,16 @@ export function ProductHero({
8167
showAllPlatforms = false,
8268
additionalInfo,
8369
type,
70+
typeValue,
8471
websiteUrl,
8572
githubUrl,
8673
docsUrl,
8774
downloadUrl,
8875
applyKeyUrl,
8976
additionalUrls,
90-
labels = {},
9177
}: ProductHeroProps) {
92-
const t = useTranslations()
78+
const t = useTranslations('components.productHero')
79+
9380
// Determine which platforms to display
9481
const displayPlatforms = platforms
9582
? showAllPlatforms
@@ -129,7 +116,7 @@ export function ProductHero({
129116
<div className="inline-flex items-center gap-[var(--spacing-sm)] flex-wrap px-[var(--spacing-md)] py-[var(--spacing-md)] border border-[var(--color-border)] text-sm">
130117
{vendor && (
131118
<div className="flex gap-1">
132-
<span className="text-[var(--color-text-muted)]">{labels.vendor || 'Vendor'}:</span>
119+
<span className="text-[var(--color-text-muted)]">{t('vendor')}:</span>
133120
<span className="font-medium">{vendor}</span>
134121
</div>
135122
)}
@@ -138,9 +125,7 @@ export function ProductHero({
138125

139126
{latestVersion && (
140127
<div className="flex gap-1">
141-
<span className="text-[var(--color-text-muted)]">
142-
{labels.version || 'Version'}:
143-
</span>
128+
<span className="text-[var(--color-text-muted)]">{t('version')}:</span>
144129
<span className="font-medium">{latestVersion}</span>
145130
</div>
146131
)}
@@ -149,9 +134,7 @@ export function ProductHero({
149134

150135
{license && (
151136
<div className="flex gap-1">
152-
<span className="text-[var(--color-text-muted)]">
153-
{labels.license || 'License'}:
154-
</span>
137+
<span className="text-[var(--color-text-muted)]">{t('license')}:</span>
155138
{renderLicense(
156139
license,
157140
'!font-medium hover:underline hover:decoration-dotted transition-colors underline-offset-2',
@@ -166,7 +149,7 @@ export function ProductHero({
166149

167150
{githubStars !== null && githubStars !== undefined && (
168151
<div className="flex gap-1">
169-
<span className="text-[var(--color-text-muted)]">{labels.stars || 'Stars'}:</span>
152+
<span className="text-[var(--color-text-muted)]">{t('stars')}:</span>
170153
<span className="font-medium">{githubStars}k</span>
171154
</div>
172155
)}
@@ -208,7 +191,7 @@ export function ProductHero({
208191
<div className="flex justify-center mb-[var(--spacing-lg)]">
209192
<div className="inline-flex items-center gap-[var(--spacing-xs)] px-[var(--spacing-md)] py-[var(--spacing-sm)] bg-[var(--color-hover)] border border-[var(--color-border)]">
210193
<span className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider font-medium">
211-
{labels.platforms || 'Platforms'}:
194+
{t('platforms')}:
212195
</span>
213196
<div className="flex gap-[var(--spacing-xs)] flex-wrap">
214197
{(['macOS', 'Windows', 'Linux'] as const).map(platform => {
@@ -233,13 +216,13 @@ export function ProductHero({
233216
)}
234217

235218
{/* Type (for providers) */}
236-
{type && labels?.typeValue && (
219+
{type && typeValue && (
237220
<div className="flex justify-center mb-[var(--spacing-lg)]">
238221
<div className="inline-flex items-center gap-[var(--spacing-xs)] px-[var(--spacing-md)] py-[var(--spacing-sm)] bg-[var(--color-hover)] border border-[var(--color-border)]">
239222
<span className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider font-medium">
240-
{labels.type || 'Type'}:
223+
{t('type')}:
241224
</span>
242-
<span className="text-sm font-medium">{labels.typeValue}</span>
225+
<span className="text-sm font-medium">{type}</span>
243226
</div>
244227
</div>
245228
)}
@@ -253,7 +236,7 @@ export function ProductHero({
253236
rel="noopener"
254237
className="inline-flex items-center gap-[var(--spacing-xs)] px-[var(--spacing-md)] py-[var(--spacing-sm)] text-sm font-medium border border-[var(--color-border-strong)] bg-[var(--color-text)] text-[var(--color-bg)] hover:bg-[var(--color-text-secondary)] transition-all"
255238
>
256-
<span></span> {labels.visitWebsite || 'Visit Website'}
239+
<span></span> {t('visitWebsite')}
257240
</a>
258241
)}
259242

@@ -275,7 +258,7 @@ export function ProductHero({
275258
rel="noopener"
276259
className="inline-flex items-center gap-[var(--spacing-xs)] px-[var(--spacing-md)] py-[var(--spacing-sm)] text-sm font-medium border border-[var(--color-border-strong)] bg-transparent hover:bg-[var(--color-hover)] transition-all"
277260
>
278-
<span></span> {labels.documentation || 'Documentation'}
261+
<span></span> {t('documentation')}
279262
</a>
280263
)}
281264

@@ -286,7 +269,7 @@ export function ProductHero({
286269
rel="noopener"
287270
className="inline-flex items-center gap-[var(--spacing-xs)] px-[var(--spacing-md)] py-[var(--spacing-sm)] text-sm font-medium border border-[var(--color-border-strong)] bg-transparent hover:bg-[var(--color-hover)] transition-all"
288271
>
289-
<span></span> {labels.download || 'Download'}
272+
<span></span> {t('download')}
290273
</a>
291274
)}
292275

@@ -297,7 +280,7 @@ export function ProductHero({
297280
rel="noopener"
298281
className="inline-flex items-center gap-[var(--spacing-xs)] px-[var(--spacing-md)] py-[var(--spacing-sm)] text-sm font-medium border border-[var(--color-border-strong)] bg-transparent hover:bg-[var(--color-hover)] transition-all"
299282
>
300-
<span>🔑</span> {labels.getApiKey || 'Get API Key'}
283+
<span>🔑</span> {t('getApiKey')}
301284
</a>
302285
)}
303286

src/components/product/VendorModels.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
1+
import { useTranslations } from 'next-intl'
12
import { Link } from '@/i18n/navigation'
23
import { formatTokenCount } from '@/lib/format'
34
import type { ManifestModel } from '@/types/manifests'
45

5-
export type VendorModelsProps = {
6-
models: ManifestModel[]
7-
locale: string
8-
title: string
9-
}
6+
export function VendorModels({ models }: { models: ManifestModel[] }) {
7+
const t = useTranslations('components.vendorModels')
108

11-
export function VendorModels({ models, locale: _locale, title }: VendorModelsProps) {
129
if (models.length === 0) {
1310
return null
1411
}
1512

1613
return (
1714
<section className="max-w-8xl mx-auto px-[var(--spacing-md)] py-[var(--spacing-lg)]">
18-
<h2 className="text-xl font-semibold tracking-tight mb-[var(--spacing-md)]">{title}</h2>
15+
<h2 className="text-xl font-semibold tracking-tight mb-[var(--spacing-md)]">{t('title')}</h2>
1916

2017
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-[var(--spacing-md)]">
2118
{models.map(model => (

src/components/product/VendorProducts.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,27 @@
1+
import { useTranslations } from 'next-intl'
12
import { Link } from '@/i18n/navigation'
23
import type { ManifestCLI, ManifestExtension, ManifestIDE } from '@/types/manifests'
34

45
type ProductWithType = (ManifestIDE | ManifestCLI | ManifestExtension) & {
56
type: 'ide' | 'cli' | 'extension'
67
}
78

8-
export type VendorProductsProps = {
9-
products: ProductWithType[]
10-
locale: string
11-
title: string
12-
}
13-
149
const PRODUCT_TYPE_LABELS = {
1510
ide: 'IDE',
1611
cli: 'CLI',
1712
extension: 'Extension',
1813
} as const
1914

20-
export function VendorProducts({ products, locale: _locale, title }: VendorProductsProps) {
15+
export function VendorProducts({ products }: { products: ProductWithType[] }) {
16+
const t = useTranslations('components.vendorProducts')
17+
2118
if (products.length === 0) {
2219
return null
2320
}
2421

2522
return (
2623
<section className="max-w-8xl mx-auto px-[var(--spacing-md)] py-[var(--spacing-lg)]">
27-
<h2 className="text-xl font-semibold tracking-tight mb-[var(--spacing-md)]">{title}</h2>
24+
<h2 className="text-xl font-semibold tracking-tight mb-[var(--spacing-md)]">{t('title')}</h2>
2825

2926
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-[var(--spacing-md)]">
3027
{products.map(product => (

0 commit comments

Comments
 (0)