'use client'; import { useState } from 'react'; import { ChevronDownIcon } from 'lucide-react'; import { Button } from '@kit/ui/button'; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger, } from '@kit/ui/dropdown-menu'; import { Input } from '@kit/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@kit/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@kit/ui/table'; import { cn } from '@kit/ui/utils'; import { defaultI18nNamespaces } from '../../../../web/lib/i18n/i18n.settings'; import type { TranslationData, Translations } from '../lib/translations-loader'; function flattenTranslations( obj: TranslationData, prefix = '', result: Record = {}, ) { for (const [key, value] of Object.entries(obj)) { const newKey = prefix ? `${prefix}.${key}` : key; if (typeof value === 'string') { result[newKey] = value; } else { flattenTranslations(value, newKey, result); } } return result; } type FlattenedTranslations = Record>; export function TranslationsComparison({ translations, }: { translations: Translations; }) { const [search, setSearch] = useState(''); const [selectedLocales, setSelectedLocales] = useState>(); const [selectedNamespace, setSelectedNamespace] = useState( defaultI18nNamespaces[0] as string, ); const locales = Object.keys(translations); if (locales.length === 0) { return
No translations found
; } const baseLocale = locales[0]!; // Initialize selected locales if not set if (!selectedLocales) { setSelectedLocales(new Set(locales)); return null; } // Flatten translations for the selected namespace const flattenedTranslations: FlattenedTranslations = {}; for (const locale of locales) { const namespaceData = translations[locale]?.[selectedNamespace]; if (namespaceData) { flattenedTranslations[locale] = flattenTranslations(namespaceData); } else { flattenedTranslations[locale] = {}; } } // Get all unique keys across all translations const allKeys = Array.from( new Set( Object.values(flattenedTranslations).flatMap((data) => Object.keys(data)), ), ).sort(); const filteredKeys = allKeys.filter((key) => key.toLowerCase().includes(search.toLowerCase()), ); const visibleLocales = locales.filter((locale) => selectedLocales.has(locale), ); const copyTranslation = async (text: string) => { try { await navigator.clipboard.writeText(text); } catch (error) { console.error('Failed to copy text:', error); } }; const toggleLocale = (locale: string) => { const newSelectedLocales = new Set(selectedLocales); if (newSelectedLocales.has(locale)) { if (newSelectedLocales.size > 1) { newSelectedLocales.delete(locale); } } else { newSelectedLocales.add(locale); } setSelectedLocales(newSelectedLocales); }; return (
setSearch(e.target.value)} className="max-w-sm" /> {locales.map((locale) => ( toggleLocale(locale)} disabled={ selectedLocales.size === 1 && selectedLocales.has(locale) } > {locale} ))}
Key {visibleLocales.map((locale) => ( {locale} ))} {filteredKeys.map((key) => (
{key}
{visibleLocales.map((locale) => { const translations = flattenedTranslations[locale] ?? {}; const baseTranslations = flattenedTranslations[baseLocale] ?? {}; const value = translations[key]; const baseValue = baseTranslations[key]; const isMissing = !value; const isDifferent = value !== baseValue; return (
{value || ( Missing )}
); })}
))}
); }