import { useMemo, useState } from 'react'; import { X } from 'lucide-react'; import { Label } from '@/components/ui/label'; import { useSettings } from 'state/useSettings'; import { useTranslation } from '@/lib/i18n'; import { SHIPPED_LOCALES } from '@/lib/locales'; /** * Language names come from `Intl.DisplayNames`, not from the catalog. * * The runtime already knows every language's name in every locale — "German" in English, "alemão" in * Portuguese — so 28 names would have been 56 hand-written catalog entries that `Intl` gets right for * free, including for locales we have not shipped yet. Same reasoning as D7 for dates and numbers. * * The tags themselves stay a hardcoded list: this is "which languages may I claim to speak", a product * choice, not something to enumerate from the runtime. */ const SPOKEN_LANGUAGE_TAGS = [ 'en', 'pt', 'es', 'fr', 'de', 'it', 'nl', 'ru', 'zh', 'ja', 'ko', 'ar', 'hi', 'tr', 'pl', 'sv', 'da', 'no', 'fi', 'uk', 'cs', 'ro', 'el', 'he', 'th', 'vi', 'id', 'ms', ] as const; /** `Intl` yields "alemão" in pt; a picker row reads better capitalized. */ const useLanguageName = () => { const { locale } = useTranslation(); return useMemo(() => { const names = new Intl.DisplayNames([locale], { type: 'language', fallback: 'code' }); return (code: string) => { const name = names.of(code) ?? code; return name.charAt(0).toUpperCase() + name.slice(1); }; }, [locale]); }; export const Languages = () => { const { settings, saveSettings } = useSettings(); const { t } = useTranslation(); const languageName = useLanguageName(); const { spoken, default: defaultLang, translateTo } = settings.languages; const [addingLang, setAddingLang] = useState(''); const save = (languages: typeof settings.languages) => { saveSettings({ ...settings, languages }); }; const addSpoken = (code: string) => { if (!code || spoken.includes(code)) return; save({ ...settings.languages, spoken: [...spoken, code] }); setAddingLang(''); }; const removeSpoken = (code: string) => { const next = spoken.filter((s) => s !== code); const updates = { ...settings.languages, spoken: next }; // `default` (UI localization) draws from SHIPPED_LOCALES, not `spoken` — removing a spoken // language never leaves it without a valid value. if (translateTo === code) updates.translateTo = next[0] ?? 'en'; save(updates); }; const availableToAdd = SPOKEN_LANGUAGE_TAGS.filter((code) => !spoken.includes(code)); return (
{/* Spoken languages */}
{t('settings.profile.languages.spokenLabel')}
{spoken.map((code) => ( {languageName(code)} {spoken.length > 1 && ( )} ))}
{availableToAdd.length > 0 && (
)}
{/* Default language — UI localization, so it only offers locales that actually ship (D2) */} {/* Translate to */}
); };