Mønster for typesikker i18n (nb/nn) i apper og pakker — implementering, parametersjekk og meningssjekk.
Install
mkdir -p .claude/skills/sif-intl && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/15222" && unzip -o skill.zip -d .claude/skills/sif-intl && rm skill.zipInstalls to .claude/skills/sif-intl
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Mønster for typesikker i18n (nb/nn) i apper og pakker — implementering, parametersjekk og meningssjekk.About this skill
sif-intl
Bruk når
- Du skal legge til eller endre tekster i en app eller pakke.
- Du skal opprette ny
i18n/nb.tselleri18n/nn.ts. - Du trenger å verifisere at
{param}-variabler er like i nb og nn. - Du skal vurdere om meningen i nb- og nn-tekster er ekvivalente.
- Du arbeider med
AppText,useAppIntl,useSifXxxIntl,applicationIntlMessages. - Du skal trekke ut tekster fra en komponent eller sett med komponenter
Leveranse
- Typesikre
nb.tsognn.tsmed korrekte nøkler og parametere - Verifiserte parametere (identiske
{param}i nb og nn) - Meningsekvivalente tekster på begge målformer
- Korrekt aggregering i
i18n/index.tsx
Arkitektur
Mønsteret er delt i to kontekster:
Pakker (f.eks. @sif/soknad-ui)
src/
i18n/
index.tsx ← samler nb, nn; eksporterer hooks, komponent og messages
nb.ts ← (alternativt aggregert via side-spesifikke filer)
pages/
my-page/
i18n/
nb.ts ← kilde for meldinger i denne siden/komponenten
nn.ts ← typed mot nb-objektet, garanterer full dekning
Eksempel nb.ts (pakke, side-spesifikk):
export const stepPageMessages_nb = {
'@sifSoknadUi.stepFooter.fortsettSenere.trigger.label': 'Lagre og fortsett senere',
'@sifSoknadUi.stepFooter.slettSøknad.dialog.text.1':
'Informasjonen du har fylt ut blir slettet, og du kommer tilbake til velkomstsiden.',
};
Eksempel nn.ts (pakke):
import { stepPageMessages_nb } from './nb';
export const stepPageMessages_nn: Record<keyof typeof stepPageMessages_nb, string> = {
'@sifSoknadUi.stepFooter.fortsettSenere.trigger.label': 'Lagre og hald fram seinare',
'@sifSoknadUi.stepFooter.slettSøknad.dialog.text.1':
'Informasjonen du har fylt ut vert sletta, og du kjem tilbake til velkomstsida.',
};
i18n/index.tsx (pakke):
import { typedIntlHelper } from '@navikt/sif-common-utils';
import { FormattedMessage, useIntl } from 'react-intl';
import { stepPageMessages_nb } from '../pages/step-page/i18n/nb';
import { stepPageMessages_nn } from '../pages/step-page/i18n/nn';
const nb = {
...stepPageMessages_nb,
};
const nn: Record<keyof typeof nb, string> = {
...stepPageMessages_nn,
};
type SifSoknadUiMessageKeys = keyof typeof nb;
export const useSifSoknadUiIntl = () => {
const intl = useIntl();
return typedIntlHelper<SifSoknadUiMessageKeys>(intl);
};
interface SifSoknadUiTextProps {
id: SifSoknadUiMessageKeys;
values?: any;
}
export const SifSoknadUiText = (props: SifSoknadUiTextProps) => {
return <FormattedMessage {...props} />;
};
export const sifSoknadUiMessages = {
nb,
nn,
};
Apper (f.eks. aktivitetspenger-soknad)
src/app/
i18n/
index.tsx ← samler lib-meldinger + app-meldinger; eksporterer AppText, useAppIntl
nb/
appMessages.ts ← aggregerer steg- og domain-meldinger
nn/
appMessages.ts ← Record<keyof typeof appMessages_nb, string>
pages/
velkommen/
i18n/
nb.ts ← side-spesifikke nb-tekster
nn.ts ← Record<keyof typeof velkommenPageMessages_nb, string>
steps/
barn/
i18n/
nb.ts ← steg-spesifikke nb-tekster
nn.ts ← Record<keyof typeof barnStegMessages_nb, string>
Eksempel nb.ts (steg, med param og HTML-tag):
export const barnStegMessages_nb = {
'barnSteg.tittel': 'Barn',
'barnSteg.spørsmål.harBarn': 'Stemmer opplysningen om {antallBarn, plural, one {barnet} other {barna}}?',
'barnSteg.opplysninger.info.text':
'Du må være registrert som forelder ... <Lenke>kontakt med Skatteetaten</Lenke>.',
};
Eksempel nn.ts (steg):
import { barnStegMessages_nb } from './nb';
export const barnStegMessages_nn: Record<keyof typeof barnStegMessages_nb, string> = {
'barnSteg.tittel': 'Barn',
'barnSteg.spørsmål.harBarn': 'Stemmer opplysninga om {antallBarn, plural, one {barnet} other {barna}}?',
'barnSteg.opplysninger.info.text': 'Du må vere registrert som forelder ... <Lenke>kontakt Skatteetaten</Lenke>.',
};
i18n/nb/appMessages.ts:
import { barnStegMessages_nb } from '../../steps/barn/i18n/nb';
export const appMessages_nb = {
...barnStegMessages_nb,
'application.title': 'Søknad om aktivitetspenger',
};
i18n/nn/appMessages.ts:
import { barnStegMessages_nn } from '../../steps/barn/i18n/nn';
import { appMessages_nb } from '../nb/appMessages';
export const appMessages_nn: Record<keyof typeof appMessages_nb, string> = {
...barnStegMessages_nn,
'application.title': 'Søknad om aktivitetspengar',
};
i18n/index.tsx (app):
import { typedIntlHelper } from '@navikt/sif-common-utils';
import { sifSoknadUiMessages } from '@sif/soknad-ui/i18n';
import { FormattedMessage, useIntl } from 'react-intl';
import { velkommenPageMessages_nb } from '../pages/velkommen/i18n/nb';
import { velkommenPageMessages_nn } from '../pages/velkommen/i18n/nn';
import { appMessages_nb } from './nb/appMessages';
import { appMessages_nn } from './nn/appMessages';
const libMessages = {
nb: { ...sifSoknadUiMessages.nb },
nn: { ...sifSoknadUiMessages.nn },
};
const nb = {
...libMessages.nb,
...appMessages_nb,
...velkommenPageMessages_nb,
};
const nn: Record<keyof typeof nb, string> = {
...libMessages.nn,
...appMessages_nn,
...velkommenPageMessages_nn,
};
export type AppMessageKeys = keyof typeof nb;
export const useAppIntl = () => {
const intl = useIntl();
return typedIntlHelper<AppMessageKeys>(intl);
};
export type AppIntlShape = ReturnType<typeof useAppIntl>;
interface AppTextProps {
id: AppMessageKeys;
values?: any;
}
export const AppText = (props: AppTextProps) => {
return <FormattedMessage {...props} />;
};
export const applicationIntlMessages = {
nb,
nn,
};
Regler og konvensjoner
Regler for copy og tekstendringer
- AI skal ikke finne på, skrive om eller forbedre brukervendte tekster på egen hånd.
- Når oppgaven er å flytte, trekke ut eller strukturere tekster, skal tekstinnholdet beholdes uendret.
- Nye tekster skal bare legges inn når teksten er eksplisitt oppgitt av bruker, finnes i eksisterende kildefil, eller kommer fra etablert copy.
- Hvis nødvendig tekstgrunnlag mangler, stopp og be om teksten i stedet for å dikte den.
- Ved migrering eller refaktorering er hovedregelen at eksisterende tekster skal være identiske før og etter endringen.
- Storybook-tekster, demo-tekster og labels i
preview.jseller stories er også brukervendte tekster og skal holde korrekt norsk rettskrivning. - Ikke erstatt norske tegn med ASCII-varianter i tekster. Bruk
æ,øogånår teksten er norsk, med mindre teksten eksplisitt kommer fra en kilde som allerede mangler disse tegnene.
Nøkkelstruktur
| Kontekst | Prefiks-konvensjon | Eksempel |
|---|---|---|
| Pakke | @pkgName. | @sifSoknadUi.stepFooter.slettSøknad.label |
| Side (app) | page.pageName. | page.velkommen.guide.tittel |
| Steg (app) | stepName. | barnSteg.tittel |
| Sub-komponent innen et steg | componentName. | tilsynsordningSøknadsperiode.leggTilEndring |
| App-nivå | fritt | application.title |
Grunnpattern for sub-komponenter: Bruk komponentnavnet (camelCase) som prefiks — ikke domenebetegnelsen for innholdet. Eksempel: nøkler i TilsynsordningSøknadsperiode.tsx bruker prefikset tilsynsordningSøknadsperiode..
nn.ts-typing
nn skal alltid types mot nb med Record<keyof typeof ..._nb, string>. Dette sikrer:
- TS-feil ved manglende nøkler i nn
- TS-feil ved nøkler i nn som ikke finnes i nb
- Full dekning garantert av typesystemet
ICU-parametre er kontrakter
Parametre i meldingsstrenger som {navn}, {dato}, {count} og custom tags er en del av kontrakten mellom meldingen og komponenten som sender values.
- Parameternavn skal være identiske i
nb.ts,nn.tsog i komponentkoden som sendervalues. - Parameternavn skal aldri oversettes. Oversett teksten rundt, men behold selve parameternavnet uendret.
- Ved migrering, portering eller refaktorering skal eksisterende parameternavn beholdes eksakt som i kilden.
- Før du avslutter en i18n-endring, gjør en eksplisitt parametersjekk: sammenlign alle
{param}og custom tags mellomnb,nnog kallstedet.
Dette er en blokkerende regel. Mismatch i parameternavn gir ødelagt interpolering i UI og er ikke akseptabelt.
Manglende nynorsk-oversettelse
Hvis nynorsk ikke finnes eller ikke er levert av en oversetter, spread nb uten å oversette selv:
import { myMessages_nb } from './nb';
export const myMessages_nn: Record<keyof typeof myMessages_nb, string> = {
...myMessages_nb,
};
Lag nynorsk-tekster kun når brukeren eksplisitt ber om det. Ellers skal nynorsk komme fra et menneske eller en offisiell oversetter.
Nynorsk pronomen
I nynorsk tekst skal me alltid brukes i staden for vi som subjektspronomen i første person fleirtal.
✅ «slik at me kan handsame saka di»
❌ «slik at vi kan handsame saka di»
Sjekk alle nn.ts-filer for vi (med mellomrom etter) og erstatt med me .
Bruk av komponent vs. hook
Velg basert på om teksten er en JSX-node (children) eller en string-prop:
| Kontekst | Bruk | Eksempel |
|---|---|---|
| JSX text node (children) |
Content truncated.