---
title: 'Locale / Translation'
version: 11.2.2
generatedAt: 2026-05-11T08:17:55.959Z
checksum: 090b7d977ba4be5e2c4c04d199a30a4048416c59f443a56985df2f80629d9c40
---

# Localization

The default constants are defined in the `/shared/defaults.js` file.

- The default locale of all components texts is: `nb-NO`.
- The default currency is: `NOK`

## Supported component translations

Eufemia components comes with a set of default translated strings for the following locales:

<Ul>
  {Object.keys(languageDisplayNames).map((l) => (
    <Li key={l}>
      <Anchor
        href={`https://github.com/dnbexperience/eufemia/blob/main/packages/dnb-eufemia/src/shared/locales/${l}.ts`}
      >
        {l}
      </Anchor>
    </Li>
  ))}
</Ul>

You can easily change one, some or all of them by using a React provider – the Eufemia Provider.

Here are the default strings located:

```js
// Included by default
import enGB from '@dnb/eufemia/shared/locales/en-GB'
import nbNO from '@dnb/eufemia/shared/locales/nb-NO'
import enGB_forms from '@dnb/eufemia/extensions/forms/constants/locales/en-GB'
import nbNO_forms from '@dnb/eufemia/extensions/forms/constants/locales/nb-NO'

// Additional locales you can add
import svSE from '@dnb/eufemia/shared/locales/sv-SE'
import svSE_forms from '@dnb/eufemia/extensions/forms/constants/locales/sv-SE'
import svSE_forms_countries from '@dnb/eufemia/extensions/forms/constants/locales/countries/sv-SE'

// Additional locales you can add
import daDK from '@dnb/eufemia/shared/locales/da-DK'
import daDK_forms from '@dnb/eufemia/extensions/forms/constants/locales/da-DK'
import daDK_forms_countries from '@dnb/eufemia/extensions/forms/constants/locales/countries/da-DK'

Use `mergeTranslations` to combine the forms translations (and country translations when needed) before you pass them to `Form.Handler` or `Provider`.

import { mergeTranslations } from '@dnb/eufemia/shared'
import svSE_forms from '@dnb/eufemia/extensions/forms/constants/locales/sv-SE'
import svSE_forms_countries from '@dnb/eufemia/extensions/forms/constants/locales/countries/sv-SE'
import daDK_forms from '@dnb/eufemia/extensions/forms/constants/locales/da-DK'
import daDK_forms_countries from '@dnb/eufemia/extensions/forms/constants/locales/countries/da-DK'

const translations = mergeTranslations(
  svSE,
  svSE_forms,
  svSE_forms_countries, // if needed
  // etc. for other locales you want to add
)
```

## How to set the locale

In React based apps, use the shared Eufemia provider:

```jsx
import { Provider } from '@dnb/eufemia/shared'

const myLocale = 'en-GB'

render(
  <Provider locale={myLocale}>
    <MyApp>Eufemia components</MyApp>
  </Provider>
)
```

For component based locale, you can also make use of the `lang` attribute – if really needed:

```jsx
import { Provider } from '@dnb/eufemia/shared'

render(
  <Provider locale="en-GB">
    <MyApp>
      <HelpButton lang="nb-NO" />
    </MyApp>
  </Provider>
)
```

## How to set locale progressively

You can easily enhance or change translated strings progressively:

```jsx
import { Provider } from '@dnb/eufemia/shared'

render(
  <Provider
    locale="nb-NO"
    translations={{
      'nb-NO': {
        Modal: { closeTitle: 'Something' },
      },
    }}
  >
    <MyApp>Eufemia components</MyApp>
  </Provider>
)
```

## How to change the locale during runtime

You can even change the locale during runtime. Find more info in the [Provider docs](/uilib/usage/customisation/provider).

```tsx
import { Field } from '@dnb/eufemia/extensions/forms'
import { Provider, Context } from '@dnb/eufemia/shared'

const ChangeLocale = () => {
  const { setLocale, locale } = React.useContext(Context)

  return (
    <Field.Selection value={locale} onChange={(value) => setLocale(value)}>
      <Field.Option value="nb-NO" title="Norsk" />
      <Field.Option value="sv-SE" title="Svenska" />
      <Field.Option value="da-DK" title="Dansk" />
      <Field.Option value="en-GB" title="English (GB)" />
    </Field.Selection>
  )
}

render(
  <Provider>
    <MyApp>
      <ChangeLocale />
    </MyApp>
  </Provider>
)
```

## Provide your own translations

