feat(i18n): Added support for Chinese language pack

This commit is contained in:
lidong
2026-03-02 18:39:34 +08:00
parent bd389fda44
commit 7ffb8a4234
84 changed files with 4122 additions and 1281 deletions
@@ -1,33 +1,7 @@
import { Badge } from '@/components/ui/badge';
import { cn } from '@/lib/utils';
import { type NoticeProgressState } from '@/lib/updates-notice-state';
const NOTICE_PROGRESS_META: Record<
NoticeProgressState,
{ label: string; className: string; showDot?: boolean }
> = {
new: {
label: 'Needs Action',
className:
'border-amber-300/70 bg-amber-100/70 text-amber-800 dark:border-amber-500/40 dark:bg-amber-500/15 dark:text-amber-300',
showDot: true,
},
seen: {
label: 'In Review',
className:
'border-blue-300/70 bg-blue-100/70 text-blue-800 dark:border-blue-500/40 dark:bg-blue-500/15 dark:text-blue-300',
},
done: {
label: 'Done',
className:
'border-emerald-300/70 bg-emerald-100/70 text-emerald-800 dark:border-emerald-500/40 dark:bg-emerald-500/15 dark:text-emerald-300',
},
dismissed: {
label: 'Dismissed',
className:
'border-muted-foreground/20 bg-muted text-muted-foreground dark:border-muted-foreground/30',
},
};
import { useTranslation } from 'react-i18next';
export function NoticeProgressBadge({
state,
@@ -36,7 +10,30 @@ export function NoticeProgressBadge({
state: NoticeProgressState;
className?: string;
}) {
const meta = NOTICE_PROGRESS_META[state];
const { t } = useTranslation();
const meta = {
new: {
label: t('updates.progressNeedsAction'),
className:
'border-amber-300/70 bg-amber-100/70 text-amber-800 dark:border-amber-500/40 dark:bg-amber-500/15 dark:text-amber-300',
showDot: true,
},
seen: {
label: t('updates.progressInReview'),
className:
'border-blue-300/70 bg-blue-100/70 text-blue-800 dark:border-blue-500/40 dark:bg-blue-500/15 dark:text-blue-300',
},
done: {
label: t('updates.progressDone'),
className:
'border-emerald-300/70 bg-emerald-100/70 text-emerald-800 dark:border-emerald-500/40 dark:bg-emerald-500/15 dark:text-emerald-300',
},
dismissed: {
label: t('updates.progressDismissed'),
className:
'border-muted-foreground/20 bg-muted text-muted-foreground dark:border-muted-foreground/30',
},
}[state] as { label: string; className: string; showDot?: boolean };
return (
<Badge
@@ -8,12 +8,7 @@ import {
type SupportScope,
} from '@/lib/support-updates-catalog';
import { SupportStatusBadge } from './support-status-badge';
const PILLAR_LABELS: { key: keyof CliSupportEntry['pillars']; label: string }[] = [
{ key: 'baseUrl', label: 'Base URL' },
{ key: 'auth', label: 'Auth' },
{ key: 'model', label: 'Model' },
];
import { useTranslation } from 'react-i18next';
const SCOPE_STYLES: Record<SupportScope, string> = {
target:
@@ -27,6 +22,13 @@ const SCOPE_STYLES: Record<SupportScope, string> = {
};
export function SupportEntryCard({ entry }: { entry: CliSupportEntry }) {
const { t } = useTranslation();
const pillarLabels: { key: keyof CliSupportEntry['pillars']; label: string }[] = [
{ key: 'baseUrl', label: 'Base URL' },
{ key: 'auth', label: 'Auth' },
{ key: 'model', label: 'Model' },
];
return (
<Card className="h-full gap-4">
<CardHeader className="gap-3">
@@ -45,7 +47,7 @@ export function SupportEntryCard({ entry }: { entry: CliSupportEntry }) {
<CardContent className="space-y-4">
<div className="grid gap-2 sm:grid-cols-3">
{PILLAR_LABELS.map((pillar) => (
{pillarLabels.map((pillar) => (
<div key={pillar.key} className="rounded-md border bg-muted/30 p-2">
<p className="text-[10px] uppercase tracking-wide text-muted-foreground">
{pillar.label}
@@ -57,7 +59,7 @@ export function SupportEntryCard({ entry }: { entry: CliSupportEntry }) {
<div className="space-y-1">
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
Dashboard
{t('updates.dashboard')}
</p>
<div className="flex flex-wrap gap-2">
{entry.routes.map((route) => (
@@ -75,7 +77,7 @@ export function SupportEntryCard({ entry }: { entry: CliSupportEntry }) {
<div className="space-y-1">
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
CLI Usage
{t('updates.cliUsage')}
</p>
<div className="space-y-1">
{entry.commands.slice(0, 2).map((command) => (
@@ -1,12 +1,7 @@
import { Badge } from '@/components/ui/badge';
import { cn } from '@/lib/utils';
import type { SupportStatus } from '@/lib/support-updates-catalog';
const STATUS_LABELS: Record<SupportStatus, string> = {
new: 'New',
stable: 'Stable',
planned: 'Planned',
};
import { useTranslation } from 'react-i18next';
const STATUS_STYLES: Record<SupportStatus, string> = {
new: 'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-900/50 dark:bg-blue-900/20 dark:text-blue-300',
@@ -23,9 +18,16 @@ export function SupportStatusBadge({
status: SupportStatus;
className?: string;
}) {
const { t } = useTranslation();
const labels: Record<SupportStatus, string> = {
new: t('updates.statusNew'),
stable: t('updates.statusStable'),
planned: t('updates.statusPlanned'),
};
return (
<Badge variant="outline" className={cn('font-medium', STATUS_STYLES[status], className)}>
{STATUS_LABELS[status]}
{labels[status]}
</Badge>
);
}
@@ -15,6 +15,7 @@ import {
type SupportNotice,
} from '@/lib/support-updates-catalog';
import { type NoticeProgressState } from '@/lib/updates-notice-state';
import { useTranslation } from 'react-i18next';
type UpdatableNoticeProgress = 'new' | 'seen' | 'done' | 'dismissed';
@@ -29,10 +30,11 @@ export function UpdatesDetailsPanel({
relatedEntries: CliSupportEntry[];
onUpdateProgress: (nextState: UpdatableNoticeProgress) => void;
}) {
const { t } = useTranslation();
if (!notice) {
return (
<div className="flex h-full items-center justify-center rounded-lg border border-dashed bg-muted/20 p-6">
<p className="text-sm text-muted-foreground">No updates available.</p>
<p className="text-sm text-muted-foreground">{t('updates.noUpdates')}</p>
</div>
);
}
@@ -53,21 +55,21 @@ export function UpdatesDetailsPanel({
<div className="mt-2 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
<CalendarClock className="h-3.5 w-3.5" />
<span>Published {formatCatalogDate(notice.publishedAt)}</span>
<span>{t('updates.published', { date: formatCatalogDate(notice.publishedAt) })}</span>
</div>
<div className="mt-3 flex flex-wrap gap-2">
<Button size="sm" onClick={() => onUpdateProgress('done')}>
<CheckCircle2 className="h-4 w-4" />
Mark done
{t('updates.markDone')}
</Button>
<Button size="sm" variant="outline" onClick={() => onUpdateProgress('dismissed')}>
<EyeOff className="h-4 w-4" />
Dismiss
{t('updates.dismiss')}
</Button>
<Button size="sm" variant="ghost" onClick={() => onUpdateProgress('new')}>
<RotateCcw className="h-4 w-4" />
Reopen
{t('updates.reopen')}
</Button>
</div>
</div>
@@ -78,7 +80,7 @@ export function UpdatesDetailsPanel({
<CardHeader className="pb-3">
<div className="flex items-center gap-2">
<Sparkles className="h-4 w-4 text-primary" />
<CardTitle className="text-base">Do Next</CardTitle>
<CardTitle className="text-base">{t('updates.doNext')}</CardTitle>
</div>
<CardDescription>{notice.primaryAction}</CardDescription>
</CardHeader>
@@ -96,8 +98,8 @@ export function UpdatesDetailsPanel({
<div className="grid h-full grid-rows-[minmax(0,1fr)_minmax(0,1fr)] gap-4 overflow-hidden">
<Card className="overflow-hidden">
<CardHeader className="pb-3">
<CardTitle className="text-base">Impacted Integrations</CardTitle>
<CardDescription>Related areas based on update scope and routing.</CardDescription>
<CardTitle className="text-base">{t('updates.impactedIntegrations')}</CardTitle>
<CardDescription>{t('updates.impactedDesc')}</CardDescription>
</CardHeader>
<CardContent className="min-h-0">
<ScrollArea className="h-full pr-2">
@@ -134,10 +136,8 @@ export function UpdatesDetailsPanel({
<Card className="overflow-hidden">
<CardHeader className="pb-3">
<CardTitle className="text-base">Why It Matters</CardTitle>
<CardDescription>
Short context only, no wall-of-text release notes.
</CardDescription>
<CardTitle className="text-base">{t('updates.whyMatters')}</CardTitle>
<CardDescription>{t('updates.whyMattersDesc')}</CardDescription>
</CardHeader>
<CardContent className="min-h-0">
<ScrollArea className="h-full pr-2">
@@ -3,8 +3,10 @@ import { ArrowUpRight, Terminal } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { CopyButton } from '@/components/ui/copy-button';
import { type SupportNoticeAction } from '@/lib/support-updates-catalog';
import { useTranslation } from 'react-i18next';
export function UpdatesNoticeActionRow({ action }: { action: SupportNoticeAction }) {
const { t } = useTranslation();
return (
<div className="rounded-md border bg-muted/20 p-3">
<div className="flex items-start justify-between gap-3">
@@ -16,7 +18,7 @@ export function UpdatesNoticeActionRow({ action }: { action: SupportNoticeAction
{action.type === 'route' && action.path && (
<Button size="sm" asChild>
<Link to={action.path}>
Open
{t('updates.open')}
<ArrowUpRight className="h-3.5 w-3.5" />
</Link>
</Button>