Pluralization looks easy until you start handling it in code. For example, English adds an “s” for plurals (e.g., 1 apple, 2 apples), and also has irregular plural forms (e.g., 1 child, 2 children). Other languages, like Arabic, have six distinct plural forms, while Polish uses one form for 2-4 units and another for 5 or more.

If you miss these nuances, your app will say things like “2 item” or “5 apple”, breaking immersion and making your UI feel unnatural. Worse, hardcoding plural rules creates a maintenance nightmare when expanding to new languages.

This guide outlines the best tactics for tackling pluralization that every beginner should be aware of.

🧮 The complexities of pluralization 🔗

Pluralization goes beyond the basic plural form, like "one" versus "many." Languages have different plural categories (or forms) based on numbers. For instance, English primarily uses "one" (singular) and "other" (plural, for anything other than one).

However, many other languages have more categories, such as "zero," "few," "many," and "other." Russian, for example, distinguishes between "one," "few," "many," and "other." Arabic has even more intricate rules, including dual forms. Consider the following examples:

  • 🇬🇧 English: 1 apple, 2 apples, 5 apples, 0 apples.
  • 🇷🇺 Russian: 1 яблоко (yabloko - apple), 2 яблока (yabloka), 5 яблок (yablok), 0 яблок (yablok).
  • 🇵🇱 Polish: 1 jabłko, 2 jabłka, 5 jabłek, 0 jabłek.

As you can see in the example above, the plural forms and the word endings change. These variations are not incidental; they follow specific grammatical rules within each language.

✅ Do's and don'ts of pluralization management 🔗

Here are some best practices to follow:

1. Never hardcode pluralization rules 🔗

if (count === 1) { return "1 apple"; } else { return count + " apples"; }

Hardcoding pluralization rules like in the example above isn’t recommended because it limits flexibility and makes it difficult to change language rules or even add new ones.

💡
A better approach is to use i18n libraries, like i18next, that handle pluralization based on pre-defined language rules. With i18next, you'll be able to update plural rules and add support for new languages easily.

2. Use a consistent approach 🔗

Consistently use a well-maintained library like the aforementioned i18next (for JavaScript), vue-i18n (for Vue.js), or Angular's built-in i18n features. These libraries provide support for different pluralization categories and simplify the implementation process.

In addition, use placeholders like count, amount and date within translation strings to handle numbers and do not concatenate number + string!

You can also use the ICU Message Format to define pluralization rules within your translation files. This format is widely supported and offers flexibility in handling complex pluralization scenarios.

3. Use automated validation tools 🔗

Use validation tools like Linguistic Quality Assurance (LQA), terminology checkers, or other validation features provided by your preferred localization library or platform to catch pluralization errors early in the development process.

Below are some of the errors you can catch using validation tools:

  • Missing forms: The tool can check if all required plural forms are present for each language.
  • Inconsistent pluralization: The tool can identify instances where different plural forms are used for the same string in the same language.
  • ICU syntax errors: The tool can validate the syntax of the ICU Message Format used for defining pluralization rules.
📖 Related read: Beyond interpolation: multiple plurals, genders, and building lists

👇 Practical examples and implementation 🔗

Now that we've covered the best practices for managing pluralization, let's move on to a practical example. We'll demonstrate how to handle pluralization in a Next.js application using the i18next library.

🔔 This guide focuses on adding pluralization features to your existing Next.js application. If you're new to Next.js, explore their official documentation to get up and running quickly.
  1. First, install the necessary dependencies with the following command:npm install i18next react-i18next
  2. Next, within the src directory of your Next.js application, create a components directory, and inside it, create an i18n.js file with the following snippet:
//src/components/i18n.js
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
const resources = {
  en: {
    translation: {
      apples_zero: "No apples",
      apples_one: "{{count}} apple",
      apples_other: "{{count}} apples",
    },
  },
  ru: {
    translation: {
      apples_zero: "нет яблок",
      apples_one: "{{count}} яблоко",
      apples_few: "{{count}} яблока",
      apples_many: "{{count}} яблок",
    },
  },
  pl: {
    translation: {
      apples_zero: "Brak jabłek",
      apples_one: "{{count}} jabłko",
      apples_few: "{{count}} jabłka",
      apples_many: "{{count}} jabłek",
    },
  },
};
i18n.use(initReactI18next).init({
  resources,
  lng: "en", // default language
  fallbackLng: "en",
  interpolation: {
    escapeValue: false,
  },
  pluralSeparator: "_", // Important for i18next to distinguish plural forms
});
export default i18n;

The snippet above does the following:

  • Imports the i18n library, which is the major internationalization framework, and also imports initReactI18next to help integrate i18n with React.
  • Defines translations for English ( en ), Russian ( ru ), and Polish ( pl ) in the resource object.
  • The translation object contains a key ( apples ) and its corresponding plural forms: zero, one, few, many, and other. This structure allows for accurate translation based on the number of apples.
  • The i18n instance is initialized using the i18n.init() method. This step involves configuring key settings such as the default language (lng), fallback language (fallbackLng), interpolation options, and the separator used to distinguish plural forms (pluralSeparator).

The pluralSeparator option is vital for i18next to correctly identify the different plural forms.

3. Within the same components directory, create a new file named pluralForms.js and add this code:

