· tutorials · 6 min read

React Internationalization (i18n) using Context API

Learn how to implement the Provider pattern with Context API and see how it can be used for internationalization in a React application.

Get a step-by-step guide on using the Provider pattern with React's Context API for i18n, along with a working code example to help you get started with this powerful technique.

React’s Context API is a powerful feature that enables the easy sharing of data between components without having to pass down props manually at every level of the component tree.

There are many use cases for using it but one of the most common use cases for context is internationalization (i18n), which allows for easy translation of text in different languages.

In this blog post, we will explore how to implement the Provider pattern with React Context API, and provide a simple example of how it can be used for i18n.

Provider Pattern and React Context API

The Provider pattern is a way of providing data to all components in a React component tree, without having to pass down props manually to every component. Basically, it means avoiding Prop drilling.

Prop drilling is a situation where data is passed from one component through multiple interdependent components until you get to the component where the data is needed.

To accomplish this we will be using the Context API, as it provides a way to create a global state object that any component in the app can access. The Provider component is used to wrap the entire component tree and provide the global state to all components that need it.

Implementation

Let’s say we have an application that needs to support multiple languages. We can create a context object for i18n, which will contain the language-specific text for the application. We can then create a Provider component that will wrap the entire component tree, and provide the i18n context to all components that need it.

Start with creating a file called i18n-context.tsx in the root directory, then import the necessary modules for our context.

import React, { createContext, useState, ReactNode } from 'react';

Now for stick typing and to avoid run-time errors, define a Language type that represents the available languages in our application. Then also define an interface called I18nContextType which represents the shape of our context store. It contains the current language selected, a setLanguage function to update the selected language and an i18n object which contains the translations for each language.

export type Language = 'en' | 'fr';

export interface I18nContextType {
  language: Language;
  setLanguage: (language: Language) => void;
  i18n: {
    [key in Language]: {
      greeting: string;
      buttonText: string;
    };
  };
}

Now define the i18n object that contains the translations for each language or create a seperate constant file and export the i18n object.

const i18n: I18nContextType['i18n'] = {
  en: {
    greeting: 'Hello',
    buttonText: 'Click me',
  },
  fr: {
    greeting: 'Bonjour',
    buttonText: 'Cliquez-moi',
  },
};

Next create the i18n context using the createContext function, passing in an object with the default values of language, setLanguage, and i18n.

export const I18nContext = createContext<I18nContextType>({
  language: 'en',
  setLanguage: () => {},
  i18n: i18n,
});

Next let’s create the I18nProvider component that wraps around our entire application, passing the children prop. In the component, we use the useState hook to store the language and setLanguage values then we use the I18nContext.Provider to provide the context to all child components.

interface I18nProviderProps {
  children: ReactNode;
}

export const I18nProvider = ({ children }: I18nProviderProps) => {
  const [language, setLanguage] = useState<Language>(
    (navigator.language.slice(0, 2) as Language) || 'en'
  );

  return (
    <I18nContext.Provider value={{ language, setLanguage, i18n }}>
      {children}
    </I18nContext.Provider>
  );
};

The I18nProvider component sets the default language to the user’s preferred language by using navigator.language.slice(0, 2) to extract the first two characters of the user’s language and match it to our defined Language type. If the user’s language is not supported, it defaults to English.

By wrapping the application with the I18nProvider, all child components can access the context store using the useContext hook. They can use the values of language, setLanguage, and i18n to display language-specific text and update the language when the user selects a different one.

Here is how the i18n-context.tsx will look like after following the above steps:

import React, { createContext, useState, ReactNode } from 'react';

// Declaing the types/interfaces for Context Store
type Language = 'en' | 'fr';

interface I18nContextType {
  language: Language;
  setLanguage: (language: Language) => void;
  i18n: {
    [key in Language]: {
      greeting: string;
      buttonText: string;
    };
  };
}

// Define language-specific text
const i18n: I18nContextType['i18n'] = {
  en: {
    greeting: 'Hello',
    buttonText: 'Click me',
  },
  fr: {
    greeting: 'Bonjour',
    buttonText: 'Cliquez-moi',
  },
};

// Create i18n context object
export const I18nContext = createContext<I18nContextType>({
  language: 'en',
  setLanguage: () => {},
  i18n: i18n,
});

interface I18nProviderProps {
  children: ReactNode;
}

// Create Provider component
export const I18nProvider = ({ children }: I18nProviderProps) => {
  const [language, setLanguage] = useState<Language>(
    (navigator.language.slice(0, 2) as Language) || 'en'
  );

  return (
    // Provide i18n context to all child components
    <I18nContext.Provider value={{ language, setLanguage, i18n }}>
      {children}
    </I18nContext.Provider>
  );
};

Usage

To consume the i18n context, we use the useContext hook from React. The useContext hook takes the context object created using createContext as an argument and returned the current context value.

Let’s wrap the App component with the I18nContext.Provider component.

import  *  as  React  from  'react';
import { StrictMode } from  'react';
import { createRoot } from  'react-dom/client';
import { I18nProvider } from  './I18nContext';
import  App  from  './App';
const  rootElement = document.getElementById('root');
const  root = createRoot(rootElement);

root.render(
<StrictMode>
	<I18nProvider> // Our Provider
		<App  />
	</I18nProvider>
</StrictMode>
);

Now update the App component with the following code:

import  React, { useContext } from  'react';
import { I18nContext } from  './I18nContext';

function  App() {
	const { language, i18n, setLanguage } = useContext(I18nContext);
	const  toggleLanguage = () => {
		setLanguage(language === 'en' ? 'fr' : 'en');
	}; 

	return (
		<div>
			<div>{i18n[language].greeting}</div>
			<button  onClick={toggleLanguage}>{i18n[language].buttonText}</button>
		</div>
	);
}
export  default  App;

The App component uses the useContext hook to access the language, i18n, and setLanguage properties from the I18nContext object.

It then renders a div with the current greeting based on the current language and a button that allows the user to toggle between the supported languages.

When the user clicks on the button, the toggleLanguage function is called, which updates the language property using the setLanguage function from the context.

The i18n object is used to retrieve the appropriate greeting and button text for the current language.

Conclusion

The Provider pattern with React Context API is a powerful way to provide a global state to all components in a React component tree. The i18n example above demonstrates how we can use this pattern for internationalization in our applications. By using the Provider pattern, we can avoid the need to pass down props manually at every level of the component tree, which can simplify our code and make it easier to maintain.

Need Help with i18n Implementation?

If you need assistance in implementing internationalization (i18n) for your React or Next.js application, I offer freelance services to help you seamlessly integrate multi-language support into your website or web app.

I can ensure that your i18n implementation is efficient, maintainable, and provides a smooth user experience across different languages and locales.

Whether you’re starting a new project or looking to add i18n to an existing codebase, I can work with you to understand your specific requirements and deliver a tailored solution that meets your needs.

Feel free to reach out to me here - Contact Seerat Awan

Seerat Awan

Tech Enthusiast | Frontend Developer

@seeratawan01

Subscribe to my newsletter to get the latest updates on my blog.

Back to Blog