DEV Community

Simon Boisset
Simon Boisset

Posted on • Originally published at simonboisset.com

i18n: The Type-Safe Approach

Internationalization (i18n) is a crucial aspect of modern web development. This article explores how to implement a type-safe i18n solution using the typed-locale library in a React application.

Introduction to typed-locale

typed-locale is a lightweight, type-safe internationalization library designed to work with TypeScript. It provides an API for managing translations with type safety for both keys and variables.

Setting Up the Project

Let's create a new React project using Vite with TypeScript:

npm create vite@latest my-i18n-app -- --template react-ts
cd my-i18n-app
npm install
Enter fullscreen mode Exit fullscreen mode

Now, install typed-locale:

npm install typed-locale
Enter fullscreen mode Exit fullscreen mode

Defining Translations

Create a new file called translations.ts in the src folder:

// src/translations.ts
import { InferTranslation, plural } from 'typed-locale';

export const en = {
  greeting: 'Hello, {{name}}!',
  itemCount: plural({
    none: 'You have no items.',
    one: 'You have one item.',
    other: 'You have {{count}} items.',
  }),
  nav: {
    home: 'Home',
    about: 'About',
    contact: 'Contact',
  },
} as const;

export type Translation = InferTranslation<typeof en>;

export const fr: Translation = {
  greeting: 'Bonjour, {{name}} !',
  itemCount: plural({
    none: 'Vous n'avez aucun article.',
    one: 'Vous avez un article.',
    other: 'Vous avez {{count}} articles.',
  }),
  nav: {
    home: 'Accueil',
    about: 'À propos',
    contact: 'Contact',
  },
};
Enter fullscreen mode Exit fullscreen mode

Creating the Translator

Now, let's create a custom hook to use our translations. Create a new file called useTranslator.ts:

// src/useTranslator.ts
import { createTranslatorFromDictionary } from "typed-locale";
import { useMemo } from "react";
import { en, fr, Translation } from "./translations";

const dictionary = { en, fr };

export const useTranslator = (locale: keyof typeof dictionary) => {
  return useMemo(
    () =>
      createTranslatorFromDictionary<Translation>({
        dictionary,
        locale,
        defaultLocale: "en",
      }),
    [locale]
  );
};
Enter fullscreen mode Exit fullscreen mode

Using the Translator in Components

Now, let's use our translator in a React component. Update your App.tsx:

// src/App.tsx
import React, { useState } from "react";
import { useTranslator } from "./useTranslator";

const App: React.FC = () => {
  const [locale, setLocale] = useState<"en" | "fr">("en");
  const [itemCount, setItemCount] = useState(0);
  const translator = useTranslator(locale);

  return (
    <div>
      <select
        value={locale}
        onChange={(e) => setLocale(e.target.value as "en" | "fr")}
      >
        <option value="en">English</option>
        <option value="fr">Français</option>
      </select>

      <nav>
        <ul>
          <li>{translator((t) => t.nav.home)}</li>
          <li>{translator((t) => t.nav.about)}</li>
          <li>{translator((t) => t.nav.contact)}</li>
        </ul>
      </nav>

      <h1>{translator((t) => t.greeting, { name: "World" })}</h1>

      <p>{translator((t) => t.itemCount, { count: itemCount })}</p>
      <button onClick={() => setItemCount(itemCount + 1)}>Add Item</button>
      <button onClick={() => setItemCount(Math.max(0, itemCount - 1))}>
        Remove Item
      </button>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Type Safety Features

typed-locale provides several type safety features:

  1. Autocomplete for translation keys: The IDE provides autocomplete suggestions for all available translation keys.

  2. Type checking for variables: TypeScript catches incorrect variable usage:

   // This will cause a TypeScript error
   translator((t) => t.greeting, { wrongVariable: "World" });
Enter fullscreen mode Exit fullscreen mode
  1. Nested translations: The type system understands nested translations, allowing translator(t => t.nav.home).

  2. Pluralization: The itemCount translation demonstrates how typed-locale handles pluralization, automatically selecting the correct plural form based on the count value.

Technical Advantages

The type-safe approach using typed-locale offers several technical advantages:

  1. Compile-time error detection: Translation-related errors are caught during compilation rather than at runtime.

  2. Improved developer experience: Autocomplete for translation keys enhances productivity.

  3. Refactoring support: TypeScript's refactoring tools work seamlessly with the translation keys.

  4. Prevents key typos: The callback approach eliminates string typos in translation keys.

  5. Small bundle size: At 1kB, typed-locale has minimal impact on application size.

  6. Framework agnostic: While this example uses React, typed-locale can be used with any JavaScript framework or vanilla JS.

Conclusion

Implementing i18n with a type-safe approach using typed-locale provides a robust solution for managing translations in TypeScript projects. By leveraging the type system, developers can create more reliable internationalized applications while maintaining code quality and productivity.

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay