agentskills.codes
SI

sif-intl

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.zip

Installs 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.
103 charsno explicit “when” trigger

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.ts eller i18n/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.ts og nn.ts med 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.js eller 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

KontekstPrefiks-konvensjonEksempel
Pakke@pkgName.@sifSoknadUi.stepFooter.slettSøknad.label
Side (app)page.pageName.page.velkommen.guide.tittel
Steg (app)stepName.barnSteg.tittel
Sub-komponent innen et stegcomponentName.tilsynsordningSøknadsperiode.leggTilEndring
App-nivåfrittapplication.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.ts og i komponentkoden som sender values.
  • 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 mellom nb, nn og 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:

KontekstBrukEksempel
JSX text node (children)

Content truncated.

Search skills

Search the agent skills registry