feat(i18n): add i18next foundation with English + zh-CN locales

Installs i18next + react-i18next + language detector and wires up a
language selector in the General settings page. Extracts strings from
the highest-visibility surfaces: all settings tabs, model management,
sidebar nav, main editor, and the floating generate box. Remaining
strings (profile forms, history, stories/effects/voices/audio tabs)
can land in follow-up PRs.

Closes #411.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
James Pine
2026-04-20 02:43:50 -07:00
co-authored by Claude Opus 4.7
parent 5aa1677a25
commit a205086733
18 changed files with 1091 additions and 299 deletions
@@ -1,5 +1,6 @@
import changelogRaw from 'virtual:changelog';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Badge } from '@/components/ui/badge';
import { type ChangelogEntry, parseChangelog } from '@/lib/utils/parseChangelog';
@@ -176,6 +177,7 @@ function inlineMarkdown(text: string): React.ReactNode {
}
function ChangelogEntryCard({ entry }: { entry: ChangelogEntry }) {
const { t } = useTranslation();
const [expanded, setExpanded] = useState(false);
const content = useMemo(() => renderMarkdown(entry.body), [entry.body]);
const isLong = entry.body.split('\n').length > 12;
@@ -185,7 +187,9 @@ function ChangelogEntryCard({ entry }: { entry: ChangelogEntry }) {
<div className="flex items-baseline gap-3 mb-3">
<h3 className="text-xl font-semibold tracking-tight">{entry.version}</h3>
{entry.date && <span className="text-xs text-muted-foreground">{entry.date}</span>}
{entry.version === 'Unreleased' && <Badge variant="outline">dev</Badge>}
{entry.version === 'Unreleased' && (
<Badge variant="outline">{t('settings.changelog.devBadge')}</Badge>
)}
</div>
<div className={isLong && !expanded ? 'max-h-48 overflow-hidden relative' : ''}>
@@ -200,7 +204,7 @@ function ChangelogEntryCard({ entry }: { entry: ChangelogEntry }) {
onClick={() => setExpanded(!expanded)}
className="text-xs text-accent hover:underline mt-2"
>
{expanded ? 'Show less' : 'Show more'}
{expanded ? t('settings.changelog.showLess') : t('settings.changelog.showMore')}
</button>
)}
</div>