diff --git a/kb/en-US/best-localization-software.mdx b/kb/en-US/best-localization-software.mdx index fac61fe4b..5d539de8a 100644 --- a/kb/en-US/best-localization-software.mdx +++ b/kb/en-US/best-localization-software.mdx @@ -1,93 +1,149 @@ --- title: What is the best localization software for developers? -summary: Look for one system that covers the whole pipeline, from the i18n library in your code to translation and delivery, instead of stitching together a separate library, translation management system, and translation vendor. -description: Look for one system that covers the whole pipeline, from the i18n library in your code to translation and delivery, instead of stitching together a library, a TMS, and a vendor. +summary: The best localization software for developers translates content where it is written in the code, with context from your product, and keeps every language current. General Translation does this with open-source libraries, Context Groups, and Locadex, an AI agent which opens pull requests. +description: The best localization software for developers translates content in the code, with product context, and keeps each language current, as General Translation's libraries, Context Groups, and Locadex do. date: 2026-05-04 +updated: 2026-10-21 category: workflow-tools authors: [jackie] redirectFrom: [best-localization-software] related: [i18n-without-translation-files, multilingual-nextjs-seo] --- -If you search "best localization software" right now, you'll find a dozen articles written by localization vendors, each ranking themselves first. They compare feature matrices, list pros and cons, and conclude (surprise!) that you should pick them. +The best localization software for developers translates content where it is written in the code, gives each translation context from your product, keeps every language current as the code changes, and lets people who read the language review the result. General Translation is localization software for developers that does all four in one system. -We're not going to do that. Instead, we'll explain why the entire category is solving the wrong problem, and what solving the right one looks like. +General Translation combines open-source internationalization (i18n) libraries, an AI-native translation platform, and Locadex, an AI agent which internationalizes your code and translates your content through pull requests. Because it connects your code and your translations, each translation can carry context from the code and stay consistent across releases. -## The localization stack is broken into three +## How do I choose localization software for my app? -Shipping a multilingual app in 2026 requires stitching together three completely separate systems: +Choose localization software by where your app's text lives, how often the copy changes, and whether you have a requirement that only one kind of product meets. If the text lives in React or Next.js components and changes with most releases, choose a developer-first tool, such as General Translation, that translates in the code and in CI. Choose another product for a specific requirement: Phrase when translation agencies work in XLIFF or Office files, Lokalise when native mobile apps must update their text without an app store release, Weblate or Tolgee when the platform has to run on your own servers, and Google Translate when you need one document in another language. -1. **An i18n library** — handles string extraction, interpolation, pluralization, and locale routing inside your codebase. It renders translations. It doesn't produce them. -2. **A translation management system (TMS)** — stores translation files, manages workflows, and coordinates handoffs. It sits between your code and your translators but doesn't do the translating. -3. **A language service provider (LSP)** — the human translators, agencies, or machine translation engines that actually produce the translated content. They don't know anything about your code or your TMS. +Localization software covers three jobs, and each option leaves some of them to you. Internationalization prepares the code to render each locale's dates, numbers, and plural forms. Translation writes the text in each language. Tooling keeps the translations in sync with the code as it changes. When an i18n library, a translation management system, and a translation vendor split those jobs, each tool sees only part of the product. -The library knows nothing about your translations. The TMS knows nothing about your code. The translation provider knows nothing about either. Wiring them together takes weeks of engineering time upfront, and then bleeds hours every sprint on syncing files, resolving merge conflicts, debugging webhooks, and babysitting a pipeline that was never designed to be one. +## How do the approaches to localization compare? -## What "end-to-end" actually means +| Approach | What it does well | What it leaves to you | +| --- | --- | --- | +| An i18n library alone (`i18next`, `react-intl`, `next-intl`) | Mature runtimes for rendering translations and formatting values | Extracting strings into key files, refactoring components, and producing and maintaining every translation | +| Google Translate | Fast, general-purpose translation of text and whole documents | It translates each string on its own, without the surrounding UI or your tone; plurals, delivery, and review are up to you | +| A raw AI API (OpenAI API, Claude API) | Fluent translation that follows instructions | Assembling context for every string, keeping terms consistent across releases, plurals and locale formatting, review, and delivery | +| A translation management system (TMS), such as Phrase, Lokalise, or Crowdin, which handles strings outside the codebase | One place for translators and vendors to edit and review strings | Exporting strings to files and importing the results, context that an exported list doesn't carry, and keeping the export current as the code changes | +| General Translation (libraries, Context Groups, and Locadex) | [``](/docs/react/reference/components/t) translates JSX in place, Context Groups (shared instructions for terms and tone) apply to new translations, and the `gt` CLI or Locadex translates each change | Wrapping content in [``](/docs/react/reference/components/t) (or letting Locadex do it), and a Starter or Enterprise plan for Locadex and team invites | -General Translation is not a library. It's not a TMS. It's the whole pipeline, used by developer-first teams at Cursor, Ramp, Profound, Cognition, Sierra, and ClickHouse. +Use an i18n library alone when translators already deliver complete language files and you only need a runtime to render them. Use Google Translate to read or send one document in another language, and a raw AI API when translation is one step in a model call your own product makes. Use a translation management system when you need a capability General Translation doesn't offer, such as exchanging XLIFF files with translation agencies or updating text in a released native app. Use General Translation when copy lives in code, changes with most releases, and each change should ship in every language with the release that adds it. -- **Open-source developer libraries**: [`gt-next`](/docs/react/nextjs-quickstart), [`gt-react`](/docs/react/react-quickstart), and support for [React Native/Expo](/docs/react/react-native-quickstart). Drop-in SDKs with full TypeScript support. -- **An AI-first translation platform** that understands your codebase, your product, and your terminology. Not generic machine translation bolted on top. -- **Locadex, the AI agent**: an [automated internationalization engineer](/docs/platform/locadex/quickstart) that scans your codebase, internationalizes your code, creates translations, and opens pull requests on every push. +## Is paid localization software worth it, or can I manage JSON locale files myself? -Building both the library and the translation engine together makes localization ten times easier. No JSON export/import, no file management, no agencies to coordinate. Translations flow directly from your source code to production. +You can manage JSON locale files yourself while the app is small: a few hundred strings, one or two target languages, and copy that rarely changes. The work grows with the number of keys times the number of languages. For example, an app with 500 keys in 10 target languages has 5,000 translated entries to keep current, and at 50 languages it has 25,000. -## What's wrong with traditional TMS platforms +Each change to the source copy repeats that work in every language file. A new string needs a key in the source file and a translation in every locale file. A changed string leaves its translations out of date until someone translates it again, and a deleted component leaves its keys in every file until someone removes them. -Legacy translation management systems were built for a pre-AI world. They assume a specific workflow: content gets extracted from your codebase, uploaded to a dashboard, assigned to translators, reviewed, approved, exported, and re-integrated. It works some of the time, but it's slow, expensive, and fragile. +An i18n library doesn't do that work. Libraries such as i18next, react-intl, and next-intl render whatever the files contain and don't write or update any translation, so a key added to `en.json` but missed in `ja.json` leaves that string untranslated for Japanese users. -**They manage files, not code.** A TMS ingests your translation files (JSON, XLIFF, PO, etc.) and gives you tools to manage them. But the real source of truth is your codebase: your components, your variables, your UI hierarchy. Managing the files without understanding the code means context is always lost in transit. +Localization software is worth paying for when maintaining those files costs more engineering or translator time than the software, or when releases wait for translations. With General Translation, copy stays in the component inside [``](/docs/react/reference/components/t), so there are no keys to name, and [`npx gt translate`](/docs/cli/reference/commands/translate) writes each language's file in every build. The CLI translates only content whose source changed and can reuse a matching translation from an earlier version of the same file. -Most platforms have added AI/MT features in the last few years, usually by routing strings through third-party machine translation engines and letting you pick which one to use. The AI sees isolated strings. It doesn't see your component tree, your variable names, or the surrounding UI. That's why MT output still needs heavy editing. +General Translation's libraries are free and MIT-licensed, and they work without an account when you supply the translations: [`npx gt generate`](/docs/cli/reference/commands/generate) writes a template file for each locale to translate by hand. Generating translations through the platform is usage-based, with no seat fees. -**They scale by adding people, not by removing work.** The TMS business model is built around coordinating human translators: roles, permissions, review chains, approval workflows. That's necessary for regulated enterprise content. It's pure overhead for a dev team shipping a SaaS app in 20 languages. +## When is another localization product a better fit? -And pricing? Several of the most popular platforms require a sales conversation to even see numbers. Others charge per-seat fees that balloon as your team grows. Unpredictable costs make it hard to budget for new languages. +Another product is a better fit when you need a capability General Translation doesn't offer. Each of these capabilities is described in the product's own documentation: -## What's wrong with standalone i18n libraries +- **Translation agencies that work in XLIFF or Office files.** [Phrase TMS](https://phrase.com/platform/tms/) supports XLIFF 1.2 and 2.0 and Microsoft Office files, gives translators a CAT tool with translation memory, and can assign several vendors to one job. General Translation translates with AI and doesn't read or write XLIFF or Office files (its Google Drive integration translates Google Docs, Sheets, and Slides), so an agency workflow built on those files belongs in a TMS. +- **Native mobile apps that update their text without a release.** Lokalise's [OTA SDK](https://developers.lokalise.com/docs/ota-sdk) for iOS, Android, and Flutter downloads new translations when the app starts, without a new app store version. General Translation's CLI translates [iOS](/docs/cli/reference/formats/xcstrings-files) and [Android](/docs/cli/reference/formats/android-strings-files) string files before the build, so new translations reach a native app with its next release. +- **A localization platform on your own servers.** [Weblate](https://docs.weblate.org/en/latest/admin/install.html) installs with Docker, a virtualenv, or Kubernetes, and [Tolgee](https://docs.tolgee.io/platform/self_hosting/getting_started) runs from Docker. General Translation's libraries are open source, but its translation platform and Dashboard are hosted by General Translation. +- **A website translated with a snippet.** [Transifex Live](https://help.transifex.com/en/articles/6244596-introduction-to-transifex-live) works through a JavaScript snippet added to the site and saves content for translation straight from the page, without files; Transifex points React and Vue apps to its Native SDK instead. General Translation translates content marked in your code, files listed in `gt.config.json`, and content in connected tools such as Sanity, Storyblok, Payload, and Google Drive. +- **A free license for an open-source project.** [Crowdin](https://crowdin.com/product/for-open-source) is free for open-source projects that meet its conditions, such as an approved open-source license and no commercial product tied to the project. General Translation charges for platform usage beyond its free allowance. -Popular i18n libraries for React and Next.js handle rendering well: interpolation, pluralization, locale routing, Server Component support. +Outside those requirements, a translation management system adds an export and import cycle to each release: extract strings into files, sync them to the TMS, wait for translations, and import the results. An exported string also doesn't include the component, variables, and conditions around it in the code, so whoever translates it works without that context. -**But they don't translate anything.** +## When is a developer-first localization tool the right choice? -They render whatever you give them. You still need to: -- Extract every translatable string into JSON files -- Maintain a separate file for every locale -- Either translate those files manually, hire translators, or plug in a TMS -- Keep every file in sync as your codebase changes +A developer-first localization tool is the right choice when most of your product's text lives in code (for example, in React or Next.js components) and copy changes with most releases. General Translation is built for that case: copy stays in your components, translation runs in your build or in a pull request, reviewers who don't edit code check translations in the Dashboard, and each change ships in every language with the release that adds it. -At 10 languages and 500 keys, that's 5,000 entries to maintain. At 50 languages it's 25,000. The library doesn't help you with any of that. It just renders whatever you give it — half the problem, at best. +General Translation also fits teams that already use an i18n library. If your app uses next-intl or react-i18next, the `gt` CLI detects the library and translates your existing message files in that library's string syntax, so you can adopt General Translation without changing libraries. -## How General Translation closes the loop +## Can I use Google Translate or an AI API instead of localization software? -**1. Install and write code.** Run the [setup wizard](/docs/cli/quickstart) and wrap content in [``](/docs/react/reference/components/t) components. No key extraction. No JSON files. +Yes, for one-off text. Google Translate's website [translates a whole document](https://support.google.com/translate/answer/2534559): you upload a `.docx`, `.pdf`, `.pptx`, or `.xlsx` file of up to 10 MB and download the translation. The OpenAI API and the Claude API fit when translation is one step inside a model call your product already makes, such as replying to a support ticket in the customer's language, and you want to choose the model and write the prompt yourself. -```tsx +A production app needs more around the translation: context for each string, the same terms in every release, plural forms and locale formatting, review, and delivery into the app. Google Translate translates each string on its own, and with an AI API you assemble the context for every string yourself. + +General Translation handles those parts around AI translation. It translates each [``](/docs/react/reference/components/t) block as one unit with its nested elements, applies the Glossary and Custom Prompts in your Context Groups, formats values for each locale, delivers translations with your build or from its CDN, and shows every translation in the Dashboard for review. + +## How does General Translation translate content in the code? + +General Translation's [``](/docs/react/reference/components/t) component wraps JSX and translates it in place, as one unit with its nested elements, so you don't assign keys or write a file for each language by hand. Run [`npx gt init`](/docs/cli/reference/commands/init) to detect your framework and create `gt.config.json`, then wrap your content: + +```tsx title="app/page.tsx" import { T } from 'gt-next'; export default function Home() { return ( - -

