Files
backspace/packages/web/src/components/modals/settingsPanels/LanguagePanel.tsx
T
devsyncwrld 688a1335cb feat(i18n): language foundation with en and pt-BR
Nothing in the project was translatable — every string sat inline in English.

en.ts is the source dictionary and its type is derived from it, so a typo or a
missing key fails typecheck instead of rendering the raw key at runtime.
pt-BR.ts is deliberately Partial: translation proceeds one system per update
and anything absent falls back to English, so a half-migrated interface is
never broken, only partly English.

Locale is persisted, guessed from the browser on first run, and kept in sync
with <html lang> through a subscription — persisted state rehydrates after
first paint, so a one-off assignment would miss it.

Translates the voice input panel (including the mic test shipped earlier
today) and the profile card as this round's system. Language options are
labelled in the active language, so a wrong pick can always be undone.
2026-08-31 12:24:50 -03:00

49 lines
1.7 KiB
TypeScript

import { SectionShell } from './_shared/SettingsPickerPrimitives';
import { useLocaleStore, useT, LOCALES, type Locale } from '../../../i18n';
import type { TranslationKey } from '../../../i18n';
const LOCALE_LABEL: Record<Locale, TranslationKey> = {
en: 'settings.language.en',
'pt-BR': 'settings.language.ptBR',
};
/**
* Language picker. Each option is labelled in the active language rather than
* in its own — a reader who cannot find their way back out of a language they
* picked by mistake is the one failure this screen must not have.
*/
export function LanguagePanel() {
const t = useT();
const locale = useLocaleStore((s) => s.locale);
const setLocale = useLocaleStore((s) => s.setLocale);
return (
<SectionShell title={t('settings.language.title')}>
<p className="text-[13px] text-txt-tertiary mb-3">
{t('settings.language.description')}
</p>
<div className="flex flex-col gap-1.5">
{LOCALES.map((option) => (
<button
key={option}
type="button"
onClick={() => setLocale(option)}
className={`flex items-center justify-between px-3 py-2 rounded-md text-[14px] text-left transition-colors ${
option === locale
? 'bg-interactive-selected text-txt-primary'
: 'text-txt-secondary hover:bg-interactive-hover'
}`}
>
<span>{t(LOCALE_LABEL[option])}</span>
{option === locale && (
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
</svg>
)}
</button>
))}
</div>
</SectionShell>
);
}