Messages
In order to render messages in components, the messages have to be provided as JSON data to the provider.
Structuring messages
The recommended approach is to group messages by components and embrace them as the primary unit of code organization in your app.
{
"About": {
"title": "About us"
}
}
import {useTranslations} from 'next-intl';
function About() {
const t = useTranslations('About');
return <h1>{t('title')}</h1>;
}
You can provide more structure by nesting messages:
{
"auth": {
"SignUp": {
"title": "Sign up",
"form": {
"placeholder": "Please enter your name",
"submit": "Submit"
}
}
}
}
import {useTranslations} from 'next-intl';
function SignUp() {
// Provide the lowest common denominator that contains
// all messages this component needs to consume.
const t = useTranslations('auth.SignUp');
return (
<>
<h1>{t('title')}</h1>
<form>
<input
// The remaining hierarchy can be resolved by
// using a dot to access nested messages.
placeholder={t('form.placeholder')}
/>
<button type="submit">{t('form.submit')}</button>
</form>
</>
);
}
To retrieve all available messages in a component, you can omit the namespace path:
const t = useTranslations();
t('auth.SignUp.title');
How can I reuse messages?
As your app grows, you'll want to reuse messages among your components. If you use component names as namespaces to structure your messages, you'll automatically benefit from reusable messages by reusing your components.
Examples:
- You commonly need a dialog that displays a "confirm" and "cancel" button. In this case, consider adding a
ConfirmDialog
component to reuse the messages along with the functionality. - You're displaying products in your app and often need to resolve a category identifier to a human readable label (e.g.
new
→ "Just in"). To ensure consistency, you can add aProductCategory
component that turns thecategory
into a string.
There might be cases where you want to use the same message in different components, but there's no reasonable opportunity for sharing the message via a component. This might be symptom that these components should use separate messages, even if they happen to be the same for the time being. By using separate labels, you gain the flexibility to use more specific labels (e.g. "not now" instead of "cancel").
Providing messages
You can provide page-specific messages via data fetching methods of Next.js (opens in a new tab) for individual pages:
export async function getStaticProps({locale}) {
return {
props: {
// You can get the messages from anywhere you like. The recommended
// pattern is to put them in JSON files separated by language and read
// the desired one based on the `locale` received from Next.js.
messages: (await import(`../../messages/${locale}.json`)).default
}
};
}
If you want to provide only the minimum amount of messages per page, you can filter your messages accordingly:
import pick from 'lodash/pick';
const namespaces = ['Index'];
export async function getStaticProps({locale}) {
return {
props: {
messages: pick(
(await import(`../../messages/${locale}.json`)).default,
namespaces
)
}
};
}
Note that the namespaces
can be a list that you generate dynamically based
on used components. See the advanced
example (opens in a new tab).
Rendering messages
next-intl
uses ICU message syntax that allows you to express language subtleties and separates state handling within messages from your app code.
You can interpolate values from your components into your messages with a special placeholder syntax.
{
"static": "Hello world!",
"interpolation": "Hello {name}!",
"plural": "You have {numMessages, plural, =0 {no messages} =1 {one message} other {# messages}}.",
"select": "{gender, select, female {She} male {He} other {They}} is online.",
"selectordinal": "It's your {year, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} birthday!"
}
t('static');
t('interpolation', {name: 'Jane'});
t('plural', {numMessages: 3});
t('select', {gender: 'female'});
t('selectordinal', {year: 11});
To work with ICU messages, it can be helpful to use an editor that supports this syntax. E.g. the Crowdin Editor (opens in a new tab) can be used by translators to work on translations without having to change app code.
Rich text
You can format rich text with custom tags and map them to React components.
{
"richText": "This is <important><very>very</very> important</important>"
}
t.rich('richText', {
important: (chunks) => <b>{chunks}</b>,
very: (chunks) => <i>{chunks}</i>
});
If you want to use the same tag multiple times, you can configure it via the default translation values.
Raw messages
Messages are always parsed and therefore e.g. for rich text you need to supply the necessary tags. If you want to avoid the parsing, e.g. because you have raw HTML stored in a message, there's a separate API for this:
{
"content": "<h1>Headline<h1><p>This is raw HTML</p>"
}
<div dangerouslySetInnerHTML={{__html: t.raw('content')}} />
Important: You should always sanitize the content that you pass to
dangerouslySetInnerHTML
to avoid cross-site scripting attacks.
The value of a raw message can be any valid JSON value: strings, booleans, objects and arrays.
Arrays of messages
If you need to render a list of messages, the recommended approach is to map an array of keys to the corresponding messages.
{
"Features": {
"trust": "Built by experts",
"customizable": "Easy to customize",
"fast": "Blazingly fast"
}
}
import {useTranslations} from 'next-intl';
function Features() {
const t = useTranslations('Features');
return (
<ul>
{['trust', 'customizable', 'fast'].map((key) => (
<li key={key}>{t(key)}</li>
))}
</ul>
);
}
If the number of items varies between locales, you can solve this by using rich text.
{
"Features": {
"items": "<item>Built by experts</item><item>Easy to customize</item><item>Blazingly fast</item>"
}
}
import {useTranslations} from 'next-intl';
function Features() {
const t = useTranslations('Features');
return (
<ul>
{t.rich('items', {
item: (chunks) => <li>{chunks}</li>
})}
</ul>
);
}