Server-side i18n
Server-side tekster (push-varsler, transaksjonelle e-poster) oversettes via et eget i18n-system som er frikoblet fra KMP/moko. Systemet er designet for global skalering: engelsk som base, nye språk legges til per marked basert på brukerdata.
Arkitektur
heading.anchorLabelapps/api/src/i18n/├── index.ts # t(), tVar(), resolveLocale()├── locales.ts # 16 locale-koder (ServerLocale type)└── messages/ ├── notifications.ts # Push-varsel strenger └── emails.ts # Transaksjonell e-post (fremtidig)Designprinsipp: Én kilde per streng, ett oppslag per bruk. Engelsk er alltid påkrevd (compile-time). Andre språk er valgfrie og faller tilbake til engelsk.
Språkstrategi
heading.anchorLabel| Lag | System | Språk i dag | Skalering |
|---|---|---|---|
| App UI (iOS/Android) | moko-resources (KMP) | 16 språk | Via moko strings.xml |
| Server varsler | i18n/ (dette systemet) | Kun engelsk | Per marked, datadrevet |
| AI-output | Anthropic prompt directives | Brukerens valgte språk | Automatisk |
Strategien er engelsk globalt fra dag 1. Nye språk legges til i meldingskatalogen når et marked beviser behov gjennom brukerdata. Aldri 16 språk samtidig.
resolveLocale(code?: string | null): ServerLocale
heading.anchorLabelNormaliserer en rå språkkode til en gyldig ServerLocale:
| Input | Output | Grunn |
|---|---|---|
'en' | 'en' | Direkte match |
'no' | 'no' | Direkte match |
'nb' | 'no' | Norsk variant |
'nn' | 'no' | Norsk variant |
null | 'en' | Fallback |
'xx' | 'en' | Ukjent kode |
t(catalog, key, locale): string
heading.anchorLabelSlår opp en oversatt streng. Faller tilbake til engelsk hvis locale mangler oversettelse.
import { t, resolveLocale } from '../../i18n/index.js'import { NOTIFICATION_MESSAGES } from '../../i18n/messages/notifications.js'
const locale = resolveLocale(user.preferredLanguage)const body = t(NOTIFICATION_MESSAGES, 'reminder.week2', locale)// → "It's been two weeks. Impulse AI is here when you need it."tVar(catalog, key, locale, vars): string
heading.anchorLabelSom t(), men interpolerer {placeholder}-variabler:
const body = tVar(NOTIFICATION_MESSAGES, 'lifecycle.trial_expiring', locale, { expiryDate: 'May 1',})// → "Your trial expires May 1. Upgrade to keep access."Meldingskatalog
heading.anchorLabelAlle strenger lever i typed catalogs under i18n/messages/. Hver entry krever en (compile-time) og kan ha valgfrie oversettelser:
export const NOTIFICATION_MESSAGES = { 'reminder.week1.a': { en: 'You captured something important. How did it go?', // de: 'Du hast etwas Wichtiges festgehalten. Wie lief es?', ← fremtidig }, 'lifecycle.trial_expiring': { en: 'Your trial expires {expiryDate}. Upgrade to keep access.', },} as const satisfies MessageCatalogMessageCatalog-typen sikrer at en alltid finnes:
type MessageCatalog = Record<string, Partial<Record<ServerLocale, string>> & { en: string }>Legge til et nytt språk
heading.anchorLabelTo steg:
1. Legg til oversettelser i katalogen:
'reminder.week1.a': { en: 'You captured something important. How did it go?', de: 'Du hast etwas Wichtiges festgehalten. Wie ist es gelaufen?',},2. Ferdig. resolveLocale() håndterer allerede alle 16 locale-koder. t() faller tilbake til EN for nøkler som mangler oversettelse i det nye språket.
Ingen migrasjon, ingen KMP-endring, ingen deploy-avhengighet.
Legge til nye meldinger
heading.anchorLabelPush-varsel:
'campaign.new_feature': { en: 'Something new is here. Open the app to explore.',},E-post (fremtidig):
'welcome.subject': { en: 'Welcome to Impulse AI',},'welcome.body': { en: 'Your transformation journey begins now, {name}.',},E-post body med HTML wrapping implementeres i en fremtidig apps/api/src/emails/-modul som kaller tVar() for subject/body og wrapper i en felles HTML-layout.
Notification Privacy Rule
heading.anchorLabelPush-varsler er en offentlig overflate (låseskjerm, varslingssenteret, widgets). Aldri inkluder brukerinnhold i push-tekst:
- Ingen impulse-titler eller beskrivelser
- Ingen session-innhold eller refleksjoner
- Ingen sjekk-inn-svar
- Ingen AI-genererte analyse-sammendrag
- Ingen personlige områdenavn
Varsler skal invitere uten å avsløre. Alle som ser brukerens låseskjerm skal ikke lære noe om deres indre prosess.
Forskjell fra moko (KMP)
heading.anchorLabel| Server i18n | moko (KMP) | |
|---|---|---|
| Kjører | API-server (Node.js) | iOS/Android app |
| Bruksområde | Push-varsler, e-post | App-UI labels |
| Endringshastighet | Høy (eksperimenterer med tone/timing) | Lav (stabile UI-labels) |
| Deploy | API-deploy | App Store release |
| Kobling | Frikoblet | KMP compile required |
Å koble server-varsler til moko ville bety at en tekstendring i en push-varsel krever KMP-kompilering og app-deploy. Det er feil avveining for tekster som itereres hyppig.