Skip to content

Commit 493597d

Browse files
authored
docs: Improve guide for message splitting (#62)
* docs: Improve guide for splitting of messages * Typo * Wording
1 parent 567f9d5 commit 493597d

File tree

17 files changed

+164
-114
lines changed

17 files changed

+164
-114
lines changed

docs/installation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ export default function App({Component, pageProps}) {
1818
3. Provide messages on a page-level.
1919
```js
2020
// pages/index.js
21-
export function getStaticProps({locale}: GetStaticPropsContext) {
21+
export async function getStaticProps({locale}: GetStaticPropsContext) {
2222
return {
2323
props: {
2424
// You can get the messages from anywhere you like, but the recommended
2525
// pattern is to put them in JSON files separated by language and read
2626
// the desired one based on the `locale` received from Next.js.
27-
messages: require(`../../messages/index/${locale}.json`),
27+
messages: await import(`../../messages/index/${locale}.json`)
2828
}
2929
};
3030
}

docs/usage.md

Lines changed: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ function SignUp() {
6565
}
6666
```
6767

68-
You don't have to group messages by components – use whatever suits your use case. You can theoretically use a common key for shared labels that are used frequently – however, from my experience I think it's often beneficial to duplicate labels across components, even if they are the same in one language. Depending on the context, a different label can be more appropriate (e.g. "not now" instead of "cancel"). Duplicating the labels allows to easily change them later on in case you want something more specific. Duplication on the network level is typically solved by gzip. In addition to this, you can achieve reuse by using shared components.
68+
You don't have to group messages by components – use whatever suits your use case. You can theoretically use a common key for shared labels that are used frequently – however, based on experience it's often beneficial to duplicate labels across components, even if they are the same in one language. Depending on the context, a different label can be more appropriate (e.g. "not now" instead of "cancel"). Duplicating the labels allows to easily change them later on in case you want something more specific. Duplication on the network level is typically solved by gzip. In addition to this, you can achieve reuse by using shared components.
6969

7070
To retrieve all available messages in a component, you can omit the namespace path:
7171

@@ -75,62 +75,40 @@ const t = useTranslations();
7575

7676
## Providing messages
7777

78-
You can provide page-specific messages via `getStaticProps` of individual pages:
78+
You can provide page-specific messages via [data fetching methods of Next.js](https://nextjs.org/docs/basic-features/data-fetching) for individual pages:
7979

8080
```js
8181
// pages/index.js
82-
export function getStaticProps({locale}) {
82+
export async function getStaticProps({locale}) {
8383
return {
8484
props: {
8585
// You can get the messages from anywhere you like, but the recommended
8686
// pattern is to put them in JSON files separated by language and read
8787
// the desired one based on the `locale` received from Next.js.
88-
messages: require(`../../messages/index/${locale}.json`),
88+
messages: await import(`../../messages/index/${locale}.json`)
8989
}
9090
};
9191
}
9292
```
9393

94-
If you have a set of common messages that should be available on every page, you can either merge them into the page-level messages:
94+
If you want to provide only the minimum amount of messages per page, you can filter your messages accordingly:
9595

9696
```js
9797
// pages/index.js
98-
export function getStaticProps({locale}) {
98+
import pick from 'lodash/pick';
99+
100+
const namespaces = ['Index'];
101+
102+
export async function getStaticProps({locale}) {
99103
return {
100104
props: {
101-
messages: {
102-
...require(`../../messages/shared/${locale}.json`),
103-
...require(`../../messages/index/${locale}.json`)
104-
}
105+
messages: pick(await import(`../../messages/index/${locale}.json`), namespaces)
105106
}
106107
};
107108
}
108109
```
109110

110-
… or alternatively set them up in `_app.js` and apply the merging there:
111-
112-
```js
113-
// pages/_app.js
114-
import NextApp from 'next/app';
115-
116-
export default function App({Component, messages, pageProps}) {
117-
return (
118-
<NextIntlProvider messages={{...messages, ...pageProps.messages}}>
119-
<Component {...pageProps} />
120-
</NextIntlProvider>
121-
);
122-
}
123-
124-
App.getInitialProps = async function getInitialProps(context: AppContext) {
125-
const {locale} = context.router;
126-
return {
127-
...(await NextApp.getInitialProps(context)),
128-
messages: require(`../../messages/${locale}.json`)
129-
};
130-
};
131-
```
132-
133-
Note that in this case you [opt-out of automatic static optimization](https://github.com/vercel/next.js/blob/master/errors/opt-out-auto-static-optimization.md#opt-out-of-automatic-static-optimization). However, pages that use `getStaticProps` are still statically optimized (even if `getStaticProps` is essentially a no-op – only the presence matters).
111+
Note that the `namespaces` can be a list that you generate dynamically based on used components. See the [example](../packages/example/src/pages/index.tsx).
134112

135113
## Rendering messages
136114

@@ -322,6 +300,8 @@ To avoid mismatches between the server and client environment, it is recommended
322300

323301
This value will be used as the default for the `formatRelativeTime` function as well as for the initial render of `useNow`.
324302

303+
**Important:** When you use `getStaticProps` and no `updateInterval`, this value will be stale. Therefore either regenerate these pages regularly with `revalidate`, use `getServerSideProps` instead or configure an `updateInterval`.
304+
325305
For consistent results in end-to-end tests, it can be helpful to mock this value to a constant value, e.g. based on an environment parameter.
326306

327307
### Dates and times within messages
@@ -372,7 +352,7 @@ If possible, you should configure an explicit time zone as this affects the rend
372352
To avoid such markup mismatches, you can globally define a time zone like this:
373353

374354
```jsx
375-
<NextIntlProvider timeZone="Austria/Vienna">...<NextIntlProvider>
355+
<NextIntlProvider timeZone="Europe/Vienna">...<NextIntlProvider>
376356
```
377357

378358
This can either be static in your app, or alternatively read from the user profile if you store such a setting. The available time zone names can be looked up in [the tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).

packages/example/messages/about/de.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

packages/example/messages/about/en.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

packages/example/messages/de.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"About": {
3+
"title": "About",
4+
"description": "Diese Seite verwendet Übersetzungen aus <code>./messages/about/{locale}.json</code> und <code>./messages/shared/{locale}.json</code>. Übersetzungen von der Startseite und anderen Sprachen werden nicht geladen.",
5+
"lastUpdated": "Dieses Beispiel wurde {lastUpdatedRelative} aktualisiert ({lastUpdated, date, short})."
6+
},
7+
"Index": {
8+
"title": "Start",
9+
"description": "Diese Seite verwendet Übersetzungen aus <code>./messages/index/{locale}.json</code> und <code>./messages/shared/{locale}.json</code>. Übersetzungen von der Über-Seite und anderen Sprachen werden nicht geladen."
10+
},
11+
"Navigation": {
12+
"index": "Start",
13+
"about": "Über",
14+
"switchLocale": "Zu {locale, select, de {Deutsch} en {Englisch}} wechseln"
15+
},
16+
"PageLayout": {
17+
"pageTitle": "next-intl"
18+
}
19+
}

packages/example/messages/en.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"About": {
3+
"title": "About",
4+
"description": "This page uses messages from <code>./messages/about/{locale}.json</code> and <code>./messages/shared/{locale}.json</code>. Messages from the start page and other locales are not loaded.",
5+
"lastUpdated": "This example was updated {lastUpdatedRelative} ({lastUpdated, date, short})."
6+
},
7+
"Index": {
8+
"title": "Home",
9+
"description": "This page uses messages from <code>./messages/index/{locale}.json</code> and <code>./messages/shared/{locale}.json</code>. Messages from the about page and other locales are not loaded."
10+
},
11+
"Navigation": {
12+
"index": "Home",
13+
"about": "About",
14+
"switchLocale": "Switch to {locale, select, de {German} en {English}}"
15+
},
16+
"PageLayout": {
17+
"pageTitle": "next-intl"
18+
}
19+
}

packages/example/messages/index/de.json

Lines changed: 0 additions & 6 deletions
This file was deleted.

packages/example/messages/index/en.json

Lines changed: 0 additions & 6 deletions
This file was deleted.

packages/example/messages/shared/de.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

packages/example/messages/shared/en.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

packages/example/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@
1111
},
1212
"dependencies": {
1313
"date-fns": "^2.16.1",
14+
"lodash": "^4.17.21",
1415
"next": "^12.0.1",
1516
"next-intl": "^2.1.0",
1617
"react": "^17.0.0",
1718
"react-dom": "^17.0.0"
1819
},
1920
"devDependencies": {
21+
"@types/lodash": "4.14.176",
2022
"eslint": "7.4.0",
2123
"eslint-config-molindo": "5.0.1",
22-
"eslint-config-next": "^11.0.0"
24+
"eslint-config-next": "^12.0.0"
2325
}
2426
}

