From 5b07becb992b27b4e8bced2a8c6b716efcae9bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Wed, 16 Oct 2024 09:50:11 +0200 Subject: [PATCH] feat: add `renderMessage` to useTranslation hook for line-break support (#4125) --- .../forms/Form/useTranslation/info.mdx | 9 ++++- .../extensions/forms/getting-started.mdx | 6 ++- .../usage/customisation/localization.mdx | 6 ++- .../hooks/__tests__/useTranslation.test.tsx | 3 ++ .../shared/__tests__/useTranslation.test.tsx | 39 +++++++++++++++++++ .../dnb-eufemia/src/shared/useTranslation.tsx | 35 +++++++++++++++-- 6 files changed, 91 insertions(+), 7 deletions(-) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useTranslation/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useTranslation/info.mdx index 353578203b7..909a0a283df 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useTranslation/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useTranslation/info.mdx @@ -28,12 +28,13 @@ render( In addition to all internal translations, you also get; - `formatMessage` - a function you can use to get a specific translation based on a key (flattened object with dot-notation). +- `renderMessage` - a function you can use to render a string with line-breaks. It converts `{br}` to a JSX line-break. ```tsx import { Form } from '@dnb/eufemia/extensions/forms' function MyComponent() { - const { formatMessage } = Form.useTranslation() + const { formatMessage, renderMessage } = Form.useTranslation() const errorRequired = formatMessage('Field.errorRequired') return <>MyComponent @@ -62,7 +63,8 @@ const myTranslations = { }, // Flat translations - 'Nested.stringWithArgs': 'My custom string with an argument: {myKey}', + 'Nested.stringWithLinebreaks': + 'My custom string with a {br}line-break', }, } @@ -84,6 +86,9 @@ const MyComponent = () => { myKey: 'myValue', }) + // Render line-breaks + const jsxOutput = t.renderMessage(t.Nested.stringWithLinebreaks) + return <>MyComponent } diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx index bb73d810b72..7803658e379 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx @@ -638,7 +638,8 @@ const myTranslations = { }, // Flat translations - 'Nested.stringWithArgs': 'My custom string with an argument: {myKey}', + 'Nested.stringWithLinebreaks': + 'My custom string with a {br}line-break', }, } @@ -660,6 +661,9 @@ const MyComponent = () => { myKey: 'myValue', }) + // Render line-breaks + const jsxOutput = t.renderMessage(t.Nested.stringWithLinebreaks) + return <>MyComponent } diff --git a/packages/dnb-design-system-portal/src/docs/uilib/usage/customisation/localization.mdx b/packages/dnb-design-system-portal/src/docs/uilib/usage/customisation/localization.mdx index 486d1676cab..0c52ef1e19c 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/usage/customisation/localization.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/usage/customisation/localization.mdx @@ -151,7 +151,8 @@ const myTranslations = { }, // Flat translations - 'Nested.stringWithArgs': 'My custom string with an argument: {myKey}', + 'Nested.stringWithLinebreaks': + 'My custom string with a {br}line-break', }, } @@ -173,6 +174,9 @@ const MyComponent = () => { myKey: 'myValue', }) + // Render line-breaks + const jsxOutput = t.renderMessage(t.Nested.stringWithLinebreaks) + return <>MyComponent } diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useTranslation.test.tsx b/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useTranslation.test.tsx index 7ede2e76bfb..5f244349017 100644 --- a/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useTranslation.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useTranslation.test.tsx @@ -24,6 +24,7 @@ describe('Form.useTranslation', () => { const nb = {} extendDeep(nb, forms_nbNO['nb-NO'], global_nbNO['nb-NO']) nb['formatMessage'] = expect.any(Function) + nb['renderMessage'] = expect.any(Function) expect(result.current).toEqual(nb) }) @@ -38,6 +39,7 @@ describe('Form.useTranslation', () => { const gb = {} extendDeep(gb, forms_enGB['en-GB'], global_enGB['en-GB']) gb['formatMessage'] = expect.any(Function) + gb['renderMessage'] = expect.any(Function) expect(resultGB.current).toEqual(gb) @@ -50,6 +52,7 @@ describe('Form.useTranslation', () => { const nb = {} extendDeep(nb, forms_nbNO['nb-NO'], global_nbNO['nb-NO']) nb['formatMessage'] = expect.any(Function) + nb['renderMessage'] = expect.any(Function) expect(resultNO.current).toEqual(nb) }) diff --git a/packages/dnb-eufemia/src/shared/__tests__/useTranslation.test.tsx b/packages/dnb-eufemia/src/shared/__tests__/useTranslation.test.tsx index a38c062a59a..8baf23036a1 100644 --- a/packages/dnb-eufemia/src/shared/__tests__/useTranslation.test.tsx +++ b/packages/dnb-eufemia/src/shared/__tests__/useTranslation.test.tsx @@ -19,6 +19,7 @@ describe('useTranslation without an ID', () => { expect(result.current).toEqual( Object.assign({}, nbNO[defaultLocale], { formatMessage: expect.any(Function), + renderMessage: expect.any(Function), }) ) }) @@ -33,6 +34,7 @@ describe('useTranslation without an ID', () => { expect(resultGB.current).toEqual( Object.assign({}, enGB['en-GB'], { formatMessage: expect.any(Function), + renderMessage: expect.any(Function), }) ) @@ -45,6 +47,7 @@ describe('useTranslation without an ID', () => { expect(resultNO.current).toEqual( Object.assign({}, nbNO['nb-NO'], { formatMessage: expect.any(Function), + renderMessage: expect.any(Function), }) ) }) @@ -477,4 +480,40 @@ describe('useTranslation with an ID', () => { ) }) }) + + describe('renderMessage', () => { + it('should render with JSX line-breaks', () => { + const { result } = renderHook(() => useTranslation()) + + expect(result.current.renderMessage('Hello{br}World')).toEqual([ + + Hello +
+
, + + World +
+
, + ]) + }) + + it('should support multiple line-breaks', () => { + const { result } = renderHook(() => useTranslation()) + + expect(result.current.renderMessage('A{br}B{br}C')).toEqual([ + + A +
+
, + + B +
+
, + + C +
+
, + ]) + }) + }) }) diff --git a/packages/dnb-eufemia/src/shared/useTranslation.tsx b/packages/dnb-eufemia/src/shared/useTranslation.tsx index e3004009b30..da505b6dc68 100644 --- a/packages/dnb-eufemia/src/shared/useTranslation.tsx +++ b/packages/dnb-eufemia/src/shared/useTranslation.tsx @@ -1,4 +1,4 @@ -import { useContext, useMemo } from 'react' +import React, { Fragment, useContext, useMemo } from 'react' import Context, { Translation, TranslationLocale, @@ -45,6 +45,7 @@ export type CombineWithExternalTranslationsArgs = { } export type FormatMessage = { formatMessage: typeof formatMessage + renderMessage: typeof renderMessage } export type CombineWithExternalTranslationsReturn = Translation & TranslationCustomLocales & @@ -78,6 +79,9 @@ export function combineWithExternalTranslations({ return formatMessage(id, args, combined) } + // Support line-breaks + combined.renderMessage = renderMessage + return combined } @@ -114,11 +118,36 @@ export function formatMessage( str = id(messages) } - if (str) { + if (typeof str === 'string') { for (const t in args) { str = str.replace(new RegExp(`{${t}}`, 'g'), args[t]) } + + if (str.includes('{br}')) { + return renderMessage(str) + } + } + + return str ?? id +} + +export function renderMessage( + text: string | Array +): string | React.ReactNode { + let element = text + + if (typeof text === 'string') { + element = text.split('{br}') + } + + if (Array.isArray(element)) { + return element.map((item, index) => ( + + {item} +
+
+ )) } - return str + return text }