DEV Community

Cover image for React: automatic date formatting in translations (i18next + date-fns)
Edwin
Edwin

Posted on • Updated on

React: automatic date formatting in translations (i18next + date-fns)

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>;
}
Enter fullscreen mode Exit fullscreen mode

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 }}" }
}
Enter fullscreen mode Exit fullscreen mode

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>;
};
Enter fullscreen mode Exit fullscreen mode

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!"
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Setup i18next

Install the library:

npm install react-i18next i18next
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 }}" }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then add the following configuration to i18next:

interpolation: {
    format: (value, format, lng) => {
        if (isDate(value)) {
            const locale = locales[lng];
            return formatDate(value, format, { locale });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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 }}"}
Enter fullscreen mode Exit fullscreen mode

As I explained above, not every language uses the same date format. Luckily date-fns provides locale aware date formats:

Alt Text

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;
}
Enter fullscreen mode Exit fullscreen mode

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 }}"}
Enter fullscreen mode Exit fullscreen mode
import { useTranslation } from "react-i18next";
const { t } = useTranslation();

// 'Posted on 11/10/2021'
t('postedOn', { date: new Date() });
Enter fullscreen mode Exit fullscreen mode

Discussion (2)

Collapse
adrai profile image
Adriano Raiano

Nice!

fyi: here I used luxon: dev.to/adrai/how-to-properly-inter...

Collapse
abidrahim profile image
Abid Rahim

Thank you for saving my time. Great piece!