Welcome to our app

-

This content is translated automatically.

-
+
+ +

Welcome to my app

+

This content will be translated automatically.

+
+
); } ``` -**2. Develop.** Translations appear on-demand. Switch languages and see results immediately. +For standalone strings, such as placeholders and `aria-label` values, use the function that [`useGT`](/docs/react/reference/hooks/use-gt) returns. General Translation's libraries are free, open source, and MIT-licensed: `gt-next` for Next.js, `gt-react` for React, `gt-vue` for Vue, and `gt-node` for Node.js. + +In React apps, General Translation's [``](/docs/react/reference/components/num), [``](/docs/react/reference/components/currency), and [``](/docs/react/reference/components/datetime) components format values for each locale. [``](/docs/react/reference/components/plural) picks each language's plural form: English has 2 forms, Arabic has 6, and Japanese has 1. + +## How does General Translation give each translation context? + +A model translates a string well only when it knows what the string means in your product. General Translation applies the [Context Groups](/docs/platform/dashboard/guides/defining-context-for-translations) you assign to a project to each new translation in it. A Context Group holds a Glossary for terms that need consistent treatment, such as a product name that stays untranslated, and Custom Prompts for audience, formality, and style. You can assign one group to your app, website, and docs. + +For one ambiguous string, pass the `context` prop to General Translation's [``](/docs/react/reference/components/t), for example to say whether "cells" means spreadsheet cells or phones. Turn on the `includeSourceCodeContext` parsing flag in `gt.config.json` to add the surrounding lines of source code as context for each translation. + +## Where do General Translation's translations load from? + +In production, General Translation translates [``](/docs/react/reference/components/t) content during the build, before users load the app, and a missing translation falls back to the source text. Your configuration sets where `gt-next` and `gt-react` load translations from. + +With a local `output` path in `gt.config.json` and a [`loadTranslations`](/docs/react/reference/functions/load-translations) function, translations [ship in your bundle](/docs/react/guides/storing-translations), and updating them takes a redeploy. With a `projectId` and no [`loadTranslations`](/docs/react/reference/functions/load-translations) function, they load from General Translation's CDN at run time, after you enable the CDN in your project settings and publish them with [`npx gt translate --publish`](/docs/cli/reference/commands/translate). + +## How do translations stay current as the code changes? + +Run [`npx gt translate`](/docs/cli/reference/commands/translate) in CI before your build command, and every production build ships with current translations. General Translation's CLI translates only content whose source has changed. + +To automate the code changes as well, connect GitHub to [Locadex](/docs/platform/locadex/quickstart), General Translation's AI agent. You configure each Locadex automation on three points: when it runs, what it changes, and how its changes reach GitHub. [Configuring automations](/docs/platform/locadex/guides/configuring-automations) lists the default for each setting. + +- **When it runs.** **Run when** starts an automation when a pull request changes, when a commit is pushed to the **Target branch**, or only when someone clicks **Run now**. A `/locadex` comment on a pull request requests a run on its current revision, and turning **Active** off pauses the automatic triggers. +- **What it changes.** The template sets the kind of change: **Generate code** wraps interface text in i18n calls such as [``](/docs/react/reference/components/t), **Generate translations and push** translates new or updated source content into each target language, and **Keep locales in sync** updates project files when languages are added or removed. **Target directory** points an automation at one app in a monorepo, **Changed files only** limits the **Generate code** relevance check to changed lines, **Preserve local edits** keeps manual edits to files Locadex generated earlier, and pre-process and post-process commands run your own install, build, lint, or check steps around each run. +- **How changes reach GitHub.** Locadex commits to a branch named with your **Created branch prefix** and opens a pull request against the target branch. When a pull request triggered the run, Locadex links its pull request in a comment on yours, and later automatic runs for the same source branch update that pull request. You can set the pull request title, the description, and the commit message, and turn on **Auto-merge** to merge Locadex pull requests when their checks pass. + +Locadex supports Next.js, React apps built with tools such as Vite, Gatsby, or React Router, and the docs frameworks Mintlify, Fern, and Docusaurus, and it requires a Starter or Enterprise plan. + +## How do people review AI translations? + +AI translation has gotten very good, but a production app still needs someone who reads the language to check it. In General Translation's Dashboard, open **Project > Translations**, select the locales to compare side by side, and click a cell to edit it. If someone edits a downloaded translation file by hand, [`npx gt save-local`](/docs/cli/reference/commands/save-local) sends the edit back to the platform. + +Team invites in General Translation require a Starter or Enterprise plan, and Starter includes unlimited users with no seat charges, so every translator, product manager, and in-country reviewer can have access. -**3. Ship.** One command generates all translations at build time. They're pre-generated, cached, and delivered via CDN. [See the deploy guide →](/docs/react/nextjs-quickstart#11-deploy-to-production) +## How much does General Translation cost? -**4. Automate.** [Locadex](/docs/platform/locadex/quickstart) monitors your repo. When you push code, it scans for changes, internationalizes new content, generates translations, and opens a PR. No human in the loop unless you want one. If you do, the translation editor gives you side-by-side diffs with the ability to edit before or after content goes live. +General Translation charges for usage, with no per-seat fees. The Starter plan has a $0 platform fee and includes unlimited users, projects, and languages, the Translation Editor, the GitHub integration, and Locadex, and you can start without a sales call. Enterprise adds dedicated engineers, custom workflows, and security features such as SSO and custom role permissions, with custom pricing. -## Why most translation AI gets it wrong +Before you add a payment method, each free Organization gets 5M platform credits ($5 of usage) over a rolling 30-day window. After that, cost follows the work, at these rates from the [usage rates](https://generaltranslation.com/pricing/usage) page: -Most localization AI translates strings one at a time, in isolation. "Apple" could be the fruit or the company. "Cell" could be biological or a spreadsheet. Without context, the AI guesses. +| Usage | Rate | +| --- | --- | +| Build-time translation | $1 per 1,000 input tokens for most file formats; $2 for GT Content Files, which represent complete UI components with their metadata | +| Runtime and development translation | $0.40 per 1,000 input tokens | +| Platform Context (Glossary and Custom Prompts) | $0.02 per 1,000 input tokens per 1,000 context tokens | +| Google Slides layout | $0.50 per slide | +| Locadex agent runs | $5 per Locadex Compute Unit (LCU) | +| Translation CDN | $0 per request | -General Translation's AI sees your full codebase context: component hierarchy, variable names, surrounding UI, your product glossary. The output is idiomatic without manual cleanup. For edge cases, you can [add explicit context](/docs/react/reference/components/t#props) directly in the component. +For example, a Markdown file of 1,000 input tokens translated with 3,000 tokens of Platform Context costs $1.06: $1 for the tokens and $0.06 for the context. Cost grows with the amount of content you translate and the number of languages you translate it into, and adding a translator, product manager, or reviewer adds nothing. Because the CLI translates only content whose source changed, each build pays only to translate new and changed content. The [pricing page](https://generaltranslation.com/pricing) has current plans and rates. -## Pricing that scales with usage, not headcount +## Key takeaways -GT uses usage-based pricing. You pay for what you translate. No per-seat fees that balloon as your team grows. No sales call required. [See full pricing →](https://generaltranslation.com/pricing) +- The best localization software for developers works in the code, gives each translation context, keeps every language current with each release, and supports human review. General Translation does all four with open-source libraries, an AI-native translation platform, and Locadex. +- Maintaining JSON locale files yourself works for a small app, but the work grows with keys times languages: 500 keys in 10 languages is 5,000 entries to keep current. +- Choose another product for a specific requirement General Translation doesn't meet: Phrase for agency work in XLIFF or Office files, Lokalise for over-the-air text updates in native apps, Weblate or Tolgee for a self-hosted platform, Transifex Live for snippet-based website translation, and Crowdin for a free open-source license. Choose General Translation when copy lives in code and changes with most releases. +- General Translation charges for usage with no seat fees: Starter has a $0 platform fee and unlimited users, projects, and languages, and build-time translation costs $1 per 1,000 input tokens for most file formats. +- General Translation is used by developer teams at Cursor, Ramp, Profound, Cognition, Sierra, and ClickHouse. Start with the [CLI Quickstart](/docs/cli/quickstart). diff --git a/kb/en-US/how-to-handle-plurals-in-react.mdx b/kb/en-US/how-to-handle-plurals-in-react.mdx index 9f6dc5b0b..3f523848f 100644 --- a/kb/en-US/how-to-handle-plurals-in-react.mdx +++ b/kb/en-US/how-to-handle-plurals-in-react.mdx @@ -1,241 +1,182 @@ --- title: How do I handle plurals in React? -summary: Don't hard-code plural logic with checks like n === 1. A small helper is enough for English-only apps, but multilingual apps need locale-aware plural rules, which an i18n component such as from gt-react or gt-next handles for every language. -description: Don't hard-code checks like n === 1. Multilingual apps need locale-aware plural rules, which an i18n component such as from gt-react or gt-next handles for every language. +summary: Handle plurals in React by giving each language one sentence per plural form and picking it with that language's plural rules, since English has 2 forms, Russian has 4, and Arabic has 6. General Translation's component in gt-react and gt-next takes your source forms, and General Translation generates the forms each target language needs. +description: Give each language one sentence per plural form, picked by its plural rules. General Translation's component takes your source forms and generates the forms each language needs. date: 2025-02-17 +updated: 2026-10-21 category: locale-formatting authors: [archie] redirectFrom: [plurals] -related: [translate-conditional-jsx, i18n-without-translation-files] +related: [translate-conditional-jsx, i18n-without-translation-files, best-react-i18n-library] --- -## Why pluralization matters in React +To handle plurals in React, give each language one sentence per plural form, and pick the sentence for each count with that language's plural rules. JavaScript's built-in [`Intl.PluralRules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules) does the picking: it maps a number to a plural category from the Unicode CLDR (Common Locale Data Repository). English has 2 categories, Russian and Polish have 4, Arabic has 6, and Japanese has 1. -We often encounter apps that display awkward messages like: +A `count === 1` check models English only. Russian uses three forms for whole numbers, "1 файл", "2 файла", and "5 файлов" (1 file, 2 files, 5 files), and 21 takes the same form as 1. A check that shows "файл" for 1 and "файлов" for every other count renders "2 файлов" and "21 файлов", and both are wrong. -```text -You have 1 new message(s) -``` +## What are the plural categories in each language? + +CLDR defines six plural categories: `zero`, `one`, `two`, `few`, `many`, and `other`. Each language uses a subset, and its rules assign every number to one category in that subset. + +| Language | Categories | Which counts get each category | +| --- | --- | --- | +| English | `one`, `other` | 1 is `one`; 0, 2, 5, and 1.5 are `other` | +| Japanese, Chinese | `other` | Every count is `other` | +| Russian | `one`, `few`, `many`, `other` | 1, 21, and 101 are `one`; 2 to 4 and 22 are `few`; 0, 5 to 20, and 100 are `many`; 1.5 is `other` | +| Polish | `one`, `few`, `many`, `other` | 1 is `one`; 2 to 4 and 22 are `few`; 0, 5 to 21, and 100 are `many`; 1.5 is `other` | +| Arabic | `zero`, `one`, `two`, `few`, `many`, `other` | 0 is `zero`; 1 is `one`; 2 is `two`; 3 to 10 are `few`; 11 to 99 are `many`; 100 to 102 are `other` | -This is a telltale sign of a developer who hasn't thought carefully about user experience. +To list a language's categories, run `new Intl.PluralRules('ru').resolvedOptions().pluralCategories` in a browser console. For Russian, it returns `one`, `few`, `many`, and `other`. -React apps often need to handle pluralization — for notification counts, list lengths, or search results. -**And it's not that hard to get pluralization right, especially if you only need your app in English.** -But there are plenty of bad practices new developers fall into, especially when building multilingual interfaces. +## How do I handle plurals in a multilingual React app? -This guide covers how to handle English plurals cleanly, format numbers for different locales, and build a fully multilingual plural system in React and Next.js. +General Translation handles plurals in React with the [``](/docs/react/reference/components/plural) component from `gt-react`, or `gt-next` in Next.js. You write the forms your source language uses, and General Translation generates the forms each target language needs. -### The problem with hard-coded plurals +1. Pass the count to [``](/docs/react/reference/components/plural) as `n`, with one prop per source form, such as `one` and `other` for English. +2. Wrap the count in [``](/docs/react/reference/components/num) and the whole [``](/docs/react/reference/components/plural) in [``](/docs/react/reference/components/t), so each form is translated as a full sentence with the number as a variable. +3. Run [`npx gt translate`](/docs/cli/reference/commands/translate) before your production build. General Translation translates each form and adds the categories each target language uses, so Arabic gets six forms and Russian gets four. -Many projects — including surprisingly large and important ones — hard-code plural logic. +```tsx +import { T, Plural, Num } from 'gt-react'; -```jsx -export default function Example({ n }) { +export function InboxCount({ count }: { count: number }) { return ( -

