Skip to content

Commit 9a7b17e

Browse files
authored
feat: Deprecate defaultTranslationValues (#1411)
`defaultTranslationValues` allow to share global values to be used in messages across your app. The most common case are shared rich text elements (e.g. `b: (chunks) => <b>{chunks}</b>`). However, over time this feature has shown drawbacks: 1. We can't serialize them automatically across the RSC boundary (see #611) 2. They get in the way of type-safe arguments (see #410) Due to this, the feature will be deprecated and the docs will suggest a better alternative for common tags in rich text that doesn't have the limitations mentioned above ([updated docs](https://next-intl-docs-git-feat-deprecate-defaulttrans-f52ebe-next-intl.vercel.app/docs/usage/messages#rich-text-reuse-tags)). Shared values don't get a direct replacement from `next-intl`, but should be handled as part of your app logic (e.g. a shared module, React Context, etc.). **Note**: #410 can not be implemented immediately as part of this, as long as `defaultTranslationValues` are still available (even if deprecated). Instead, this feature could be added as part of the next major release. Contributes to #611
1 parent 5502984 commit 9a7b17e

File tree

12 files changed

+89
-36
lines changed

12 files changed

+89
-36
lines changed

docs/pages/docs/usage/configuration.mdx

+6-1
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,12 @@ function Component() {
541541
}
542542
```
543543

544-
## Default translation values
544+
## Default translation values (deprecated) [#default-translation-values]
545+
546+
<Callout>
547+
This feature is deprecated and will be removed in the next major version of `next-intl` ([alternative](/docs/usage/messages#rich-text-reuse-tags)).
548+
549+
</Callout>
545550

546551
To achieve consistent usage of translation values and reduce redundancy, you can define a set of global default values. This configuration can also be used to apply consistent styling of commonly used rich text elements.
547552

docs/pages/docs/usage/messages.mdx

+40-3
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ t('message'); // "Escape curly braces with single quotes (e.g. {name})"
333333

334334
## Rich text
335335

336-
You can format rich text with custom tags and map them to React components:
336+
You can format rich text with custom tags and map them to React components via `t.rich`:
337337

338338
```json filename="en.json"
339339
{
@@ -351,9 +351,46 @@ t.rich('message', {
351351
Tags can be arbitrarily nested (e.g. `This is <important><very>very</very> important</important>`).
352352

353353
<Details id="rich-text-reuse-tags">
354-
<summary>How can I reuse a tag across my app?</summary>
354+
<summary>How can I reuse tags across my app?</summary>
355355

356-
If you want to use the same tag across your app, you can configure it via [default translation values](/docs/usage/configuration#default-translation-values).
356+
Common tags for rich text that you want to share across your app can be defined in a shared module and imported where relevant for usage with `t.rich`.
357+
358+
A convenient pattern is to use a component that provides common tags via a render prop:
359+
360+
```js
361+
import {useTranslations} from 'next-intl';
362+
import RichText from '@/components/RichText';
363+
364+
function AboutPage() {
365+
const t = useTranslations('AboutPage');
366+
return <RichText>{(tags) => t.rich('description', tags)}</RichText>;
367+
}
368+
```
369+
370+
In this case, the `RichText` component can provide styled tags and also a general layout for the text:
371+
372+
```js filename="components/RichText.tsx"
373+
import {ReactNode} from 'react';
374+
375+
// These tags are available
376+
type Tag = 'p' | 'b' | 'i';
377+
378+
type Props = {
379+
children(tags: Record<Tag, (chunks: ReactNode) => ReactNode>): ReactNode
380+
};
381+
382+
export default function RichText({children}: Props) {
383+
return (
384+
<div className="prose">
385+
{children({
386+
p: (chunks: ReactNode) => <p>{chunks}</p>,
387+
b: (chunks: ReactNode) => <b className="font-semibold">{chunks}</b>,
388+
i: (chunks: ReactNode) => <i className="italic">{chunks}</i>
389+
})}
390+
</div>
391+
);
392+
}
393+
```
357394

358395
</Details>
359396

examples/example-app-router-playground/messages/de.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
},
55
"AsyncComponent": {
66
"basic": "AsyncComponent",
7-
"markup": "Markup with <b>{globalString}</b>",
7+
"markup": "Markup with <important>bold content</important>",
88
"rich": "This is a <important>rich</important> text."
99
},
1010
"Client": {
@@ -21,8 +21,7 @@
2121
},
2222
"Index": {
2323
"description": "Das ist die Startseite.",
24-
"globalDefaults": "<highlight>{globalString}</highlight>",
25-
"rich": "Das ist <important>formatierter</important> Test.",
24+
"rich": "Das ist <b>formatierter</b> Test.",
2625
"title": "Start"
2726
},
2827
"JustIn": {

examples/example-app-router-playground/messages/en.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
},
55
"AsyncComponent": {
66
"basic": "AsyncComponent",
7-
"markup": "Markup with <b>{globalString}</b>",
7+
"markup": "Markup with <important>bold content</important>",
88
"rich": "This is a <important>rich</important> text."
99
},
1010
"Client": {
@@ -21,8 +21,7 @@
2121
},
2222
"Index": {
2323
"description": "This is the home page.",
24-
"globalDefaults": "<highlight>{globalString}</highlight>",
25-
"rich": "This is a <important>rich</important> text.",
24+
"rich": "This is a <b>rich</b> text.",
2625
"title": "Home"
2726
},
2827
"JustIn": {

examples/example-app-router-playground/messages/es.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
},
55
"AsyncComponent": {
66
"basic": "AsyncComponent",
7-
"markup": "Markup with <b>{globalString}</b>",
7+
"markup": "Markup with <important>bold content</important>",
88
"rich": "This is a <important>rich</important> text."
99
},
1010
"Client": {
@@ -21,8 +21,7 @@
2121
},
2222
"Index": {
2323
"description": "Esta es la página de inicio.",
24-
"globalDefaults": "<highlight>{globalString}</highlight>",
25-
"rich": "Este es un texto <important>importante</important>.",
24+
"rich": "Este es un texto <b>importante</b>.",
2625
"title": "Inicio"
2726
},
2827
"JustIn": {

examples/example-app-router-playground/messages/ja.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@
2121
},
2222
"Index": {
2323
"description": "This is the home page (ja).",
24-
"globalDefaults": "<highlight>{globalString}</highlight> (ja)",
25-
"rich": "This is a <important>rich</important> text (ja).",
24+
"rich": "This is a <b>rich</b> text (ja).",
2625
"title": "Home (ja)"
2726
},
2827
"JustIn": {

examples/example-app-router-playground/src/app/[locale]/page.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import PageLayout from '../../components/PageLayout';
1212
import MessagesAsPropsCounter from '../../components/client/01-MessagesAsPropsCounter';
1313
import MessagesOnClientCounter from '../../components/client/02-MessagesOnClientCounter';
1414
import DropdownMenu from '@/components/DropdownMenu';
15+
import RichText from '@/components/RichText';
1516
import {Link} from '@/i18n/routing';
1617

1718
type Props = {
@@ -27,14 +28,13 @@ export default function Index({searchParams}: Props) {
2728
return (
2829
<PageLayout title={t('title')}>
2930
<p>{t('description')}</p>
30-
<p data-testid="RichText">
31-
{t.rich('rich', {important: (chunks) => <b>{chunks}</b>})}
32-
</p>
31+
<RichText data-testid="RichText">
32+
{(tags) => t.rich('rich', tags)}
33+
</RichText>
3334
<p
3435
dangerouslySetInnerHTML={{__html: t.raw('rich')}}
3536
data-testid="RawText"
3637
/>
37-
<p data-testid="GlobalDefaults">{t.rich('globalDefaults')}</p>
3838
{/* @ts-expect-error Purposefully trigger an error */}
3939
<p data-testid="MissingMessage">{t('missing')}</p>
4040
<p data-testid="CurrentTime">

examples/example-app-router-playground/src/components/AsyncComponent.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ export default async function AsyncComponent() {
77
<div data-testid="AsyncComponent">
88
<p>{t('basic')}</p>
99
<p>{t.rich('rich', {important: (chunks) => <b>{chunks}</b>})}</p>
10-
<p>{t.markup('markup', {b: (chunks) => `<b>${chunks}</b>`})}</p>
10+
<p>
11+
{t.markup('markup', {
12+
important: (chunks) => `<b>${chunks}</b>`
13+
})}
14+
</p>
1115
<p>{String(t.has('basic'))}</p>
1216
</div>
1317
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {ComponentProps, ReactNode} from 'react';
2+
3+
type Tag = 'b' | 'i';
4+
5+
type Props = {
6+
children(tags: Record<Tag, (chunks: ReactNode) => ReactNode>): ReactNode;
7+
} & Omit<ComponentProps<'p'>, 'children'>;
8+
9+
export default function RichText({children, ...rest}: Props) {
10+
return (
11+
<p {...rest}>
12+
{children({
13+
b: (chunks: ReactNode) => <b style={{fontWeight: 'bold'}}>{chunks}</b>,
14+
i: (chunks: ReactNode) => <i style={{fontStyle: 'italic'}}>{chunks}</i>
15+
})}
16+
</p>
17+
);
18+
}

examples/example-app-router-playground/src/i18n/request.tsx

-4
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@ export default getRequestConfig(async ({requestLocale}) => {
4545
now: now ? new Date(now) : undefined,
4646
timeZone,
4747
messages,
48-
defaultTranslationValues: {
49-
globalString: 'Global string',
50-
highlight: (chunks) => <strong>{chunks}</strong>
51-
},
5248
formats,
5349
onError(error) {
5450
if (

examples/example-app-router-playground/tests/main.spec.ts

+5-11
Original file line numberDiff line numberDiff line change
@@ -215,21 +215,15 @@ it('can use next-intl on the client side', async ({page}) => {
215215
it('can use rich text', async ({page}) => {
216216
await page.goto('/en');
217217
const element = page.getByTestId('RichText');
218-
expect(await element.innerHTML()).toBe('This is a <b>rich</b> text.');
219-
});
220-
221-
it('can use raw text', async ({page}) => {
222-
await page.goto('/en');
223-
const element = page.getByTestId('RawText');
224218
expect(await element.innerHTML()).toBe(
225-
'This is a <important>rich</important> text.'
219+
'This is a <b style="font-weight:bold">rich</b> text.'
226220
);
227221
});
228222

229-
it('can use global defaults', async ({page}) => {
223+
it('can use raw text', async ({page}) => {
230224
await page.goto('/en');
231-
const element = page.getByTestId('GlobalDefaults');
232-
expect(await element.innerHTML()).toBe('<strong>Global string</strong>');
225+
const element = page.getByTestId('RawText');
226+
expect(await element.innerHTML()).toBe('This is a <b>rich</b> text.');
233227
});
234228

235229
it('can use `getMessageFallback`', async ({page}) => {
@@ -642,7 +636,7 @@ it('can use async APIs in async components', async ({page}) => {
642636
const element1 = page.getByTestId('AsyncComponent');
643637
element1.getByText('AsyncComponent');
644638
expect(await element1.innerHTML()).toContain('This is a <b>rich</b> text.');
645-
element1.getByText('Markup with <b>Global string</b>');
639+
element1.getByText('Markup with <b>bold content</b>');
646640

647641
page
648642
.getByTestId('AsyncComponentWithoutNamespace')

packages/use-intl/src/core/IntlConfig.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ type IntlConfig<Messages = AbstractIntlMessages> = {
4343
messages?: Messages;
4444
/** Global default values for translation values and rich text elements.
4545
* Can be used for consistent usage or styling of rich text elements.
46-
* Defaults will be overidden by locally provided values. */
46+
* Defaults will be overidden by locally provided values.
47+
*
48+
* @deprecated See https://next-intl-docs.vercel.app/docs/usage/messages#rich-text-reuse-tags
49+
* */
4750
defaultTranslationValues?: RichTranslationValues;
4851
};
4952

0 commit comments

Comments
 (0)