Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(components): implement i18n with consistent translation name…
…spaces

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>
  • Loading branch information
ericyangpan and claude committed Jan 4, 2026
commit 90fa0e0b7fbf344d1a324f26417e9b87397ae0fd
4 changes: 3 additions & 1 deletion src/components/navigation/Breadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

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

// Dynamically adjust sticky position based on header height
Expand Down Expand Up @@ -48,7 +50,7 @@ export function Breadcrumb({ items }: { items: BreadcrumbItem[] }) {
{
'@type': 'ListItem',
position: 1,
name: 'Home',
name: t('components.breadcrumb.home'),
item: SITE_CONFIG.url,
},
...items.map((item, index) => {
Expand Down
16 changes: 8 additions & 8 deletions src/components/product/GitHubStarHistory.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { useTranslations } from 'next-intl'
import { useEffect, useState } from 'react'
import {
CartesianGrid,
Expand Down Expand Up @@ -30,6 +31,7 @@ interface StarHistoryApiResponse {
}

export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) {
const t = useTranslations('components.githubStarHistory')
const [data, setData] = useState<StarDataPoint[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
Expand Down Expand Up @@ -110,9 +112,7 @@ export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) {
<div className="max-w-8xl mx-auto px-[var(--spacing-md)]">
<div className="border border-[var(--color-border)] p-[var(--spacing-md)]">
<div className="flex items-center justify-center h-[300px]">
<p className="text-sm text-[var(--color-text-muted)] animate-pulse">
Loading star history...
</p>
<p className="text-sm text-[var(--color-text-muted)] animate-pulse">{t('loading')}</p>
</div>
</div>
</div>
Expand All @@ -131,10 +131,10 @@ export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) {
{/* Header */}
<div className="mb-[var(--spacing-md)]">
<h2 className="text-lg font-semibold tracking-tight mb-[var(--spacing-xs)]">
GitHub Star History
{t('title')}
</h2>
<p className="text-sm text-[var(--color-text-secondary)] font-light">
Star growth trend over time
{t('description')}
</p>
</div>

Expand Down Expand Up @@ -187,8 +187,8 @@ export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) {
color: 'var(--color-text-secondary)',
}}
formatter={(value: number | undefined) => [
`${value?.toLocaleString() ?? '0'} stars`,
'Stars',
`${value?.toLocaleString() ?? '0'} ${t('stars')}`,
t('stars'),
]}
/>
<Line
Expand All @@ -212,7 +212,7 @@ export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) {
{error && (
<div className="mt-[var(--spacing-sm)] pt-[var(--spacing-sm)] border-t border-[var(--color-border)]">
<p className="text-xs text-[var(--color-text-muted)] font-light">
Note: Displaying sample data. Star history API temporarily unavailable.
{t('fallbackNote')}
</p>
</div>
)}
Expand Down
33 changes: 23 additions & 10 deletions src/components/product/PlatformLinks.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import { useTranslations } from 'next-intl'
import { LinkCardGrid } from '@/components/product/LinkCard'
import type { ManifestPlatformUrls } from '@/types/manifests'

export interface PlatformLinksProps {
platformUrls: ManifestPlatformUrls | null | undefined
title: string
links: Array<{
key: string
title: string
description: string
}>
layout?: 'horizontal' | 'vertical'
gridCols?: string
}
Expand All @@ -22,18 +17,36 @@ export interface PlatformLinksProps {
*/
export function PlatformLinks({
platformUrls,
title,
links,
layout = 'horizontal',
gridCols = 'grid-cols-1 md:grid-cols-3',
}: PlatformLinksProps) {
if (!platformUrls || links.length === 0) {
const t = useTranslations('components.platformLinks')

if (!platformUrls) {
return null
}

const links = [
{
key: 'huggingface',
title: t('aiPlatforms.huggingface.title'),
description: t('aiPlatforms.huggingface.description'),
},
{
key: 'artificialAnalysis',
title: t('aiPlatforms.artificialAnalysis.title'),
description: t('aiPlatforms.artificialAnalysis.description'),
},
{
key: 'openrouter',
title: t('aiPlatforms.openrouter.title'),
description: t('aiPlatforms.openrouter.description'),
},
]

return (
<LinkCardGrid
title={title}
title={t('title')}
links={links}
urls={platformUrls}
layout={layout}
Expand Down
49 changes: 16 additions & 33 deletions src/components/product/ProductHero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface ProductHeroProps {

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

// Links
websiteUrl?: string
Expand All @@ -50,21 +51,6 @@ export interface ProductHeroProps {
url: string
icon?: string
}[]

// i18n labels
labels?: {
vendor?: string
version?: string
license?: string
stars?: string
platforms?: string
type?: string
typeValue?: string // Translated type value (e.g., "Foundation Model Provider")
visitWebsite?: string
documentation?: string
download?: string
getApiKey?: string
}
}

export function ProductHero({
Expand All @@ -81,15 +67,16 @@ export function ProductHero({
showAllPlatforms = false,
additionalInfo,
type,
typeValue,
websiteUrl,
githubUrl,
docsUrl,
downloadUrl,
applyKeyUrl,
additionalUrls,
labels = {},
}: ProductHeroProps) {
const t = useTranslations()
const t = useTranslations('components.productHero')

// Determine which platforms to display
const displayPlatforms = platforms
? showAllPlatforms
Expand Down Expand Up @@ -129,7 +116,7 @@ export function ProductHero({
<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">
{vendor && (
<div className="flex gap-1">
<span className="text-[var(--color-text-muted)]">{labels.vendor || 'Vendor'}:</span>
<span className="text-[var(--color-text-muted)]">{t('vendor')}:</span>
<span className="font-medium">{vendor}</span>
</div>
)}
Expand All @@ -138,9 +125,7 @@ export function ProductHero({

{latestVersion && (
<div className="flex gap-1">
<span className="text-[var(--color-text-muted)]">
{labels.version || 'Version'}:
</span>
<span className="text-[var(--color-text-muted)]">{t('version')}:</span>
<span className="font-medium">{latestVersion}</span>
</div>
)}
Expand All @@ -149,9 +134,7 @@ export function ProductHero({

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

{githubStars !== null && githubStars !== undefined && (
<div className="flex gap-1">
<span className="text-[var(--color-text-muted)]">{labels.stars || 'Stars'}:</span>
<span className="text-[var(--color-text-muted)]">{t('stars')}:</span>
<span className="font-medium">{githubStars}k</span>
</div>
)}
Expand Down Expand Up @@ -208,7 +191,7 @@ export function ProductHero({
<div className="flex justify-center mb-[var(--spacing-lg)]">
<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)]">
<span className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider font-medium">
{labels.platforms || 'Platforms'}:
{t('platforms')}:
</span>
<div className="flex gap-[var(--spacing-xs)] flex-wrap">
{(['macOS', 'Windows', 'Linux'] as const).map(platform => {
Expand All @@ -233,13 +216,13 @@ export function ProductHero({
)}

{/* Type (for providers) */}
{type && labels?.typeValue && (
{type && typeValue && (
<div className="flex justify-center mb-[var(--spacing-lg)]">
<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)]">
<span className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider font-medium">
{labels.type || 'Type'}:
{t('type')}:
</span>
<span className="text-sm font-medium">{labels.typeValue}</span>
<span className="text-sm font-medium">{type}</span>
</div>
</div>
)}
Expand All @@ -253,7 +236,7 @@ export function ProductHero({
rel="noopener"
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"
>
<span>β†—</span> {labels.visitWebsite || 'Visit Website'}
<span>β†—</span> {t('visitWebsite')}
</a>
)}

Expand All @@ -275,7 +258,7 @@ export function ProductHero({
rel="noopener"
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"
>
<span>β†’</span> {labels.documentation || 'Documentation'}
<span>β†’</span> {t('documentation')}
</a>
)}

Expand All @@ -286,7 +269,7 @@ export function ProductHero({
rel="noopener"
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"
>
<span>⬇</span> {labels.download || 'Download'}
<span>⬇</span> {t('download')}
</a>
)}

Expand All @@ -297,7 +280,7 @@ export function ProductHero({
rel="noopener"
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"
>
<span>πŸ”‘</span> {labels.getApiKey || 'Get API Key'}
<span>πŸ”‘</span> {t('getApiKey')}
</a>
)}

Expand Down
11 changes: 4 additions & 7 deletions src/components/product/VendorModels.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { useTranslations } from 'next-intl'
import { Link } from '@/i18n/navigation'
import { formatTokenCount } from '@/lib/format'
import type { ManifestModel } from '@/types/manifests'

export type VendorModelsProps = {
models: ManifestModel[]
locale: string
title: string
}
export function VendorModels({ models }: { models: ManifestModel[] }) {
const t = useTranslations('components.vendorModels')

export function VendorModels({ models, locale: _locale, title }: VendorModelsProps) {
if (models.length === 0) {
return null
}

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

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-[var(--spacing-md)]">
{models.map(model => (
Expand Down
13 changes: 5 additions & 8 deletions src/components/product/VendorProducts.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
import { useTranslations } from 'next-intl'
import { Link } from '@/i18n/navigation'
import type { ManifestCLI, ManifestExtension, ManifestIDE } from '@/types/manifests'

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

export type VendorProductsProps = {
products: ProductWithType[]
locale: string
title: string
}

const PRODUCT_TYPE_LABELS = {
ide: 'IDE',
cli: 'CLI',
extension: 'Extension',
} as const

export function VendorProducts({ products, locale: _locale, title }: VendorProductsProps) {
export function VendorProducts({ products }: { products: ProductWithType[] }) {
const t = useTranslations('components.vendorProducts')

if (products.length === 0) {
return null
}

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

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