- Displaying {n} item{n === 1 ? '' : 's'} -

- ) + + You have {count} message.} + other={<>You have {count} messages.} + /> + + ); } ``` -But pluralization is often more complex than just adding an "s" to the end of a word. -Some nouns have irregular plural forms, like "child" and "children". -Sometimes, other parts of the sentence also need to change to reflect the noun, like -"is" and "are" depending on the count. - -The table below illustrates some common scenarios: - -| Scenario | Examples | Notes | -| -------------- | ----------------------------------------------------------- | ------------------------------------------------------------------- | -| Viewer count | "1 person is watching"
"2 people are watching" | Irregular noun ("person" → "people") and verb changes needed. | -| Item deletion | "Delete this message?"
"Delete these 2 messages?" | Demonstrative changes ("this" vs. "these") plus noun pluralization. | -| Search results | "No results"
"1 result found"
"2 results found" | Different phrasing for zero, one, and multiple results. | +At render, General Translation's [``](/docs/react/reference/components/plural) picks the form for `count` with the active locale's [CLDR plural rules](https://cldr.unicode.org/index/cldr-spec/plural-rules). If that category is missing, it uses `plural`, then `other`, then its children. [``](/docs/react/reference/components/num) formats the count locally with `Intl.NumberFormat`, so 1000000 renders as 1,000,000 in English, 1.000.000 in German, and 10,00,000 in Hindi, and the value is never sent to the General Translation API. -Using conditional expressions quickly becomes unwieldy. +To keep plural messages translated as copy changes, run [`npx gt translate`](/docs/cli/reference/commands/translate) in CI, or [configure Locadex](/docs/platform/locadex/guides/configuring-automations), General Translation's AI agent, to translate new source content when a pull request changes or a commit is pushed. Reviewers can correct any generated form in the [Translation Editor](/docs/platform/dashboard/guides/reviewing-translations) in General Translation's Dashboard. [Handling plurals and branches](/docs/react/guides/handling-plurals-and-branches) has the steps for React, Next.js, and the experimental `gt-tanstack-start` and `gt-react-native`. -And it becomes a nightmare when you need to ship your app in other languages. -What works for English often breaks completely in languages like Polish or Arabic, which have entirely different rules for handling quantities. -**Companies we work with often put off internationalization -because of the pain of refactoring hardcoded UI like this.** - ---- +## How do I pluralize a string in an English-only React app? -## How to handle plurals in English +An app that ships only in English needs two forms, `one` and `other`, so a small helper is enough. Pass complete phrases, so irregular nouns ("person" and "people") and verbs ("is" and "are") change with the count. -In English, using the right plurals in your app is usually straightforward. +```ts title="pluralize.ts" +const englishPlurals = new Intl.PluralRules('en-US'); -For simple noun pluralization, write a utility function: - -```jsx -export function pluralize( - count: number, - singular: string, - plural: string = singular + 's' -) { - return `${count === 1 ? singular : plural}`; +export function pluralize(count: number, one: string, other: string): string { + return englishPlurals.select(count) === 'one' ? one : other; } ``` -Now there is a single function for handling all your plural logic, -and it works for irregular plurals too: - -```js -pluralize(2, 'user') // "users" -pluralize(2, 'person', 'people') // "people" -pluralize(2, 'child', 'children') // "children" -``` +```tsx +import { pluralize } from './pluralize'; -But what if you need more complicated logic, like: - -```text -"No one is watching" -"1 person is watching" -"2 people are watching" +export function CartSummary({ items, viewers }: { items: number; viewers: number }) { + return ( + <> + {/* 1 item, 5 items */} +

{pluralize(items, `${items} item`, `${items} items`)}

+ {/* 1 person is watching, 3 people are watching */} +

{pluralize(viewers, '1 person is watching', `${viewers} people are watching`)}

+ + ); +} ``` -At this stage, you should think seriously about a low-maintenance internationalization ("i18n") library. +English rules put 0 in `other`, so the helper renders "0 items". The helper hard-codes English rules and takes two phrases, while Russian needs three, so replace it with [``](/docs/react/reference/components/plural) or another internationalization (i18n) library when the app adds a second language. -Although developers often think that i18n libraries are only for multilingual interfaces, -they can be very useful for plural and variable formatting even in single-language applications. -Under the hood, most i18n libraries use JavaScript's built-in `Intl.PluralRules` API to determine the correct plural form for any language. +## How do I pluralize a string outside JSX? -There are plenty of React i18n libraries, including ours, [`gt-react`](/docs/react/overview) (or [`gt-next`](/docs/react/nextjs-quickstart) if you're using Next.js). -Displaying an English plural using gt-react is simple: +For text that has to be a plain string, such as a `placeholder` or an `aria-label`, General Translation's [`useGT`](/docs/react/reference/hooks/use-gt) hook returns a function that accepts an ICU (International Components for Unicode) plural message. Inside the message, `#` prints the count. -```jsx -import { Plural } from 'gt-react' +```tsx +import { useGT } from 'gt-react'; -function Example({ count }) { +export function ProductSearch({ count }: { count: number }) { + const gt = useGT(); return ( - - {count} people are watching - - ) + + ); } ``` -UI is conditionally rendered based on the value of `n`. - -Most libraries use JavaScript's [`Intl.PluralRules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules) API to decide which plural form to display. -This means that in English, you would use the name `"one"` to refer to a singular and `"other"` to refer to a plural. -Our [``](/docs/react/reference/components/plural) component checks the exact plural category first, then the generic `plural` and `other` props. It falls back to its children only when none of those props are available. - -Using a library here is best practice even for English-only applications, and makes future internationalization much easier. - ---- - -## Pluralization in multilingual React apps (i18n) - -If your app only serves English-speaking users, the approach above may be all you need — but most production apps eventually need to support multiple languages. That's where things get interesting. - -- In Arabic, nouns have different forms depending on whether there are zero, one, two, or many of them -- In locales such as `de-DE`, `es-ES`, and `it-IT`, large numbers use periods instead of commas, so 1,000,000 becomes 1.000.000 -- In Hindi, digits are grouped in pairs, so 1,000,000 would become 10,00,000 +In async App Router components, use [`getGT`](/docs/react/nextjs/reference/functions/get-gt) from `gt-next/server` instead. [Translating strings](/docs/react/guides/translating-strings) covers the rest of the string API. -**For an internationalized app, you should use a dedicated i18n library** which will have its own documentation on how to handle plurals and number formatting. +## How do react-i18next and react-intl handle plurals? -### Formatting numbers for different locales +Both libraries pick the category with CLDR plural rules and read each language's sentences from a catalog file, so your team or your translators write every form of every message in every language. Neither library generates the translations. -You can use the `Intl` object to [format numbers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) for any locale. -The easiest way to do this is with the built-in `toLocaleString()` method. +[react-i18next](https://react.i18next.com/) stores one key per category, with the category as a suffix, and picks the key from the `count` option. -By default, this will use the runtime's current locale: - -```javascript -const n = 1000000 -n.toLocaleString() // displays 1,000,000 when the runtime locale is "en-US" (American English) -n.toLocaleString('de') // 1.000.000 because the locale has been specified as "de" (German) +```json title="locales/en/translation.json" +{ + "messages_one": "You have {{count}} message.", + "messages_other": "You have {{count}} messages." +} ``` -gt-react also offers a [``](/docs/react/reference/components/num) component which relies on `Intl.NumberFormat` under the hood. - -```jsx -import { Num } from 'gt-react' +```tsx +import { useTranslation } from 'react-i18next'; -// displays 1,000,000 when the language is "en-US" -// displays 1.000.000 when the language is "de" -// displays 10,00,000 when the language is "hi" -export default function Example() { - return 1000000 +export function InboxCount({ count }: { count: number }) { + const { t } = useTranslation(); + return