packages/example/src/components/Navigation.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ export default function Navigation() {
2424
</div>
2525
);
2626
}
27+
28+
Navigation.messages = ['Navigation'];
Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
1+
import {useTranslations} from 'next-intl';
2+
import Head from 'next/head';
13
import {ReactNode} from 'react';
24
import Navigation from './Navigation';
35

46
type Props = {
57
children: ReactNode;
6-
title: ReactNode;
8+
title: string;
79
};
810

911
export default function PageLayout({children, title}: Props) {
12+
const t = useTranslations('PageLayout');
13+
1014
return (
11-
<div
12-
style={{
13-
padding: 24,
14-
fontFamily: 'system-ui, sans-serif',
15-
lineHeight: 1.5
16-
}}
17-
>
18-
<Navigation />
19-
<div style={{maxWidth: 510}}>
20-
<h1>{title}</h1>
21-
{children}
15+
<>
16+
<Head>
17+
<title>{[title, t('pageTitle')].join(' - ')}</title>
18+
</Head>
19+
<div
20+
style={{
21+
padding: 24,
22+
fontFamily: 'system-ui, sans-serif',
23+
lineHeight: 1.5
24+
}}
25+
>
26+
<Navigation />
27+
<div style={{maxWidth: 510}}>
28+
<h1>{title}</h1>
29+
{children}
30+
</div>
2231
</div>
23-
</div>
32+
</>
2433
);
2534
}
35+
36+
PageLayout.messages = ['PageLayout', ...Navigation.messages];