You can provide your own translations by using the shared [Provider](/uilib/usage/customisation/provider). Translation strings with several levels of depth can be given as a flat object with dot-notation, or as a nested object (cascaded).

```tsx
import { Provider } from '@dnb/eufemia/shared'

const nbNO = { myString: 'Min egendefinerte streng' }
const enGB = {
  // Cascaded translations
  Nested: {
    stringWithArgs: 'My custom string with an argument: {myKey}',
  },

  // Flat translations
  'Nested.stringWithArgs': 'My custom string with an argument: {myKey}',
}

const myTranslations = {
  'nb-NO': nbNO,
  'en-GB': enGB,
}

render(
  <Provider translations={myTranslations} locale="en-GB">
    <MyApp>
      <MyComponent />
    </MyApp>
  </Provider>
)
```

## Consume translations in your components

You can use the `useTranslation` hook to get the strings from the shared context. The hook returns an object with the strings and a `formatMessage` function you can use to get the translated strings with arguments.

```tsx
import { useTranslation } from '@dnb/eufemia/shared'

const myTranslations = {
  'nb-NO': { myString: 'Min egendefinerte streng' },
  'en-GB': {
    // Cascaded translations
    Nested: {
      stringWithArgs: 'My custom string with an argument: {myKey}',
    },

    // Flat translations
    'Nested.stringWithLinebreaks':
      'My custom string with a {br}line-break',
  },
}

type Translation = (typeof myTranslations)[keyof typeof myTranslations]

const MyComponent = () => {
  const t = useTranslation<Translation>()

  // Internal translations
  const existingString = t.Dropdown.title

  // Your translations
  const myString = t.myString

  // Use the "formatMessage" function to handle strings with arguments
  const myStringWithArgsA = t.formatMessage(t.Nested.stringWithArgs, {
    myKey: 'myValue',
  })
  // You can also get the string with a key (dot-notation)
  const myStringWithArgsB = t.formatMessage('Nested.stringWithArgs', {
    myKey: 'myValue',
  })

  // Render line-breaks
  const jsxOutput = t.renderMessage(t.Nested.stringWithLinebreaks)

  return <>MyComponent</>
}

render(
  <Provider translations={myTranslations} locale="en-GB">
    <MyApp>
      <MyComponent />
    </MyApp>
  </Provider>
)
```

**Good to know:** You can consume the strings with a dot-notated key, directly from
the `formatMessage` function:

```tsx
formatMessage('myGroup.subString')
```

### Formatted messages

For richer inline formatting in your translated strings, you can use the `renderWithFormatting` helper from `@dnb/eufemia/shared`. It supports simple markup tokens inside your messages:

- `{br}` inserts a line break (`<br />`).
- `**bold**` wraps content in `<strong>` by default.
- `_italic_` wraps content in `<em>` by default.
- `[label](url)` renders an anchor link.
  - Bare URLs (e.g. `http://…` or `https://…`) are automatically linked and use the URL as the label.
- Backticks render monospace literals. Useful for short, copy‑critical strings like reference IDs, promo codes etc. Example: `` `AB12-XYZ9` ``. You can customize the renderer via `renderWithFormatting(text, { code: (c) => <span className="dnb-code">{c}</span> })` if you prefer monospace styling without the semantic `<code>` tag.

You can also customize the wrappers and the break token.

```tsx
import {
  useTranslation,
  renderWithFormatting,
  Provider,
} from '@dnb/eufemia/shared'

const translations = {
  'en-GB': {
    'myGroup.subString':
      'Use **bold** and _italic_ with a {br}line-break.',
  },
}

type T = (typeof translations)['en-GB']

function MyComponent() {
  const t = useTranslation<T>()
  return <>{renderWithFormatting(t.myGroup.subString)}</>
}

function MyApp() {
  return (
    <Provider translations={translations} locale="en-GB">
      <MyComponent />
    </Provider>
  )
}
```

#### Use without translations

You can also use `renderWithFormatting` directly, without the translation context. This is handy for static copy or small strings you build at runtime.

```tsx
import { renderWithFormatting } from '@dnb/eufemia/shared'

const text =
  'Use **bold**, _italic_, `AB12-XYZ9` and a link https://www.dnb.no{br}Next line'

export function InlineFormattingExample() {
  return <>{renderWithFormatting(text)}</>
}
```

Array input and dynamic strings are also supported:

