Skip to content

Commit 16e91a4

Browse files
ericyangpanclaude
andcommitted
refactor(components): localize hardcoded strings to use translation keys
- ModelSpecifications, ModelBenchmarks, VendorMatrix now use i18n - Add helper functions for category/vendor type localization - Simplify BackToNavigation section structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent bb39838 commit 16e91a4

File tree

7 files changed

+136
-110
lines changed

7 files changed

+136
-110
lines changed

src/components/navigation/BackToNavigation.tsx

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,18 @@ export function BackToNavigation({ href, title }: BackToNavigationProps) {
2727
const t = useTranslations('components.backToNavigation')
2828

2929
return (
30-
<section className="py-[var(--spacing-lg)] border-b border-[var(--color-border)]">
31-
<div className="max-w-8xl mx-auto px-[var(--spacing-md)]">
32-
<div className="grid grid-cols-1 md:grid-cols-3 gap-[var(--spacing-md)]">
33-
<Link
34-
href={href}
35-
className="border border-[var(--color-border)] p-[var(--spacing-md)] hover:border-[var(--color-border-strong)] transition-all hover:-translate-y-0.5 flex flex-col gap-1 text-center md:col-span-3"
36-
>
37-
<span className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider font-medium">
38-
{t('backTo')}
39-
</span>
40-
<span className="text-lg font-semibold tracking-tight">{title}</span>
41-
<span className="text-xs text-[var(--color-text-muted)]">[{t('indexLabel')}]</span>
42-
</Link>
43-
</div>
30+
<section className="pt-[var(--spacing-lg)]">
31+
<div className="max-w-8xl mx-auto">
32+
<Link
33+
href={href}
34+
className="border border-[var(--color-border)] p-[var(--spacing-md)] hover:border-[var(--color-border-strong)] transition-all hover:-translate-y-0.5 flex flex-col gap-1 text-center"
35+
>
36+
<span className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider font-medium">
37+
{t('backTo')}
38+
</span>
39+
<span className="text-lg font-semibold tracking-tight">{title}</span>
40+
<span className="text-xs text-[var(--color-text-muted)]">[{t('indexLabel')}]</span>
41+
</Link>
4442
</div>
4543
</section>
4644
)

src/components/navigation/Breadcrumb.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,23 @@ export function Breadcrumb({ items }: { items: BreadcrumbItem[] }) {
8484
return (
8585
<span
8686
key={`${item.href}-${index}`}
87-
className="inline-flex items-center gap-[var(--spacing-xs)]"
87+
className={`inline-flex items-center ${isLast ? 'text-[var(--color-text)] font-medium' : ''}`}
8888
>
8989
{isLast ? (
90-
<span className="text-[var(--color-text)] font-medium">{item.name}</span>
90+
item.name
9191
) : (
92-
<Link
93-
href={normalizedHref}
94-
className="text-[var(--color-text-secondary)] hover:text-[var(--color-text)] transition-colors"
95-
>
96-
{item.name}
97-
</Link>
92+
<>
93+
<Link
94+
href={normalizedHref}
95+
className="text-[var(--color-text-secondary)] hover:text-[var(--color-text)] transition-colors"
96+
>
97+
{item.name}
98+
</Link>
99+
<span className="text-[var(--color-text-muted)] ml-[var(--spacing-xs)]">
100+
/
101+
</span>
102+
</>
98103
)}
99-
{!isLast && <span className="text-[var(--color-text-muted)]">/</span>}
100104
</span>
101105
)
102106
})}

src/components/product/ModelBenchmarks.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1+
import { useTranslations } from 'next-intl'
12
import { BENCHMARK_KEYS, formatBenchmarkValue, hasBenchmarks } from '@/lib/benchmarks'
23
import type { ManifestModel } from '@/types/manifests'
34

45
export interface ModelBenchmarksProps {
56
benchmarks: ManifestModel['benchmarks']
6-
translations: {
7-
title: string
8-
[key: string]: string
9-
}
107
}
118