packages/example/src/pages/_app.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default function App({Component, pageProps}: AppProps) {
2626
// Also an explicit time zone is helpful to ensure dates render the
2727
// same way on the client as on the server, which might be located
2828
// in a different time zone.
29-
timeZone="UTC"
29+
timeZone="Europe/Vienna"
3030
>
3131
<Component {...pageProps} />
3232
</NextIntlProvider>

packages/example/src/pages/about.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {parseISO} from 'date-fns';
2-
import {GetStaticPropsContext} from 'next';
2+
import {pick} from 'lodash';
3+
import {GetServerSidePropsContext} from 'next';
34
import {useIntl, useTranslations} from 'next-intl';
45
import {useRouter} from 'next/router';
56
import Code from '../components/Code';
@@ -9,7 +10,7 @@ export default function About() {
910
const t = useTranslations('About');
1011
const {locale} = useRouter();
1112
const intl = useIntl();
12-
const lastUpdated = parseISO('2021-01-26T17:04:45.567Z');
13+
const lastUpdated = parseISO('2021-10-28T10:04:45.567Z');
1314

1415
return (
1516
<PageLayout title={t('title')}>
@@ -29,13 +30,18 @@ export default function About() {
2930
);
3031
}
3132

32-
export function getStaticProps({locale}: GetStaticPropsContext) {
33+
About.messages = ['About', ...PageLayout.messages];
34+
35+
export async function getServerSideProps({locale}: GetServerSidePropsContext) {
3336
return {
3437
props: {
35-
messages: {
36-
...require(`../../messages/shared/${locale}.json`),
37-
...require(`../../messages/about/${locale}.json`)
38-
},
38+
messages: pick(
39+
await import(`../../messages/${locale}.json`),
40+
About.messages
41+
),
42+
// Note that when `now` is passed to the app, you need to make sure the
43+
// value is updated from time to time, so relative times are updated. See
44+
// https://github.com/amannn/next-intl/blob/main/docs/usage.md#formatrelativetime
3945
now: new Date().getTime()
4046
}
4147
};

packages/example/src/pages/index.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import pick from 'lodash/pick';
12
import {GetStaticPropsContext} from 'next';
23
import {useTranslations} from 'next-intl';
34
import {useRouter} from 'next/router';
@@ -20,13 +21,20 @@ export default function Index() {
2021
);
2122
}
2223

23-
export function getStaticProps({locale}: GetStaticPropsContext) {
24+
// The namespaces can be generated based on used components. `PageLayout` in
25+
// turn requires messages for `Navigation` and therefore a recursive list of
26+
// namespaces is created dynamically, where the owner of a component doesn't
27+
// have to know which nested components are rendered. Note that this approach
28+
// is limited to components which are not lazy loaded.
29+
Index.messages = ['Index', ...PageLayout.messages];
30+
31+
export async function getStaticProps({locale}: GetStaticPropsContext) {
2432
return {
2533
props: {
26-
messages: {
27-
...require(`../../messages/shared/${locale}.json`),
28-
...require(`../../messages/index/${locale}.json`)
29-
}
34+
messages: pick(
35+
await import(`../../messages/${locale}.json`),
36+
Index.messages
37+
)
3038
}
3139
};
3240
}

0 commit comments

Comments
 (0)