Update April 2022
Since October '21, i18next has added support for formatting using native Intl API in version 21.3. You should probably check that out first, but I think this article can still be useful to you as it explains how to work with date-fns.
Intro
In this article I will show you how to translate your React app in multiple languages and how to automatically format dates in the user's locale.
Rendering a date using a localized format is important: for example, the US uses MM/DD/YYYY
, while some other countries use DD/MM/YYYY
.
We will be using React together with i18next and date-fns.
The naive solution would be to handle both concepts of translation and date formatting separately:
render() {
return <span>
{t('article.postedOn')}
{format(article.date, 'MM/DD/YYYY')}
</span>;
}
The end result of this article is that we can pass a Date
object to our translation function and easily declare which date format we want to use:
// In our React component:
render() {
return <span>
{ t('article.postedOn', {date: new Date()}) }
</span>;
}
// In our translation bundle:
{ "article":
{ "postedOn": "This article was posted on {{ date, short }}" }
}
And the user will see a message like: This article was posted on 12/19/2020
.
React i18next
i18next is a popular solution to manage translations in your app. I won't go into detail how to configure it for usage with React, I used this guide as a setup for this article.
Using i18next in your app
The basic idea is that you can translate strings using a t()
function. You pass in a translation key and i18next will look up the translation in its bundle for the currently active locale:
import { useTranslation } from "react-i18next";
const MyComponent = () => {
const { t } = useTranslation();
return <span>{ t('messages.welcome') }</span>;
};
Translation bundles
For each language that you support, you create a translation bundle as JSON and pass it to i18next:
{
"en": {
"translation": {
"messages": {
"welcome": "Welcome!"
}
}
},
"nl": {
"translation": {
"messages": {
"welcome": "Welkom!"
}
}
}
}
Setup i18next
Install the library:
npm install react-i18next i18next
Create a new module i18next.js
where you configure the library:
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
// Here we import the bundle file as defined above
import resources from "./translation.json";
i18n.use(initReactI18next) // passes i18n down to react-i18next
.init({
resources,
lng: "en",
interpolation: {
// react already saves from xss
escapeValue: false
}
});
export default i18n;
And simply import this file in your app.
Interpolation
It is common that you need to use a name or a date in your translated text. The position of the dynamic value in the translated string can vary between languages, so we use a template string with curly braces and pass the variable to the t()
function:
t('welcome', {name: 'John'});
// translation bundle:
{ "welcome": "Welcome {{ name }}" }
This is called interpolation in i18next.
Adding date-fns
Date-fns is a modular library for working with dates in JS and a popular alternative to the monolithic MomentJS. To install:
npm install date-fns
Automatically format dates with i18next
In the i18next.js
file, we need to import some stuff from date-fns:
import { format as formatDate, isDate } from "date-fns";
import { en, nl } from "date-fns/locale"; // import all locales we need
const locales = { en, nl }; // used to look up the required locale
Then add the following configuration to i18next:
interpolation: {
format: (value, format, lng) => {
if (isDate(value)) {
const locale = locales[lng];
return formatDate(value, format, { locale });
}
}
}
We simply check if the dynamic value is a date and then let date-fns format it. As the third options
parameter of format()
, we tell date-fns which locale object to use.
If we now pass a Date
object in the options of our t()
function, it will automatically be formatted. We can set the format inside the curly braces in the translation bundle:
{ "postedOn": "Posted on {{ date, MM/DD/YYYY }}"}
As I explained above, not every language uses the same date format. Luckily date-fns provides locale aware date formats:
So instead of MM/DD/YYYY
we should use P
. Don't forget to pass in the locale
option to the format function.
To make our date formats easy to work with, we can predefine some formatters that we would like to use in our app:
format: (value, format, lng) => {
if (isDate(value)) {
const locale = locales[lng];
if (format === "short")
return formatDate(value, "P", { locale });
if (format === "long")
return formatDate(value, "PPPP", { locale });
if (format === "relative")
return formatRelative(value, new Date(), { locale });
if (format === "ago")
return formatDistance(value, new Date(), {
locale,
addSuffix: true
});
return formatDate(value, format, { locale });
}
return value;
}
Here we use powerful date-fns functions such as formatDistance and formatRelative to create a human readable representation of a date in the past.
And now we can simply choose from a set of formatters in our translation bundle:
{ "postedOn": "Posted on {{ date, short }}"}
import { useTranslation } from "react-i18next";
const { t } = useTranslation();
// 'Posted on 11/10/2021'
t('postedOn', { date: new Date() });
Top comments (2)
Nice!
fyi: here I used luxon: dev.to/adrai/how-to-properly-inter...
Thank you for saving my time. Great piece!