```tsx
import { renderWithFormatting } from '@dnb/eufemia/shared'

function ArrayInputExample() {
  const parts = ['Hello', '{br}', 'world! See https://example.com']
  return <>{renderWithFormatting(parts)}</>
}

function DynamicExample({ refId }: { refId: string }) {
  const text = `Keep your reference \`${'${refId}'}\` for support.`
  return <>{renderWithFormatting(text)}</>
}
```

### Rich text (inline elements)

Translation strings can contain XML-like tags that map to React components. Pass a function for each tag name — it receives the tag content and returns a React node:

```tsx
import { useTranslation, Provider } from '@dnb/eufemia/shared'

const translations = {
  'en-GB': {
    MyApp: {
      info: 'You can read more in <link>the documentation</link>.',
    },
  },
  'nb-NO': {
    MyApp: {
      info: 'Du kan lese mer i <link>dokumentasjonen</link>.',
    },
  },
}

type T = (typeof translations)['en-GB']

function MyComponent() {
  const { formatMessage } = useTranslation<T>()
  return (
    <P>
      {formatMessage('MyApp.info', {
        link: (chunks) => <Anchor href="/docs">{chunks}</Anchor>,
      })}
    </P>
  )
}

render(
  <Provider translations={translations} locale="en-GB">
    <MyComponent />
  </Provider>
)
```

This also works with the `Translation` component:

```tsx
<Translation
  id="MyApp.info"
  link={(chunks) => <Anchor href="/docs">{chunks}</Anchor>}
/>
```

You can use multiple tags and combine them with simple `{placeholder}` values:

```tsx
const translations = {
  'en-GB': {
    MyApp: {
      welcome:
        'Hello {name}, see <bold>important</bold> updates in <link>the changelog</link>.',
    },
  },
}

formatMessage('MyApp.welcome', {
  name: 'Ola',
  bold: (chunks) => <strong>{chunks}</strong>,
  link: (chunks) => <Anchor href="/changelog">{chunks}</Anchor>,
})
```

When [ICU Message Format](#icu-message-format) is enabled, tags work inside ICU messages as well:

```tsx
formatMessage('MyApp.items', {
  count: 3,
  link: (chunks) => <Anchor href="/cart">{chunks}</Anchor>,
})
// translation: 'You have {count, plural, one {# item} other {# items}}. <link>View cart</link>'
```

### ICU Message Format

Eufemia supports [ICU MessageFormat](https://unicode-org.github.io/icu/userguide/format_parse/messages/) syntax in translation strings. This enables pluralization, gender selection, and other locale-aware formatting directly in your messages.

ICU support is opt-in to keep your bundle size small. Enable it by importing the `icu` message formatter and passing it to the `Provider`:

```tsx
import { icu, Provider } from '@dnb/eufemia/shared'

