mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Add complete i18n infrastructure with 21 supported languages: English, Simplified Chinese, Traditional Chinese, Japanese, Korean, Spanish, French, Italian, Brazilian Portuguese, German, Dutch, Swedish, Russian, Polish, Ukrainian, Arabic (RTL), Turkish, Hindi, Vietnamese, Indonesian, and Thai. - I18nProvider context with three-tier locale detection (user preference > navigator.languages > instance default > English) - ~1500 translation keys per locale with TypeScript-enforced completeness - Dynamic code-splitting: only the active locale is loaded at runtime - Language selectors in footer, login page, settings, and mobile sidebar - Arabic RTL support with CSS logical properties across all components - Tool names, descriptions, and categories translated via i18n helpers - Public API endpoint GET /api/v1/config/locale for instance default - Multi-script font stack (CJK, Arabic, Devanagari, Thai, Cyrillic) - format() and plural() helpers for interpolation and pluralization - API error translation mapping (translateApiError) - 36 Playwright e2e tests verifying all 21 locales load correctly - 25 unit tests for format, plural, locale detection, and completeness - Updated translations.md docs and CLAUDE.md conventions
43 lines
1.2 KiB
TypeScript
43 lines
1.2 KiB
TypeScript
import { AlertTriangle, ChevronDown, ChevronRight } from "lucide-react";
|
|
import { useState } from "react";
|
|
|
|
export function CollapsibleSection({
|
|
title,
|
|
badge,
|
|
warning,
|
|
defaultOpen,
|
|
children,
|
|
}: {
|
|
title: string;
|
|
badge?: string;
|
|
warning?: boolean;
|
|
defaultOpen?: boolean;
|
|
children: React.ReactNode;
|
|
}) {
|
|
const [open, setOpen] = useState(defaultOpen ?? false);
|
|
|
|
return (
|
|
<div className="border border-border rounded-lg overflow-hidden">
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen(!open)}
|
|
className="w-full flex items-center gap-2 px-3 py-2 text-xs font-medium text-foreground hover:bg-muted/50 transition-colors"
|
|
>
|
|
{open ? (
|
|
<ChevronDown className="h-3 w-3 shrink-0" />
|
|
) : (
|
|
<ChevronRight className="h-3 w-3 shrink-0" />
|
|
)}
|
|
<span className="flex-1 text-start">{title}</span>
|
|
{warning && <AlertTriangle className="h-3 w-3 text-amber-500 shrink-0" />}
|
|
{badge && (
|
|
<span className="px-1.5 py-0.5 rounded bg-muted text-muted-foreground text-[10px]">
|
|
{badge}
|
|
</span>
|
|
)}
|
|
</button>
|
|
{open && <div className="px-3 pb-2">{children}</div>}
|
|
</div>
|
|
);
|
|
}
|