Skip to content

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:

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

Here are the default strings located:

// 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:

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:

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:

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.

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. Translation strings with several levels of depth can be given as a flat object with dot-notation, or as a nested object (cascaded).

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.

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:

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.

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.

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:

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:

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:

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

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

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 is enabled, tags work inside ICU messages as well:

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 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:

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

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.

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.):

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 for currency, percent, and compact notation.

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:

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:

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:

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 docs for the full list.

Nested messages

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

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:

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 and the ICU User Guide.

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

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:

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 for form-scoped translations. Read more in the Forms getting started guide.

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.

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:

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:

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

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:

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

and use it like this:

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:
{
"Modal.closeTitle": "Overwrite",
"my.string": "string {foo}"
}
  1. and use it with a React hook like this:
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>
)
  1. or as a React component:
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:

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
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:

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

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:

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:

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.

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 component like this:

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:

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:

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

And add the file, like so:

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

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

ScenarioBehavior
Missing message idReturns the raw id as fallback. Warns in development when the id contains a dot (e.g. MyApp.key).
Missing variableLeaves the {placeholder} in the output. Warns about unreplaced placeholders.
Invalid ICU syntaxCatches the parse error, returns the message id as fallback, and warns.
Missing ICU variableCatches the runtime error, returns the message id as fallback, and warns.
Missing locale bundleFalls back to the default locale (nb-NO) and warns.
Fallback localeSee Fallback for missing or partial translations.
{br} in messagesNot treated as a missing variable. Handled by renderWithFormatting.
Function args without tagsWhen 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.