React: automatic date formatting in translations (i18next + date-fns)
Tags: react, i18next, datefns, translation
Published: 2020-12-19 17:16:46

# Update April 2022

Since October '21, i18next has [added support for formatting using native Intl API](https://www.i18next.com/translation-function/formatting) in [version 21.3](https://github.com/i18next/i18next/releases/tag/v21.3.0). 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](https://www.i18next.com/) and [date-fns](https://date-fns.org/).

{% codesandbox v27h6 %}

The naive solution would be to handle both concepts of translation and date formatting separately:

```jsx
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:

```js
// 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](https://www.i18next.com/) 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](https://react.i18next.com/guides/quick-start) 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:

```js
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:

```json
{
	"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:

```js
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:

```js
t('welcome', {name: 'John'});

// translation bundle:
{ "welcome": "Welcome {{ name }}" }
```

This is called [interpolation](https://www.i18next.com/translation-function/interpolation) in i18next.

# Adding date-fns

[Date-fns](https://date-fns.org) is a modular library for working with dates in JS and a popular alternative to the monolithic [MomentJS](https://momentjs.com/). 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:
```js
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:

```js
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:
```json
{ "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:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/kpqvh7bwej3vtjb1z2ju.png)

So instead of `MM/DD/YYYY` we should use `P`. Don't forget to pass in the `locale` option to the [format function](https://date-fns.org/v2.16.1/docs/format#arguments).

To make our date formats easy to work with, we can predefine some formatters that we would like to use in our app:

```js
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](https://date-fns.org/v2.16.1/docs/formatDistance) and [formatRelative](https://date-fns.org/v2.16.1/docs/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:

```json
{ "postedOn": "Posted on {{ date, short }}"}
```

```js
import { useTranslation } from "react-i18next";
const { t } = useTranslation();

// 'Posted on 11/10/2021'
t('postedOn', { date: new Date() });
```