UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

1,304 lines (1,012 loc) 36.3 kB
--- title: 'Locale / Translation' version: 11.3.0 generatedAt: 2026-05-19T08:46:53.034Z 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 a 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. |