{t('messages', { count })}

; } ``` -### Understanding plural forms across languages +An Arabic file needs six keys, from `messages_zero` to `messages_other`. The [i18next plurals docs](https://www.i18next.com/translation-function/plurals) list every suffix. -The six plural forms supported by JavaScript's `Intl.PluralRules` API are: `zero`, `one`, `two`, `few`, `many`, `other`. -Although English uses only `one` ("singular") and `other` ("plural"), -languages like Arabic and Polish have more than just these two forms. +[react-intl](https://formatjs.github.io/docs/react-intl/), FormatJS's React library, writes all the categories inside one ICU MessageFormat string, where `#` is the count formatted for the locale. -For example, an English-speaking user might expect: +```tsx +import { FormattedMessage } from 'react-intl'; -```text -"No one is watching" -"1 person is watching" -"2 people are watching" -``` - -Whereas an Arabic-speaking user might expect different expressions for singular, -dual (when the count is exactly two things), and small and large plural forms: - -```text -"لا أحد يشاهد" -"1 شخص يشاهد" -"2 شخصان يشاهدان" -"3 أشخاص يشاهدون" -"11 شخصاً يشاهدون" +export function InboxCount({ count }: { count: number }) { + return ( + + ); +} ``` -This is where an internationalization library becomes essential. -Every language has its own logic for when and how to display plurals, -so it's better to rely on a dedicated library to get it right. +An Arabic catalog repeats the message with `zero`, `one`, `two`, `few`, `many`, and `other` branches. [FormatJS's ICU syntax guide](https://formatjs.github.io/docs/core-concepts/icu-syntax/) covers plural branches and exact matches such as `=0`. -A good i18n library will do two things: +## Which approach should you use for plurals in React? -1. Decide which plural form (`one`, `many`, `other`, etc.) to use based on the locale -2. Locate the translation in the right language which corresponds to that form +| Approach | Where the forms live | Who writes each target language's forms | +| --- | --- | --- | +| An `Intl.PluralRules` helper | Your code, in English only | Nobody, since the app ships in one language | +| react-i18next | Suffixed keys in one JSON file per language | Your team or translators | +| react-intl | ICU messages in one catalog per language | Your team or translators | +| General Translation's [``](/docs/react/reference/components/plural) | Your source-language forms, in JSX | General Translation, when [`npx gt translate`](/docs/cli/reference/commands/translate) runs | -**If you already have an internationalization library, -check their docs for information on plural formatting.** -Almost all libraries have dedicated documentation on rendering plurals. +Use a helper when the app ships only in English. Use react-i18next or react-intl alone when your team already keeps catalog files and translators write every form by hand. Use General Translation's [``](/docs/react/reference/components/plural) when the app ships in several languages and its copy changes every release, since General Translation writes every target language's forms for each new plural message. -### Full example: plurals in a multilingual React app +## Common questions -If you don't already have an internationalization library, consider [`gt-react`](/docs/react/react-quickstart)! +### How do I show "No items" when the count is 0? -gt-react's [``](/docs/react/reference/components/plural) component: +English rules put 0 in `other`, so a message with only `one` and `other` reads "0 items". In a string, add an exact match for zero to the ICU message you pass to [`useGT`](/docs/react/reference/hooks/use-gt): -- Is a simple, declarative way to render plurals correctly in React -- Works natively with the [``](/docs/react/reference/components/num) formatting component -- Works natively with the [``](/docs/react/reference/components/t) translation component, which integrates with our [translation service](https://generaltranslation.com) to generate plural forms automatically - -Putting all the building blocks together, here's a complete multilingual component: +```tsx +gt('{count, plural, =0 {No items} one {# item} other {# items}}', { count }); +``` -```jsx -import { T, Plural, Num } from 'gt-react' +In JSX, check `count === 0` and render the empty state in its own [``](/docs/react/reference/components/t) block. -// Works out of the box in 100+ languages -function Example({ count }) { - return ( - - - {count} person is watching - - } - > - {count} people are watching - - - ) -} -``` +### Does the Plural component work in Next.js? ---- +Yes. Import [``](/docs/react/reference/components/plural), [``](/docs/react/reference/components/t), and [``](/docs/react/reference/components/num) from `gt-next`, with the same props as in `gt-react`. General Translation also ships them in the experimental `gt-tanstack-start` and `gt-react-native`. -Set up pluralization with the [`gt-react` Quickstart](/docs/react/react-quickstart) for React apps or the [`gt-next` Quickstart](/docs/react/nextjs-quickstart) for Next.js apps. See the [`` API reference](/docs/react/reference/components/plural) for the full component API. +### Which counts should I test plurals with? -Pluralization is one of the most common i18n challenges in React — getting it right from the start saves significant refactoring later. +Render each plural message at 0, 1, 2, 5, 11, 21, and 100 in every target locale. Those counts reach every whole-number category in English, Russian, Polish, and Arabic. Add 1.5 to reach `other` in Russian and Polish. diff --git a/kb/en-US/i18n-without-translation-files.mdx b/kb/en-US/i18n-without-translation-files.mdx index 9b0d0cde3..ffdd9e1ce 100644 --- a/kb/en-US/i18n-without-translation-files.mdx +++ b/kb/en-US/i18n-without-translation-files.mdx @@ -1,84 +1,147 @@ --- title: Can I do i18n without translation files? -summary: Yes. With gt-react or gt-next, content stays inline in your components inside a component, and translations are generated at build time as output, so there are no keys or per-locale JSON files to write and keep in sync. -description: Yes. With gt-react or gt-next, content stays inline in a component and translations are generated at build time, with no keys or per-locale JSON files to keep in sync. +summary: "Yes. General Translation's gt-react and gt-next libraries do React and Next.js i18n with no translation keys and no JSON file per language to maintain. Copy stays inline in JSX inside the component, and the gt translate command generates each language's translations in your build, with context from your code. Locadex can keep them current through pull requests." +description: Yes. With General Translation's gt-react and gt-next, copy stays inline in JSX inside , and gt translate generates each language's translations in your build, with no keys or per-locale JSON files. date: 2026-05-07 +updated: 2026-10-21 category: internationalization authors: [jackie, ernest] redirectFrom: [i18n-without-translation-files, nextjs-i18n-code-is-the-source-of-truth] -related: [translate-conditional-jsx, how-to-handle-plurals-in-react, best-localization-software] +related: [add-i18n-to-nextjs, ai-agent-i18n-setup, how-to-handle-plurals-in-react, translations-in-ci] --- -Everyone who's internationalized a JavaScript app knows the workflow. You install an i18n library, create an `en.json` file, pull every user-facing string out of your components, assign each one a key, and reference the key where the string used to be. Then you duplicate that JSON file for every language you support. `es.json`, `fr.json`, `ja.json`. +Yes. General Translation's open-source `gt-react` and `gt-next` libraries let you internationalize (i18n) a React or Next.js app without translation keys and without writing a JSON file for each language. You write copy inline in your JSX inside the [``](/docs/react/reference/components/t) component, and [`npx gt translate`](/docs/cli/reference/commands/translate) generates the translations for every language when you build. -At first, it's fine. Thirty strings, three languages, 90 entries. +Key-based libraries such as i18next, react-intl, and next-intl look up each string by key in a message file per language, so every new string needs a key in every file. A key added to `en.json` but missed in `ja.json` leaves one line of your Japanese checkout page untranslated. -Then your app grows. Six months later you have 400 strings and 12 languages. 4,800 entries across a dozen files. A developer adds a new feature, writes five new strings, forgets to update three of the translation files. Nobody notices until a user in Tokyo sees English fallbacks in a Japanese interface. Someone suggests buying a translation management system. +Translation files still exist with General Translation, but as build output. The CLI writes one generated file per language and can publish the translations to General Translation's CDN. No one edits those files by hand or copies keys between them. -