129
/**
1310
* ModelBenchmarks Section
1411
*
1512
* Displays performance benchmark scores for AI models.
1613
*/
17-
export function ModelBenchmarks({ benchmarks, translations }: ModelBenchmarksProps) {
14+
export function ModelBenchmarks({ benchmarks }: ModelBenchmarksProps) {
15+
const t = useTranslations('components.modelBenchmarks')
1816
if (!benchmarks || !hasBenchmarks(benchmarks)) {
1917
return null
2018
}
@@ -23,7 +21,7 @@ export function ModelBenchmarks({ benchmarks, translations }: ModelBenchmarksPro
2321
<section className="py-[var(--spacing-lg)] border-b border-[var(--color-border)]">
2422
<div className="max-w-8xl mx-auto px-[var(--spacing-md)]">
2523
<h2 className="text-2xl font-semibold tracking-[-0.02em] mb-[var(--spacing-sm)]">
26-
{translations.title}
24+
{t('title')}
2725
</h2>
2826

2927
<div className="grid grid-cols-1 md:grid-cols-2 gap-[var(--spacing-md)] mt-[var(--spacing-lg)]">
@@ -34,14 +32,12 @@ export function ModelBenchmarks({ benchmarks, translations }: ModelBenchmarksPro
3432
return (
3533
<div key={key} className="border border-[var(--color-border)] p-[var(--spacing-md)]">
3634
<h3 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider font-medium mb-[var(--spacing-xs)]">
37-
{translations[key]}
35+
{t(key)}
3836
</h3>
3937
<p className="text-lg font-semibold tracking-tight mb-1">
4038
{formatBenchmarkValue(key, value)}
4139
</p>
42-
<p className="text-xs text-[var(--color-text-muted)]">
43-
{translations[`${key}Desc`]}
44-
</p>
40+
<p className="text-xs text-[var(--color-text-muted)]">{t(`${key}Desc`)}</p>
4541
</div>
4642
)
4743
})}

src/components/product/ModelSpecifications.tsx

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1+
import { useTranslations } from 'next-intl'
12
import { formatTokenCount } from '@/lib/format'
23
import type { ManifestModel } from '@/types/manifests'
34

45
export interface ModelSpecificationsProps {
56
model: Pick<ManifestModel, 'size' | 'contextWindow' | 'maxOutput' | 'tokenPricing'>
6-
translations: {
7-
title: string
8-
modelSize: string
9-
contextWindow: string
10-
maxOutput: string
11-
pricing: string
12-
input: string
13-
output: string
14-
cache: string
15-
}
167
}
178

189
/**
@@ -21,7 +12,8 @@ export interface ModelSpecificationsProps {
2112
* Displays technical specifications for AI models including size,
2213
* context window, max output, and token pricing.
2314
*/
24-
export function ModelSpecifications({ model, translations }: ModelSpecificationsProps) {
15+
export function ModelSpecifications({ model }: ModelSpecificationsProps) {
16+
const t = useTranslations('components.modelSpecifications')
2517
const hasContent =
2618
model.size ||
2719
model.contextWindow ||
@@ -38,22 +30,22 @@ export function ModelSpecifications({ model, translations }: ModelSpecifications
3830
<section className="py-[var(--spacing-lg)] border-b border-[var(--color-border)]">
3931
<div className="max-w-8xl mx-auto px-[var(--spacing-md)]">
4032
<h2 className="text-2xl font-semibold tracking-[-0.02em] mb-[var(--spacing-sm)]">
41-
{translations.title}
33+
{t('title')}
4234
</h2>
4335

4436
<div className="grid grid-cols-1 md:grid-cols-2 gap-[var(--spacing-md)] mt-[var(--spacing-lg)]">
4537
{model.size && (
4638
<div className="border border-[var(--color-border)] p-[var(--spacing-md)]">
4739
<h3 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider font-medium mb-[var(--spacing-xs)]">
48-
{translations.modelSize}
40+
{t('modelSize')}
4941
</h3>
5042
<p className="text-lg font-semibold tracking-tight">{model.size}</p>
5143
</div>
5244
)}
5345

5446
<div className="border border-[var(--color-border)] p-[var(--spacing-md)]">
5547
<h3 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider font-medium mb-[var(--spacing-xs)]">
56-
{translations.contextWindow}
48+
{t('contextWindow')}
5749
</h3>
5850
<p className="text-lg font-semibold tracking-tight">
5951
{formatTokenCount(model.contextWindow)}
@@ -62,7 +54,7 @@ export function ModelSpecifications({ model, translations }: ModelSpecifications
6254

6355
<div className="border border-[var(--color-border)] p-[var(--spacing-md)]">
6456
<h3 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider font-medium mb-[var(--spacing-xs)]">
65-
{translations.maxOutput}
57+
{t('maxOutput')}
6658
</h3>
6759
<p className="text-lg font-semibold tracking-tight">
6860
{formatTokenCount(model.maxOutput)}
@@ -72,34 +64,28 @@ export function ModelSpecifications({ model, translations }: ModelSpecifications
7264
{model.tokenPricing && (
7365
<div className="border border-[var(--color-border)] p-[var(--spacing-md)]">
7466
<h3 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider font-medium mb-[var(--spacing-xs)]">
75-
{translations.pricing}
67+
{t('pricing')}
7668
</h3>
7769
<div className="space-y-1">
7870
{model.tokenPricing.input !== null && model.tokenPricing.input !== undefined && (
7971
<p className="text-sm">
80-
<span className="text-[var(--color-text-muted)] text-xs">
81-
{translations.input}{' '}
82-
</span>
72+
<span className="text-[var(--color-text-muted)] text-xs">{t('input')} </span>
8373
<span className="font-semibold tracking-tight">
8474
${model.tokenPricing.input}/M
8575
</span>
8676
</p>
8777
)}
8878
{model.tokenPricing.output !== null && model.tokenPricing.output !== undefined && (
8979
<p className="text-sm">
90-
<span className="text-[var(--color-text-muted)] text-xs">
91-
{translations.output}{' '}
92-
</span>
80+
<span className="text-[var(--color-text-muted)] text-xs">{t('output')} </span>
9381
<span className="font-semibold tracking-tight">
9482
${model.tokenPricing.output}/M
9583
</span>
9684
</p>
9785
)}
9886
{model.tokenPricing.cache !== null && model.tokenPricing.cache !== undefined && (
9987
<p className="text-sm">
100-
<span className="text-[var(--color-text-muted)] text-xs">
101-
{translations.cache}{' '}
102-
</span>
88+
<span className="text-[var(--color-text-muted)] text-xs">{t('cache')} </span>
10389
<span className="font-semibold tracking-tight">
10490
${model.tokenPricing.cache}/M
10591
</span>

src/components/product/PlatformLinks.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ export interface PlatformLinksProps {
1919
gridCols?: string
2020
}
2121

22+
/**
23+
* Check whether the given platform URLs object contains at least one usable URL.
24+
*
25+
* We treat empty strings and whitespace-only strings as "missing".
26+
*/
27+
function hasAnyPlatformUrl(platformUrls: PlatformUrls): boolean {
28+
return Object.values(platformUrls).some(value => {
29+
if (typeof value !== 'string') return false
30+
return value.trim().length > 0
31+
})
32+
}
33+
2234
/**
2335
* PlatformLinks Section
2436
*
@@ -33,7 +45,7 @@ export function PlatformLinks({
3345
layout = 'horizontal',
3446
gridCols = 'grid-cols-1 md:grid-cols-3',
3547
}: PlatformLinksProps) {
36-
if (!platformUrls) {
48+
if (!platformUrls || links.length === 0 || !hasAnyPlatformUrl(platformUrls)) {
3749
return null
3850
}
3951

src/components/product/RelatedProducts.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@ import { Link } from '@/i18n/navigation'
55
import type { ManifestCLI, ManifestExtension, ManifestIDE } from '@/types/manifests'
66

77
export interface RelatedProductsProps {
8-
products: Array<{
8+
products?: Array<{
99
type: 'ide' | 'cli' | 'extension'
1010
data: ManifestIDE | ManifestCLI | ManifestExtension | null
1111
}>
1212
}
1313

14-
export function RelatedProducts({ products }: RelatedProductsProps) {
14+
/**
15+
* Render a "Related Products" section.
16+
*
17+
* This component is intentionally defensive: it filters out null product data and returns `null`
18+
* when there is nothing meaningful to display, so callers don't need conditional rendering.
19+
*/
20+
export function RelatedProducts({ products = [] }: RelatedProductsProps) {
1521
const t = useTranslations('components.relatedProducts')
1622

1723
// Filter out products with null data

0 commit comments

Comments
 (0)