"use client"; import { Download, Globe, Save } from "lucide-react"; import { useMemo, useState } from "react"; import { saveTranslations } from "@/actions/translations"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { useServerAction } from "@/hooks/use-server-action"; import { flattenObject, unflattenObject } from "@/lib/translations-utils"; type Locale = "en" | "it" | "nl" | "de" | "fr" | "es" | "pt" | "pl" | "sv" | "tr" | "ro" | "hu" | "cs" | "sk" | "da" | "no" | "el" | "bg" | "hr" | "sr" | "uk" | "ru"; const LOCALES: { code: Locale; label: string }[] = [ { code: "en", label: "EN" }, { code: "it", label: "IT" }, { code: "nl", label: "NL" }, { code: "de", label: "DE" }, { code: "fr", label: "FR" }, { code: "es", label: "ES" }, { code: "pt", label: "PT" }, { code: "pl", label: "PL" }, { code: "sv", label: "SV" }, { code: "tr", label: "TR" }, { code: "ro", label: "RO" }, { code: "hu", label: "HU" }, { code: "cs", label: "CS" }, { code: "sk", label: "SK" }, { code: "da", label: "DA" }, { code: "no", label: "NO" }, { code: "el", label: "EL" }, { code: "bg", label: "BG" }, { code: "hr", label: "HR" }, { code: "sr", label: "SR" }, { code: "uk", label: "UK" }, { code: "ru", label: "RU" }, ]; interface Props { localeContents: Record; } export function TranslationsClient({ localeContents }: Props) { const [data, setData] = useState>>(() => { const initial: Record> = {}; for (const [locale, content] of Object.entries(localeContents)) { initial[locale] = flattenObject(JSON.parse(content)); } return initial; }); const [search, setSearch] = useState(""); const [group, setGroup] = useState(""); const [activeLang, setActiveLang] = useState("en"); const { run, isPending } = useServerAction(); const PER_PAGE = 30; const [page, setPage] = useState(1); const activeData = data[activeLang]; const groups = useMemo(() => { const g = new Set(); Object.keys(data["en"] ?? {}).forEach((k) => { const parts = k.split("."); if (parts.length > 1) g.add(parts[0]); }); return Array.from(g).sort(); }, [data]); const filteredKeys = useMemo(() => { if (!activeData) return []; return Object.keys(activeData).filter((key) => { const matchSearch = !search || key.toLowerCase().includes(search.toLowerCase()) || activeData[key].toLowerCase().includes(search.toLowerCase()); const matchGroup = !group || key.startsWith(`${group}.`); return matchSearch && matchGroup; }); }, [activeData, search, group]); const totalPages = Math.ceil(filteredKeys.length / PER_PAGE); const pageKeys = filteredKeys.slice((page - 1) * PER_PAGE, page * PER_PAGE); function updateValue(key: string, value: string) { setData({ ...data, [activeLang]: { ...activeData, [key]: value } }); } function handleSave() { run(() => saveTranslations({ locale: activeLang, data: unflattenObject(activeData) }), { successMessage: "Translations saved!", }); } function handleExport() { const blob = new Blob([JSON.stringify(unflattenObject(activeData), null, 2)], { type: "application/json", }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `${activeLang}.json`; a.click(); URL.revokeObjectURL(url); } return (

CMS

{LOCALES.map(({ code, label }) => ( ))}
{ setSearch(e.target.value); setPage(1); }} className="max-w-xs" /> {filteredKeys.length} keys
{pageKeys.map((key) => (
{key} updateValue(key, e.target.value)} className="flex-1 h-8 text-sm" />
))}
{totalPages > 1 && (
Page {page} of {totalPages}
)}
); }