Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(useTranslation): add support for custom translations and formatMessage method #3442

Merged
merged 2 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 10 additions & 16 deletions packages/dnb-design-system-portal/src/core/ChangeLocale.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
import React from 'react'
import { Dropdown } from '@dnb/eufemia/src'

import Context from '@dnb/eufemia/src/shared/Context'
import Context, { InternalLocale } from '@dnb/eufemia/src/shared/Context'
import { setLang } from './PortalProviders'
import { Field } from '@dnb/eufemia/src/extensions/forms'

export default function ChangeLocale({ listUSLocale = null, ...props }) {
const { locale, setLocale } = React.useContext(Context)

const date = {
'nb-NO': 'Norsk',
'en-GB': 'English (GB)',
}
if (listUSLocale) {
date['en-US'] = 'English (US)'
}

return (
<Dropdown
<Field.Selection
value={locale}
data={date}
on_change={({ data: { value } }) => {
setLocale(value)
onChange={(value) => {
setLocale(value as InternalLocale)
setLang(value)
}}
{...props}
/>
>
<Field.Option value="nb-NO" title="Norsk" />
<Field.Option value="en-GB" title="English (GB)" />
{listUSLocale && <Field.Option value="en-US" title="English (US)" />}
</Field.Selection>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import enUS from '@dnb/eufemia/src/shared/locales/en-US'
import { isTrue } from '@dnb/eufemia/src/shared/component-helper'
import PortalLayout, { PortalLayoutProps } from './PortalLayout'
import { useThemeHandler } from 'gatsby-plugin-eufemia-theme-handler'
import { InternalLocale } from '@dnb/eufemia/src/shared/Context'

// This ensures we processes also the css prop during build
// More into in the docs: https://emotion.sh/docs/ssr#gatsby
Expand Down Expand Up @@ -86,9 +87,9 @@ function SkeletonEnabled({ children }) {
return children
}

export function getLang(locale = 'nb-NO') {
export function getLang(locale: InternalLocale = 'nb-NO'): InternalLocale {
try {
const l = window.localStorage.getItem('locale')
const l = window.localStorage.getItem('locale') as InternalLocale
if (l) {
locale = l
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,30 +78,28 @@ render(

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

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

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

return <Dropdown
value={locale}
data={{ 'nb-NO': 'Norsk', 'en-GB': 'English' }}
on_change={({ data: { value } }) => {
setLocale(value)
}}
/>
return (
<Field.Selection value={locale} onChange={(value) => setLocale(value)}>
<Field.Option value="nb-NO" title="Norsk" />
<Field.Option value="en-GB" title="English (GB)" />
</Field.Selection>
)
}

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

Expand Down Expand Up @@ -140,9 +138,61 @@ render(

You have even the option to extend the strings with your own and use it as an internationalization tool replacement for e.g. `react-intl`.

### The useTranslation Hook
## Get the strings from Context

It is possible to use the Eufemia shared Provider for your own project / App localization.

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

const customTranslation = {
// extend the translation
'en-GB': {
myString: 'Custom string',
myGroup: {
subString: 'Second string',
stringWithArgument: 'String with {myArg}',
},
},
}

Now, lets say you have your translation files as JSON object/files `en.json`:
type CustomLocales = keyof typeof customTranslation
type CustomTranslation = (typeof customTranslation)[CustomLocales]

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

... and consume the strings in your components:

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

function MyComponent() {
const { myString, myGroup, formatMessage } =
useTranslation<CustomTranslation>()
return (
<>
<P>{myString}</P>
<P>{myGroup.subString}</P>
<P>
{formatMessage('myGroup.stringWithArgument', {
myArg: 'dynamic-value',
})}
</P>
</>
)
}
```

### Cascaded string ids support

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

```json
{
Expand All @@ -151,17 +201,18 @@ Now, lets say you have your translation files as JSON object/files `en.json`:
}
```

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

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

```jsx
import EufemiaProvider from '@dnb/eufemia/shared/Provider'
import useTranslation, {
Translation,
} from '@dnb/eufemia/shared/useTranslation'
import nb from './nb.json'
import en from './en.json'

const Component = () => {
const MyComponent = () => {
const str = useTranslation('my.string', {
foo: 'bar',
})
Expand All @@ -177,49 +228,35 @@ render(
'en-GB': en,
}}
>
<Component />
<Translation id="my.string" foo="bar" />
<MyComponent />
</EufemiaProvider>,
)
```

## Get the strings from Context

It is possible to use the Eufemia shared Provider for your own project / App localization.
3. or as a React component:

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

const customTranslation = {
// extend the translation
'en-GB': {
myString: 'Custom string'
myGroup: {
subString: 'Second string'
}
}
}
import nb from './nb.json'
import en from './en.json'

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

... and consume the strings in your components, like **MyComponent**:

```jsx
import Context from '@dnb/eufemia/shared/Context'

export default function MyComponent() {
const { translation } = React.useContext(Context)
return <>{translation.myString}</>
}
```

## How add new locales

Create a new file (`sv-SE.js`) containing all the strings:
Expand Down Expand Up @@ -247,15 +284,15 @@ import Provider from '@dnb/eufemia/shared/Provider'
import customTranslation from './locales/sv-SE'

render(
<Provider locales={customTranslation}>
<Provider translations={customTranslation}>
<MyApp>Eufemia components</MyApp>
</Provider>,
)
```

or add/update the locales during runtime:

```jsx
```tsx
import Provider from '@dnb/eufemia/shared/Provider'
import Context from '@dnb/eufemia/shared/Context'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ render(

You can either set the locale as a properly e.g. `<Provider locale="en-GB" ...` and handle the change from the app root, or change it inside the app, respective Context:

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

Expand All @@ -53,13 +54,10 @@ const ChangeLocale = () => {
}, [])

return (
<Dropdown
value={locale}
data={{ 'en-GB': 'English', 'nb-NO': 'Norsk' }}
on_change={({ data: { value } }) => {
setLocale(value)
}}
/>
<Field.Selection value={locale} onChange={(value) => setLocale(value)}>
<Field.Option value="nb-NO" title="Norsk" />
<Field.Option value="en-GB" title="English (GB)" />
</Field.Selection>
)
}

Expand All @@ -79,7 +77,8 @@ Yes, it's possible to have nested providers.

You have then to decide what you need to update.

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

Expand All @@ -96,11 +95,13 @@ const ChangeLocale = () => {
}, [])

return (
<Dropdown
<Field.Selection
value={locale}
data={{ 'en-GB': 'English', 'nb-NO': 'Norsk' }}
on_change={({ data }) => data && setCurrentLocale(data.selected_key)}
/>
onChange={(value) => setCurrentLocale(value)}
>
<Field.Option value="nb-NO" title="Norsk" />
<Field.Option value="en-GB" title="English (GB)" />
</Field.Selection>
)
}

Expand Down
4 changes: 2 additions & 2 deletions packages/dnb-eufemia/src/components/form-row/FormRow.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { SectionSpacing, SectionStyleTypes } from '../Section';
import type { FormLabelLabelDirection, FormLabelText } from '../FormLabel';
import type { SkeletonShow } from '../Skeleton';
import type { SpacingProps } from '../space/types';
import type { Locale } from '../../shared/Context';
import type { InternalLocale } from '../../shared/Context';
import type { GlobalStatusConfigObject } from '../GlobalStatus';
export type FormRowDirection = 'vertical' | 'horizontal';
export type FormRowChildren =
Expand All @@ -21,7 +21,7 @@ export interface FormRowProps
label_class?: string;
no_label?: boolean;
no_fieldset?: boolean;
locale?: Locale;
locale?: InternalLocale;
wrap?: boolean;
direction?: FormRowDirection;
vertical?: boolean;
Expand Down
4 changes: 2 additions & 2 deletions packages/dnb-eufemia/src/components/form-set/FormSet.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { SectionSpacing, SectionStyleTypes } from '../Section';
import type { SkeletonShow } from '../Skeleton';
import type { SpacingProps } from '../space/types';
import type { FormLabelLabelDirection, FormLabelText } from '../FormLabel';
import type { Locale } from '../../shared/Context';
import type { InternalLocale } from '../../shared/Context';
import type { GlobalStatusConfigObject } from '../GlobalStatus';
export type FormSetDirection = 'vertical' | 'horizontal';
export type FormSetChildren =
Expand All @@ -26,7 +26,7 @@ export interface FormSetProps
label_class?: string;
no_label?: boolean;
no_fieldset?: boolean;
locale?: Locale;
locale?: InternalLocale;
wrap?: boolean;
direction?: FormSetDirection;
vertical?: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import type { Locale } from '../../shared/Context';
import type { InternalLocale } from '../../shared/Context';
import type { ButtonIconPosition } from '../Button';
import type { FormLabelLabelDirection, FormLabelText } from '../FormLabel';
import type {
Expand Down Expand Up @@ -73,7 +73,7 @@ export interface InputMaskedProps
/**
* Define the locale to be used in the `as_number` or `as_currency` masked. It will be inherited from the [Eufemia Provider](/uilib/usage/customisation/provider) if not given. Defaults to `nb-NO`.
*/
locale?: Locale;
locale?: InternalLocale;
/**
* Set to `true` to use `NOK` or give it a currency code e.g. `USD` to automatically set a currency mask based on the given or inherited locale.
*/
Expand Down
Loading
Loading