--- paths: - "app/**" - "components/**" - "messages/**" - "lib/email/**" - "lib/invoices/**" - "lib/reports/**" - "lib/salary/**" --- # i18n The app is Swedish-first and bilingual (Swedish + English) for UI chrome. Locale is per-user on `user_preferences.locale` (`'sv' | 'en'`, default `'sv'`), resolved server-side via next-intl. The user picker lives at `/settings/account`. **Pattern for new UI:** ```tsx // Server component import { getTranslations } from 'next-intl/server' const t = await getTranslations('namespace') // Client component 'use client' import { useTranslations } from 'next-intl' const t = useTranslations('namespace') // In JSX ``` Add new strings to both `messages/sv.json` and `messages/en.json` under the matching namespace (`common`, `nav`, `auth`, `settings`, `empty`, etc.). Never ship an English key without a Swedish counterpart — Swedish is the default and the fallback. **Locale-aware formatters:** - `formatCurrency(amount)` — stays SEK with sv-SE conventions in BOTH locales (Swedish accounting standard, not a UI string). - `formatDate(date)` — ISO `yyyy-MM-dd`, locale-independent. - `formatDateLong(date, locale)` — accepts locale. In client components use `useFormat()` (`lib/hooks/use-format.ts`) which pulls the active locale. **Error messages:** `getErrorMessage(err, { locale, context })` from `lib/errors/get-error-message.ts` is bilingual on the primary maps (Postgres codes, HTTP statuses, context fallbacks, generic fallback). The structured error envelope (`{ error: { code, message, message_en } }`) already carries both; the function picks the right one from `locale`. Pass `useLocale()` / `getLocale()` as the locale arg. **Stays Swedish — do NOT translate:** | Surface | Reason | |---|---| | Invoice PDFs (`lib/invoices/pdf-template.tsx`) | Customer-facing — driven by `customer.language` (`sv` default, `en` opt-in). The template's chrome translates; statutory chapter refs (ML 17 kap 24§, ML 3 kap.) stay intact in both locales. | | Customer email templates (`lib/email/invoice-templates.ts`, `reminder-templates.ts`) | Same — `customer.language` drives the output. `reminder-templates.ts` is still Swedish-only; mirror the PDF/invoice-templates approach if you add English here. | | Year-end wizard (`app/(dashboard)/bookkeeping/year-end/page.tsx`) | Statutory bokslut terminology; English would be misleading | | Journal entry editor (`app/(dashboard)/bookkeeping/[id]/page.tsx`) | Deeply regulatory (verifikat, voucher numbers, BAS) | | INK2 / NE-bilaga / SRU (`lib/reports/ink2/**`, `lib/reports/ne-bilaga/**`, `lib/reports/sru-*`) | Skatteverket forms — field codes and labels are statutory | | SIE export (`lib/reports/sie-export.ts`) | SIE format is Swedish-only by spec (#KONTO, #VER, etc.) | | BAS chart names (`lib/bookkeeping/bas-data/**`) | Standardized Swedish account names per BAS 2026 | | VAT declaration ruta labels (`lib/reports/vat-declaration*.ts`) | Momsdeklaration field labels are Skatteverket form labels | | Salary AGI / KU (`lib/salary/agi*`, `lib/salary/ku*`) | Skatteverket-bound forms | | Bookkeeping engine domain errors ("Verifikationen balanserar inte", "Bokföringen är låst") | Regulatory concepts; English equivalents would be ambiguous | Anything in the table above stays Swedish in BOTH locales. If you find yourself reaching for `t()` inside one of these files, stop and reconsider.