//src/components/pluralForms.js
"use client";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import i18n from "./Translation";
const MyComponent = () => {
  const { t } = useTranslation();
  const [count, setCount] = useState(0);
  const [language, setLanguage] = useState("en");
  const handleLanguageChange = (lang) => {
    i18n.changeLanguage(lang);
    setLanguage(lang);
  };
  const Translanguages = [
    { code: "en", name: "English" },
    { code: "ru", name: "Russian" },
    { code: "pl", name: "Polish" },
  ];
  return (
    <div className="container mx-auto p-4">
      {/* Language Buttons */}
      <div className="flex justify-center space-x-4">
        {Translanguages.map((lang) => (
          <button
            key={lang.code}
            onClick={() => handleLanguageChange(lang.code)}
            className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
          >
            {lang.name}
          </button>
        ))}
      </div>
      {/* Items Counter */}
      <div className="flex justify-center mt-8">
        <p className="text-xl font-bold">{t("apples", { count: count })}</p>
      </div>
      {/* Add/Minus Buttons */}
      <div className="flex justify-center mt-4 space-x-2">
        <button
          onClick={() => setCount(count + 1)}
          className="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
        >
          Add Apple
        </button>
        <button
          onClick={() => setCount(count - 1)}
          disabled={count === 0}
          className="px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
        >
          Minus Apple
        </button>
      </div>
    </div>
  );
};
export default MyComponent;

The above snippet does the following :

  • It imports useState modules for managing component state, useTranslation for handling translations with i18next, and imports the initialized i18n instance from the i18n.js file.
  • The component provides language selection buttons for English, Russian, and Polish. When a language button is clicked, the handleLanguageChange function updates the language state and triggers a language change in the i18ninstance.
  • Then it renders the current count of apples using the t function from useTranslation and the items key, which is dynamically translated based on the current language and the number of apples.

4. Now import and render the pluralForms component inside the page.js file with the below snippet:


//src/app/page.js
import React from "react";
import MyComponent from "../components/pluralForms";
function page() {
  return (
    <div>
      <MyComponent />
    </div>
  );
}
export default page;

Upon saving and starting the application, you should see the following in your browser:

article-image

This example demonstrates how to define plural forms in a translation file and use the t function from react-i18next to display the correct plural form based on the count variable. Also, by defining translations with placeholders for different plural forms, various grammatical structures and edge cases (like zero or large numbers) were handled with ease.

🪁 How to simplify pluralization with Localazy 🔗

Localazy takes the headache out of plural localization by automatically handling all those tricky language rules. Translators see exactly what plural forms they need to fill in to cover all the cases.

Just make sure to define the correct plural form that you are using in your CLI configuration. Localazy provides built-in tools and features for managing the translations and their plural forms across all languages.

Some of the features that can help with proper plural localization also include:

  • 📝 Context sharing:  Add context information to your translations and help translators understand the meaning and usage of each string.
  • 📖 Translation memory: Reuse translations across projects, reducing the amount of work required.
  • 🎲 Custom plurals definition: If your i18n library expects something other than standard plurals for a given language, you can adjust the way Localazy handles them.
article-image

The benefits of using Localazy when it comes to plurals:

  • Improved consistency: Localazy helps you maintain consistent pluralization across all your languages.
  • Reduced errors: Automated checks help you catch mistakes before they reach your users.
  • Streamlined workflows: Localazy simplifies the translation process, making it easier for translators to work on your projects.

Configure Localazy to recognize variables 🔗

There are different plural variants that Localazy supports for the given file format.

Below is an example configuration for the "underscore postfix" variant:


//localazy.json

{
  "writeKey": "YOUR WRITE KEY",
  "readKey": "YOUR READ KEY",
  "upload": {
    "files": "public/locales/translations/plurals.json",
    "type": "json",
    "features": ["plural_postfix_us"]
  },
}

The ["plural_postfix_us"] tells Localazy that the translation file uses suffixes to define plural variants. The suffix _us indicates that the plural forms follow an underscore pattern. Below is an example of how to set up a plural variant in your React application.

First, create a JSON file (e.g., plurals.json) in the public folder of your React project. If you are handling pluralization for just English, then define the variants like in the below using _one and _other suffixes:


{
  "apple_one": "1 apple",
  "apple_other": "{{count}} apples"
}

Next, update the localazy.json file to include the features option:


//localazy.json
{
  "writeKey": "YOUR_WRITE_KEY",
  "readKey": "YOUR_READ_KEY",
  "upload": {
    "files": "public/locales/translations/plurals.json",
    "type": "json",
    "features": ["plural_postfix_us"]
  },
  "download": {
    "files": "public/locales/ios-app/${iosLprojFolder}/Localizable.strings"
  }
}

In the snippet above:

  • files: Specifies the path to the translation file (public/locales/translations/plurals.json).
  • features: Includes plural_postfix_us to handle plural variants with an underscore pattern.

Lastly, upload the translations with the localazy upload command:

localazy upload

The above command uploads the plurals.json file and makes sure that the variants ( _one,_other) are recognized and processed correctly by Localazy.

Later, when you download the translated file, e.g., for the Polish language, the plurals would look like this:


{
  "apple_one": "1 jabłko",
  "apple_few": "{{count}} jabłka",
  "apple_many": "{{count}} jabłek"
}

➡️ Next steps 🔗

Implementing these best practices will result in a more consistent and user-friendly experience for your app users, regardless of their language.

Explore our documentation to further simplify your localization process and take your multilingual development to the next level. The integrated machine translation will help you reach new markets faster, and Localazy will serve as a source of truth for your translation, making collaboration with your team and translators much easier.