Introduction
In this blog post, we will explore the process of integrating a basic translation functionality into your React application. However, it's important to clarify that this approach falls short of the capabilities offered by third-party plugins like react-i18next. If your requirements are confined to the following items, then the insights shared here can be quite valuable.
- Basic translation support
- Willing to craft your custom translation solution
- Simply interested to know how these translation libraries work under the hood
When I examine the source code of well-known third-party libraries, I often find them intricate and convoluted. Understanding them proves challenging, almost as if they're composed in a different programming language altogether. The complexity far surpasses what I would have done to address a similar issue. so, here is my simple take on solving it.
Key features of this library
Dynamic values
- To support dynamic values inside the translated content, provide the dynamic values object as the second argument to the
t
helper as shown below.
Examples:
{ "greeting": "Hello, ${name}" }
import { useTranslation } from './translations' export const App = () => { const { t } = useTranslation() return <h1>{t('greeting', { name: 'Vinodh' })}</h1> }
Default translation fallback
- If the translation key is missing in the selected locale JSON then the value from the default locale is returned as a fallback.
Examples:
en.json
{ "greeting": "Hello, ${name}", "learnMore": "Click on the Vite and React logos to learn more" }
fr.json
{ "greeting": "Bonjour, ${name}" }
Component
import { useTranslation } from './translations' export const App = () => { const { t } = useTranslation() return ( <> <h1>{t('greeting', { name: 'Vinodh' })}</h1> <p>{t('learnMore')}</p> </> ) }
Result
- The compiled HTML for the French translation will be something like this.
<h1>Bonjour, Vinodh</h1> <p>Click on the Vite and React logos to learn more</p>
As the 'learnMore' key is not present in the fr.json
, the corresponding default locale en.json
value is returned.
Pluralization
- Pluralization is supported using a special property called
count
. - To make it work, you need to provide both zero, one, and many values to your JSON files.
Examples:
{ "items": { "zero": "Your cart is empty", "one": "You have one item in your cart", "many": "You have {count} items in your cart" } }
import { useState } from 'react' import { useTranslation } from './translations' export const Cart = () => { const { t } = useTranslation() const [itemsCount, setItemsCount] = useState(0) return ( <> <h1>{t('items', { count: itemsCount })}</h1> <button onClick={() => setItemsCount((count) => count + 1)}>Add item</button> </> ) }
HTML support:
- Supports HTML sanitization by default.
- To support HTML tags, add them in the translation params alone and explicitly set the "escapeValue" translation param to true.
- HTML tags inside the translations JSON are not supported as it will make the JSON vulnerable to security attacks.
Examples:
{ "greeting": "Hello, ${name}. Your role is ${role}" }
- Using the
t
helper
import { useTranslation } from './translations' export const App = () => { const { t } = useTranslation() const role = 'Admin' return ( <> {t( 'greeting', { name: '<span className="font-medium">Vinodh</span>', role: `<span className="text-gray">${role}</span>`, }, { escapeValue: false, } )} </> ) }
- To simplify this usage, the library has a custom component that escapes the param values by default internally.
<RenderTranslationWithHtml tKey={'greeting'} params={{ name: '<span className="ca-font-medium">Vinodh</span>', role: `<span className="ca-font-medium">${role}</span>` }} />,
Now that we know what we are going to build, let's start with our app setup.
App setup
Let's quickly create a react app using Vite bundler.
pnpm create vite i18n-app --template react-ts cd i18n-app pnpm install && pnpm update pnpm run dev
Your app should be available at http://localhost:5173/
.
PS: If you want to create a react app with complete bells and whistles then check out this Create a react app using Vite](https://dev.to/vinomanick/modern-way-to-create-a-react-app-using-vite-part-1-32ol) blog.
Translation files
- Create a
locales
folder that is going to have all our translation JSONs and create the translation JSONs for 'English' and 'French' language.
mkdir locales touch locales/en.json touch locales/fr.json
- Update the
en.json
with the following content.
{ "app": { "heading": "Translation supported app" } }
- Update the
fr.json
with the following content.
{ "app": { "heading": "Application prise en charge par la traduction" } }
- Create an
index.ts
file that is going to export a translation map that contains the supported locales and its corresponding dynamic import loader.
touch locales/index.ts
/* eslint-disable @typescript-eslint/no-explicit-any */ export const TRANSLATIONS_MAPPING: Record<string, () => Promise<any>> = { en: () => import('./en.json'), fr: () => import('./fr.json'), }
Translations Library
- Create a
translations
folder inside the src to keep all our translation-related code.
mkdir src/translations
- Create an interface file to store all our common interfaces.
mkdir src/translations/translations.interfaces.ts
/* eslint-disable @typescript-eslint/no-explicit-any */ export interface TranslationsObject { [key: string]: any; } export interface TranslationsMap { [x: string]: () => Promise<TranslationsObject>; } export interface TranslateParams { count?: number; [key: string]: any; } export interface TranslateOptions { escapeValue?: boolean; }
Setting up the context
We are going to need a React context to store our translations object which can be used in all the child components.
touch src/translations/translations-context.tsx
import { createContext } from 'react' import { TranslationsObject } from './translations.interfaces' interface TContext { locale: string; isLoading: boolean; setLocale: (language: string) => void; translations: TranslationsObject; defaultLocale: string; } export const TranslationsContext = createContext < TContext > { locale: '', isLoading: true, setLocale: () => null, translations: {}, defaultLocale: '', }
Our basic setup is done for the translations library, let's start building our Translation provider and "useTranslation" hook in the next part.
Top comments (0)