render(
  <Provider messageFormatter={icu} locale="en-GB">
    <App />
  </Provider>
)
```

Once enabled, ICU syntax is detected automatically. If a translation string contains ICU patterns like `{key, plural, ...}` or `{key, select, ...}`, it will be processed through the ICU formatter. Simple `{placeholder}` strings continue to work as before.

#### How ICU syntax works

An ICU message is a plain string. The simplest form is just literal text:

```
Hello everyone
```

To insert a dynamic value, wrap a key name in curly braces. The key is looked up in the values you pass and its value is placed into the output:

```
Hello {name}
```

To format a value based on its type, add a type after the key:

```
{key, type}
```

To further control the output, add a format or style:

```
{key, type, format}
```

For example, `{amount, number}` formats a number with locale-aware grouping, and `{d, date, long}` formats a date in the long style for the current locale.

Some types like `plural` and `select` use a set of matches instead of a format string. Each match maps a value to an output message:

```
{count, plural, one {# item} other {# items}}
```

The `other` match is always required — it acts as the fallback when no other match applies. Inside a `plural` match, `#` is replaced with the formatted number.

Messages can be nested — for example, combining `select` with `plural`:

```
{gender, select,
  male {He has {count, plural, one {# item} other {# items}}}
  female {She has {count, plural, one {# item} other {# items}}}
  other {They have {count, plural, one {# item} other {# items}}}
}
```

To escape curly braces or other ICU syntax characters, wrap them in single quotes:

```
This is not a placeholder: '{value}'
```

Two consecutive single quotes produce a literal single quote: `This isn''t a placeholder` → `This isn't a placeholder`. For human-readable strings, prefer curly quotes (`'`, U+2019) instead of the ASCII apostrophe.

The following sections show each ICU feature in detail with Eufemia examples.

#### Pluralization

Use `plural` to vary text based on a count. The `#` token inside the message is replaced with the formatted number. The `other` category is always required.

```tsx
import { useTranslation, Provider, icu } from '@dnb/eufemia/shared'

const translations = {
  'en-GB': {
    Notifications: {
      summary:
        'You have {count, plural, =0 {no new notifications} one {# new notification} other {# new notifications}}.',
    },
  },
  'nb-NO': {
    Notifications: {
      summary:
        'Du har {count, plural, =0 {ingen nye varsler} one {# nytt varsel} other {# nye varsler}}.',
    },
  },
}

type T = (typeof translations)['en-GB']

function NotificationBanner() {
  const { formatMessage } = useTranslation<T>()
  return <P>{formatMessage('Notifications.summary', { count: 3 })}</P>
  // en-GB: "You have 3 new notifications."
  // nb-NO: "Du har 3 nye varsler."
}

render(
  <Provider
    messageFormatter={icu}
    translations={translations}
    locale="en-GB"
  >
    <NotificationBanner />
  </Provider>
)
```

Plural categories vary by locale. English uses `one` and `other`. Some languages (like Arabic) use `zero`, `one`, `two`, `few`, `many`, and `other`. Use exact matches like `=0` when you need specific wording for a particular number regardless of locale.

#### Select

Use `select` to choose between message variants based on a string value. This is commonly used for gendered text or category-based messages.

```tsx
const translations = {
  'en-GB': {
    Status: {
      response:
        '{gender, select, male {He} female {She} other {They}} responded to your request.',
    },
  },
}

type T = (typeof translations)['en-GB']

function StatusMessage() {
  const { formatMessage } = useTranslation<T>()
  return <P>{formatMessage('Status.response', { gender: 'female' })}</P>
  // Output: "She responded to your request."
}
```

#### Selectordinal

Use `selectordinal` for ordinal number formatting (1st, 2nd, 3rd, etc.):

```tsx
const translations = {
  'en-GB': {
    Ranking: {
      position:
        'You finished in {pos, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} place!',
    },
  },
}

type T = (typeof translations)['en-GB']

function RankingMessage() {
  const { formatMessage } = useTranslation<T>()
  return <P>{formatMessage('Ranking.position', { pos: 3 })}</P>
  // Output: "You finished in 3rd place!"
}
```

#### Number formatting

Use `{value, number}` to format numbers with locale-aware grouping and decimal separators. You can add [ICU number skeletons](https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html) for currency, percent, and compact notation.

```tsx
const translations = {
  'en-GB': {
    Account: {
      // Basic number: "1,234.56"
      total: 'Total: {amount, number}',

      // Currency: "kr 1 234,00" (nb-NO) / "NOK 1,234.00" (en-GB)
      balance: 'Balance: {amount, number, ::currency/NOK}',

      // Percent: "25%"
      progress: 'Progress: {pct, number, ::percent}',

      // Compact: "1.5K"
      followers: '{count, number, ::compact-short} followers',
    },
  },
}

type T = (typeof translations)['en-GB']

function AccountInfo() {
  const { formatMessage } = useTranslation<T>()
  return (
    <>
      <P>{formatMessage('Account.total', { amount: 1234.56 })}</P>
      <P>{formatMessage('Account.balance', { amount: 1234 })}</P>
      <P>{formatMessage('Account.progress', { pct: 0.25 })}</P>
      <P>{formatMessage('Account.followers', { count: 1500 })}</P>
    </>
  )
}
```

#### Date formatting

Use `{value, date}` with an optional style — `short`, `medium`, `long`, or `full` — to format dates according to the locale:

```tsx
const translations = {
  'en-GB': {
    Events: {
      // Default: "15 Jan 2025"
      created: 'Created: {d, date}',

      // Short: "15/01/2025"
      shortDate: '{d, date, short}',

      // Medium: "15 Jan 2025"
      mediumDate: '{d, date, medium}',

      // Long: "15 January 2025"
      longDate: '{d, date, long}',

      // Full: "Wednesday, 15 January 2025"
      fullDate: '{d, date, full}',
    },
  },
}

type T = (typeof translations)['en-GB']

function EventDate() {
  const { formatMessage } = useTranslation<T>()
  const d = new Date(2025, 0, 15)
  return <P>{formatMessage('Events.longDate', { d })}</P>
  // en-GB: "15 January 2025"
}
```

#### Time formatting

Use `{value, time}` with a style to format times:

```tsx
const translations = {
  'en-GB': {
    Schedule: {
      // Short: "14:30"
      starts: 'Starts at {t, time, short}',

      // Medium: "14:30:45"
      precise: 'Logged at {t, time, medium}',
    },
  },
}

type T = (typeof translations)['en-GB']

function ScheduleInfo() {
  const { formatMessage } = useTranslation<T>()
  return (
    <P>
      {formatMessage('Schedule.starts', {
        t: new Date(2025, 0, 15, 14, 30),
      })}
    </P>
  )
  // en-GB: "Starts at 14:30"
}
```

#### Pre-formatted values

ICU does not cover all formatting needs — for example, bank account numbers or national identity numbers. For these, format the value before passing it in as a simple placeholder. You can use Eufemia's formatting utilities like `formatBankAccountNumber`:

```tsx
import { useTranslation } from '@dnb/eufemia/shared'
import { formatBankAccountNumber } from '@dnb/eufemia/components/NumberFormat'

const translations = {
  'en-GB': {
    Account: {
      info: 'Your account number is {account}.',
    },
  },
}

type T = (typeof translations)['en-GB']

function AccountInfo({ accountNumber }: { accountNumber: string }) {
  const { formatMessage } = useTranslation<T>()

  // Use Eufemia's formatter for bank account numbers
  const account = formatBankAccountNumber(accountNumber)

  return <P>{formatMessage('Account.info', { account })}</P>
  // Output: "Your account number is 2000 12 34567."
}
```

Other formatting utilities like `formatNationalIdentityNumber`, `formatOrganizationNumber`, and `formatPhoneNumber` work the same way. See the [NumberFormat](/uilib/components/NumberFormat/) docs for the full list.

#### Nested messages

ICU messages can be nested — for example, combining `select` with `plural`:

```tsx
const translations = {
  'en-GB': {
    Items: {
      summary:
        '{gender, select, male {He has {count, plural, one {# item} other {# items}}} female {She has {count, plural, one {# item} other {# items}}} other {They have {count, plural, one {# item} other {# items}}}}',
    },
  },
}

type T = (typeof translations)['en-GB']

function ItemSummary() {
  const { formatMessage } = useTranslation<T>()
  return (
    <P>{formatMessage('Items.summary', { gender: 'female', count: 3 })}</P>
  )
  // Output: "She has 3 items"
}
```

#### With the Translation component

ICU messages also work with the `<Translation />` component. Pass values as props:

```tsx
import { Translation, Provider, icu } from '@dnb/eufemia/shared'

const translations = {
  'en-GB': {
    Cart: {
      items:
        'You have {count, plural, =0 {an empty cart} one {# item} other {# items}} in your cart.',
    },
  },
}

render(
  <Provider
    messageFormatter={icu}
    translations={translations}
    locale="en-GB"
  >
    <P>
      <Translation id="Cart.items" count={5} />
    </P>
    {/* Output: "You have 5 items in your cart." */}
  </Provider>
)
```

For a full reference of ICU MessageFormat syntax, see the [FormatJS ICU syntax guide](https://formatjs.github.io/docs/core-concepts/icu-syntax) and the [ICU User Guide](https://unicode-org.github.io/icu/userguide/format_parse/messages/).

### Fallback for missing or partial translations

The shared `useTranslation` hook will output missing keys when:

- Empty explicit locale: returns pointer strings (e.g. `MyNamespace.label`) derived from `fallbackLocale="nb-NO"`.
- Partial explicit locale: merges missing keys as pointer strings, preserving existing ones.
- Non-existent current locale (no explicit entry in your translations): the hook preserves defaults (no pointers).

```tsx
import { useTranslation, Provider } from '@dnb/eufemia/shared'

const translations = {
  'sv-SE': {}, // empty explicit current-locale
  'en-GB': { MyNamespace: { label: 'English label' } },
}

type T = (typeof translations)['en-GB']

function Example() {
  const t = useTranslation<T>({
    fallbackLocale: 'en-GB', // default: 'nb-NO'
  })
  return <>{t.MyNamespace.label /* 'MyNamespace.label' */}</>
}

render(
  <Provider locale="sv-SE" translations={translations}>
    <Example />
  </Provider>
)
```

## Load translations dynamically

When you have many locales or large translation files, you can load them on demand using the `translationsLoader` prop on the [Provider](/uilib/usage/customisation/provider/). It accepts an async function that receives the current locale and returns a translations object. The loader is called on mount and whenever the locale changes.

Components render with default translations immediately. When the loader resolves, translations are merged in and components re-render with the updated strings.

The loader function can use any source — dynamic `import()` of `.ts`, `.js`, or `.json` files, `fetch()` calls, or any other async operation. As long as the function returns a translations object, it works.

```tsx
import { Provider } from '@dnb/eufemia/shared'

const translationsLoader = async (locale) => {
  switch (locale) {
    case 'en-GB':
      return (await import('./locales/en-GB')).default
    case 'sv-SE':
      return (await import('./locales/sv-SE')).default
    default:
      return (await import('./locales/nb-NO')).default
  }
}

render(
  <Provider translationsLoader={translationsLoader} locale="en-GB">
    <MyApp>Eufemia components</MyApp>
  </Provider>
)
```

You can combine `translationsLoader` with the static `translations` prop. Static translations are available immediately, and loaded translations are merged on top:

```tsx
import { Provider } from '@dnb/eufemia/shared'

const staticTranslations = {
  'nb-NO': { Modal: { closeTitle: 'Lukk' } },
}

const translationsLoader = async (locale) => {
  const response = await fetch(`/api/translations/${locale}`)
  return response.json()
}

render(
  <Provider
    translations={staticTranslations}
    translationsLoader={translationsLoader}
    locale="nb-NO"
  >
    <MyApp>Eufemia components</MyApp>
  </Provider>
)
```

The `translationsLoader` is also available on [Form.Handler](/uilib/extensions/forms/Form/Handler/) for form-scoped translations. Read more in the [Forms getting started guide](/uilib/extensions/forms/getting-started/#load-translations-dynamically).

### Async translations with translationsLoader

Use the `translationsLoader` prop to load translations asynchronously, for example from a CDN or a lazy import. The loader receives the current locale and should return a translations object.

```tsx
import { Provider } from '@dnb/eufemia/shared'

const translationsLoader = async (locale) => {
  const response = await fetch(`/translations/${locale}.json`)
  return response.json()
}

render(
  <Provider translationsLoader={translationsLoader}>
    <MyApp />
  </Provider>
)
```

Because the consumer owns the loader function, you can handle loading state, errors, and retries directly inside it:

```tsx
import { Provider } from '@dnb/eufemia/shared'

function App() {
  const [translationsLoading, setTranslationsLoading] =
    React.useState(true)

  const translationsLoader = React.useCallback(async (locale) => {
    setTranslationsLoading(true)

    try {
      const translations = await import(`../translations/${locale}.json`)
      return translations.default
    } catch (error) {
      console.error('Failed to load translations', error)
      return null
    } finally {
      setTranslationsLoading(false)
    }
  }, [])

  return (
    <Provider
      translationsLoader={translationsLoader}
      skeleton={translationsLoading}
    >
      <MyApp />
    </Provider>
  )
}
```

You can also return fallback translations when an error occurs, so the UI still renders meaningful content in the correct language:

```tsx
import { Provider, useTranslation } from '@dnb/eufemia/shared'

const fallbackTranslations = {
  'nb-NO': {
    errorMessage: 'Kunne ikke laste oversettelser',
  },
  'en-GB': {
    errorMessage: 'Could not load translations',
  },
}

const translationsLoader = async (locale) => {
  try {
    const response = await fetch(`/api/translations/${locale}`)
    return response.json()
  } catch (error) {
    return fallbackTranslations
  }
}

type FallbackTranslation =
  (typeof fallbackTranslations)[keyof typeof fallbackTranslations]

function ErrorBanner() {
  const { errorMessage } = useTranslation<FallbackTranslation>()
  if (errorMessage) {
    return <FormStatus state="error" text={errorMessage} />
  }
  return null
}

render(
  <Provider translationsLoader={translationsLoader}>
    <ErrorBanner />
    <MyApp />
  </Provider>
)
```

## TypeScript support

```tsx
import { Provider, Locales } from '@dnb/eufemia/shared'

const nbNO = {
  myString: 'Min egendefinerte streng',
}
const enGB = {
  myString: 'My custom string',
} satisfies typeof nbNO // Ensure the types are compatible

const myTranslations = {
  'nb-NO': nbNO,
  'en-GB': enGB,
}

// Infer the type of the translations
type Translation = (typeof myTranslations)[keyof typeof myTranslations]
```

## How to combine with other tools

You can easily combine the locales support it with other translation tools, like `react-intl`.

Like, having the Eufemia components strings inside a JSON object/file `en.json`:

```json
{
  "Modal.closeTitle": "Overwrite",
  "other.string": "{foo} ({bar} of {max})"
}
```

and use it like this:

```jsx
import { Provider as EufemiaProvider } from '@dnb/eufemia/shared'
import nb from './nb.json' // Has to be an JavaScript object

render(
  <EufemiaProvider
    locale="nb-NO"
    translations={{
      'nb-NO': nb,
    }}
  >
    <MyApp>Eufemia components</MyApp>
  </EufemiaProvider>
)
```

### Cascaded object (flat object, dot-notated keys) support

1. Lets say you have your translation files as JSON object/files `en.json`:

```json
{
  "Modal.closeTitle": "Overwrite",
  "my.string": "string {foo}"
}
```

2. and use it with a React hook like this:

```tsx
import {
  useTranslation,
  Provider as EufemiaProvider,
} from '@dnb/eufemia/shared'

import nb from './nb.json'
import en from './en.json'

const MyComponent = () => {
  // Note: no TypeScript support when using an identifier.
  const str = useTranslation('my.string', {
    foo: 'bar',
  })

  return str
}

render(
  <EufemiaProvider
    locale="nb-NO"
    translations={{
      'nb-NO': nb,
      'en-GB': en,
    }}
  >
    <MyComponent />
  </EufemiaProvider>
)
```

3. or as a React component:

```tsx
import {
  Translation,
  Provider as EufemiaProvider,
} from '@dnb/eufemia/shared'

import nb from './nb.json'
import en from './en.json'

render(
  <EufemiaProvider
    locale="nb-NO"
    translations={{
      'nb-NO': nb,
      'en-GB': en,
    }}
  >
    <Translation id="my.string" foo="bar" />
  </EufemiaProvider>
)
```

For TypeScript support, you can use the `Translation` component with a function. You may also want to make a wrapper, so you can use your own translation types:

```tsx
import {
  Translation,
  TranslationProps,
  Provider as EufemiaProvider,
} from '@dnb/eufemia/shared'

const translations = {
  'nb-NO': { my: { string: 'streng {foo}' } },
  'en-GB': { my: { string: 'string {foo}' } },
}
type TranslationType = (typeof translations)[keyof typeof translations]

render(
  <EufemiaProvider locale="nb-NO" translations={translations}>
    <Translation<TranslationType> id={(t) => t.my.string} foo="bar" />
  </EufemiaProvider>
)
```

### Formatting markers inside `<Translation />`

When using `<Translation />`, simple inline formatting is applied automatically:

- `{br}` → line break
- `**bold**`, `_italic_`, `` `code` ``
- `[label](https://…)` links, and bare URLs become anchors

```tsx
import {
  Translation,
  Provider as EufemiaProvider,
} from '@dnb/eufemia/shared'

const translations = {
  'en-GB': {
    info: 'Use **bold** and _italic_ with a {br}line-break.',
  },
}
type TranslationType = (typeof translations)[keyof typeof translations]

render(
  <EufemiaProvider translations={translations} locale="en-GB">
    <P>
      <Translation<TranslationType> id={(t) => t.info} />
    </P>
  </EufemiaProvider>
)
```

## How to add Eufemia provided locales

### Eufemia components

Eufemia provides component translations for the following locales:

<Ul>
  {Object.keys(languageDisplayNames).map((l) => (
    <Li key={l}>
      <Anchor
        href={`https://github.com/dnbexperience/eufemia/blob/main/packages/dnb-eufemia/src/shared/locales/${l}.ts`}
      >
        {l}
      </Anchor>
    </Li>
  ))}
</Ul>

To include e.g. `sv-SE` you can use the following code:

```js
import { Provider } from '@dnb/eufemia/shared'
import svSE from '@dnb/eufemia/shared/locales/sv-SE'

render(
  <Provider translations={svSE} locale="sv-SE">
    Your app
  </Provider>
)
```

To include e.g. `da-DK` you can use the following code:

```js
import { Provider } from '@dnb/eufemia/shared'
import daDK from '@dnb/eufemia/shared/locales/da-DK'

render(
  <Provider translations={daDK} locale="da-DK">
    Your app
  </Provider>
)
```

### Eufemia Forms

Eufemia provides forms translations for the following locales:

<Ul>
  {Object.keys(languageDisplayNames).map((l) => (
    <Li key={l}>
      <Anchor
        href={`https://github.com/dnbexperience/eufemia/blob/main/packages/dnb-eufemia/src/extensions/forms/constants/locales/${l}.ts`}
      >
        {l}
      </Anchor>
    </Li>
  ))}
</Ul>

**Note:** Only `nb-NO` and `en-GB` are included by default.

To support other locales such as `sv-SE` or `da-DK`, you need to import and merge the locale translations yourself.

Use `mergeTranslations` to combine the forms translations (and country translations when needed) before you pass them to `Form.Handler` or `Provider`.

```js
import { mergeTranslations } from '@dnb/eufemia/shared'
import svSE_forms from '@dnb/eufemia/extensions/forms/constants/locales/sv-SE'
import svSE_forms_countries from '@dnb/eufemia/extensions/forms/constants/locales/countries/sv-SE'
import daDK_forms from '@dnb/eufemia/extensions/forms/constants/locales/da-DK'
import daDK_forms_countries from '@dnb/eufemia/extensions/forms/constants/locales/countries/da-DK'

const translations = mergeTranslations(
  svSE_forms,
  svSE_forms_countries, // if needed
  daDK_forms, // if needed
  daDK_forms_countries // if needed
)
```

You can provide the merged translations for fields and values in a few different ways.

#### Form.Handler

You can provide forms translations to the `translations` property within the [Form.Handler](/uilib/extensions/forms/Form/Handler/) component like this:

```js
import { Form } from '@dnb/eufemia/src/extensions/forms'
import { mergeTranslations } from '@dnb/eufemia/shared'
import svSE_forms from '@dnb/eufemia/extensions/forms/constants/locales/sv-SE'
import svSE_forms_countries from '@dnb/eufemia/extensions/forms/constants/locales/countries/sv-SE'

const translations = mergeTranslations(svSE_forms, svSE_forms_countries)

render(
  <Form.Handler translations={translations} locale="sv-SE">
    Your form
  </Form.Handler>
)
```

#### Global translations

However, instead of providing the forms translations per form, you can also provide them globally using the `Provider` component:

```js
import { Provider, mergeTranslations } from '@dnb/eufemia/shared'
import svSE from '@dnb/eufemia/shared/locales/sv-SE'
import svSE_forms from '@dnb/eufemia/extensions/forms/constants/locales/sv-SE'
import svSE_forms_countries from '@dnb/eufemia/extensions/forms/constants/locales/countries/sv-SE'

const translations = mergeTranslations(
  svSE,
  svSE_forms,
  svSE_forms_countries
)

render(
  <Provider translations={translations} locale="sv-SE">
    Your app, including Eufemia Forms
  </Provider>
)
```

## How to add new locales

Create a new file (`nn-NO.js`) containing all the strings:

```js
export default {
  'nn-NO': {
    GlobalError: {
      404: {
        title: 'Me finn ikkje sida du leitar etter …',
      },
    },
  },
}
```

And add the file, like so:

```jsx
import { Provider } from '@dnb/eufemia/shared'
import myTranslations from './locales/nn-NO'

render(
  <Provider translations={myTranslations}>
    <MyApp>Eufemia components</MyApp>
  </Provider>
)
```

### Add or update the locales during runtime

```tsx
import { Provider, Context } from '@dnb/eufemia/shared'

import myTranslations from './locales/nn-NO'

const ChangeLocale = () => {
  const { update, locale } = React.useContext(Context)

  // Add new locales
  update({ locales: myTranslations, locale: 'nn-NO' })

  return locale
}

render(
  <Provider>
    <MyApp>
      ...
      <ChangeLocale />
      ...
    </MyApp>
  </Provider>
)
```

## Error handling

`formatMessage` provides development warnings (`console.log`) to help catch translation bugs. These warnings are **silent in production** (`NODE_ENV=production`).

| Scenario                       | Behavior                                                                                                                                                                   |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Missing message id**         | Returns the raw id as fallback. Warns in development when the id contains a dot (e.g. `MyApp.key`).                                                                        |
| **Missing variable**           | Leaves the `{placeholder}` in the output. Warns about unreplaced placeholders.                                                                                             |
| **Invalid ICU syntax**         | Catches the parse error, returns the message id as fallback, and warns.                                                                                                    |
| **Missing ICU variable**       | Catches the runtime error, returns the message id as fallback, and warns.                                                                                                  |
| **Missing locale bundle**      | Falls back to the default locale (`nb-NO`) and warns.                                                                                                                      |
| **Fallback locale**            | See [Fallback for missing or partial translations](#fallback-for-missing-or-partial-translations).                                                                         |
| **`{br}` in messages**         | Not treated as a missing variable. Handled by `renderWithFormatting`.                                                                                                      |
| **Function args without tags** | When a function is passed as a replacement value but no matching `<tag>` exists, the function is called without arguments and its return value is used as the replacement. |
