diff --git a/kb/best-react-i18n-library.mdx b/kb/best-react-i18n-library.mdx new file mode 100644 index 000000000..afea52582 --- /dev/null +++ b/kb/best-react-i18n-library.mdx @@ -0,0 +1,142 @@ +--- +title: What's the best i18n library for a React app? +summary: The four main React i18n libraries are General Translation's gt-react, react-i18next, Lingui, and React Intl. All four render and format translations, but only gt-react also generates them, from copy written inline in your JSX. Choose react-i18next to keep i18next catalogs and plugins, Lingui for extraction to compiled catalogs, and React Intl for FormatJS and ICU MessageFormat. +date: 2026-10-16 +category: workflow-tools +authors: [jackie] +related: [general-translation-vs-i18n-libraries, multilingual-app-launch-checklist, i18n-without-translation-files, how-to-handle-plurals-in-react] +--- + +The best React i18n library depends on where you want copy to live and who produces the translations. gt-react keeps copy inline in JSX and is the only option here that also generates translations. react-i18next, Lingui, and React Intl render translations you supply in catalog files, each with a different authoring style and toolchain. + +Start with your rendering model. A browser-only React app, a server-rendered React app, and a Next.js App Router app have different setup requirements, and each library documents them separately. + +## How do the main React i18n libraries compare? + +| Library | How you write copy | Translates your content? | Plurals | Server rendering | Choose it when | +| --- | --- | --- | --- | --- | --- | +| gt-react (General Translation) | Inline JSX in [``](/docs/react/reference/components/t), strings with [`useGT`](/docs/react/reference/hooks/use-gt), optional automatic wrapping, or keyed dictionaries | Yes, with [`gt translate`](/docs/cli/reference/commands/translate) | [``](/docs/react/reference/components/plural) branches | [SSR setup](/docs/react/react-quickstart); [gt-next](/docs/react/nextjs-quickstart) for the App Router | You want readable copy in components and translations generated on each build | +| react-i18next | Keys in JSON resources, with `Trans` for rich text | No. Use your team or a service such as Locize | One key per plural form | [SSR guide](https://react.i18next.com/latest/ssr) | You already use i18next or need its plugins | +| Lingui | Inline macros or explicit IDs, extracted to PO catalogs | No. Catalogs go to translators or a service | ICU MessageFormat | [RSC integration](https://lingui.dev/tutorials/react-rsc) | You want an extract-and-compile catalog workflow | +| React Intl (FormatJS) | Message descriptors with IDs and default messages | No. You supply translated catalogs | ICU MessageFormat | Check your framework's integration | Your team already works with FormatJS and ICU | + +## What does the same button look like in react-i18next and gt-react? + +With react-i18next, the copy lives in a resource file: + +```json title="locales/en/translation.json" +{ + "settings": { + "save": "Save changes" + } +} +``` + +The component looks it up by key: + +```tsx +import { useTranslation } from 'react-i18next'; + +export function SaveButton() { + const { t } = useTranslation(); + return ; +} +``` + +Every other language needs its own resource file, written by hand or sent out for translation. + +With gt-react, the copy stays in the component: + +```tsx +import { T } from 'gt-react'; + +export function SaveButton() { + return ( + + + + ); +} +``` + +Running [`gt translate`](/docs/cli/reference/commands/translate) before your build sends each [``](/docs/react/reference/components/t) block, with its element structure, to General Translation and writes a translation for every target locale. A copy edit shows up in the component diff, and the next run translates it again. + +## 1. gt-react: copy in JSX, translations generated + +gt-react keeps source copy in the component. Wrap JSX in [``](/docs/react/reference/components/t), and use [`useGT`](/docs/react/reference/hooks/use-gt) where an API needs a string, such as an input's `placeholder`. For rich text, keep the sentence and its inline elements in one [``](/docs/react/reference/components/t) block so they're translated together, and add a [`context` prop](/docs/react/reference/components/t#context) when the wording alone is ambiguous. + +For an app with a lot of unmarked JSX, [automatic JSX injection](/docs/cli/guides/using-auto-jsx) adds the translation wrappers for you at build time. Set `enableAutoJsxInjection` under `files.gt.parsingFlags` in `gt.config.json` and add `@generaltranslation/compiler` to your bundler. It works in React SPAs, server-rendered React apps, and Next.js webpack builds, and leaves existing [``](/docs/react/reference/components/t) blocks alone. Check the extracted text, dynamic values, and rich-text grouping on a few screens before enabling it across the app. + +gt-react is [MIT-licensed](https://github.com/generaltranslation/gt) and built on General Translation's own ICU and formatting packages, not on another i18n library. It renders [bundled translations](/docs/react/guides/storing-translations) without a General Translation account or runtime requests. When General Translation generates the translations, reviewers can correct them in the [Translation Editor](/docs/platform/dashboard/guides/reviewing-translations). + +Setup differs by rendering model: use the [React SPA quickstart](/docs/react/react-spa-quickstart) for browser-only apps, the [React quickstart](/docs/react/react-quickstart) for server-rendered apps, and [gt-next](/docs/react/nextjs-quickstart) for Next.js. + +**Choose gt-react** when you want copy reviewable in component diffs, translations generated as part of your build, or automatic wrapping to localize an existing app quickly. + +## 2. react-i18next: the i18next ecosystem + +[react-i18next](https://react.i18next.com/) connects React to i18next, the most widely used JavaScript i18n framework. Its hooks render messages from JSON resources, and its `Trans` component handles text that contains React elements. + +The [i18next plugin ecosystem](https://www.i18next.com/overview/plugins-and-utils) includes translation-loading backends, language detectors, and extraction tools, and the official CLI handles extraction, locale synchronization, and TypeScript type generation. i18next also supports [source text as keys](https://www.i18next.com/principles/fallback), though its documentation warns this makes copy changes harder to coordinate with translations. + +react-i18next doesn't translate your resources. Translations come from your team or a service; the i18next team documents a [Locize workflow](https://react.i18next.com/guides/multiple-translation-files). The `gt` CLI can also [translate react-i18next JSON files](/docs/cli/guides/generating-translations) without a migration. + +**Choose react-i18next** when you already have i18next resources, or need its plugins for loading and language detection. + +## 3. Lingui: extract and compile catalogs + +[Lingui](https://lingui.dev/introduction) lets you write messages inline with macros, extract them into catalogs, translate them, and compile the result for the app. It supports generated and explicit message IDs, with PO as the default catalog format. Its [React tutorial](https://lingui.dev/tutorials/react) covers the full cycle, and Server Component apps have a [dedicated integration](https://lingui.dev/tutorials/react-rsc). + +Lingui keeps copy near the UI, but your team owns every step after extraction: translation, review, compilation, and loading. Extraction finds new messages; it doesn't translate them. + +**Choose Lingui** when your translators or translation service work with PO catalogs and you want Lingui's extraction and compiler tooling. + +## 4. React Intl: FormatJS and ICU MessageFormat + +[React Intl](https://formatjs.github.io/docs/react-intl/) is FormatJS's React library for messages, plurals, dates, and numbers. [Message descriptors](https://formatjs.github.io/docs/react-intl/api/) identify each message by ID, with an optional default message and a description for translators. [FormatJS extraction](https://formatjs.github.io/docs/getting-started/message-extraction/) collects them and can generate the IDs. + +Messages use [ICU MessageFormat](https://formatjs.github.io/docs/core-concepts/icu-syntax/), so reviewers need to read its placeholder and plural syntax, and your pipeline supplies the translated catalogs. Server rendering and client boundaries vary by framework, so check your framework's integration before adopting it. + +**Choose React Intl** when your team already uses FormatJS or wants ICU MessageFormat throughout. + +## How should you test a React i18n library before choosing? + +Pick one feature with a heading, a named variable, a plural count, an accessible input label, and a formatted price. Localize it into two languages with each candidate, then change a source sentence and add a new message. Compare: + +1. **Authoring:** Can a reviewer understand the copy and its context in the diff? +2. **Translation:** How does new or changed text reach whoever translates it? +3. **Verification:** Can you catch missing messages and broken variables before release? +4. **Delivery:** Which files or services must be available when the production app renders? + +If your current library wins, keep it. The `gt` CLI translates react-i18next catalogs too, so choosing how to author copy doesn't lock in how it gets translated. Before you ship, run the [multilingual app launch checklist](/kb/multilingual-app-launch-checklist). + +General Translation is used by developer teams at Cursor, Ramp, Profound, Cognition, Sierra, and ClickHouse. To try it, start with the [React quickstart](/docs/react/react-quickstart). + +## Common questions + +### Which React i18n library translates my app automatically? + +gt-react. react-i18next, Lingui, and React Intl render translations you supply. With gt-react, [`gt translate`](/docs/cli/reference/commands/translate) generates translations for every target locale using your code as context. + +### Is react-i18next or React Intl better? + +Neither is better in general. react-i18next suits teams that want JSON resources and i18next's plugins. React Intl suits teams standardized on FormatJS and ICU MessageFormat. Both leave translation to your team or a separate service. + +### Which React i18n library supports Server Components? + +For Next.js App Router apps, use [gt-next](/docs/react/nextjs-quickstart), which supports Server and Client Components. Lingui has a [dedicated RSC integration](https://lingui.dev/tutorials/react-rsc), and next-intl is another App Router option. + +### Can I use General Translation without leaving react-i18next? + +Yes. The `gt` CLI detects react-i18next and translates its JSON resources while preserving their syntax. See [Generating translations](/docs/cli/guides/generating-translations). + +### Do I still need JSON translation files with gt-react? + +Not as source files. You write copy inline, and translations are generated as files in your build or served from General Translation's CDN. See [i18n without translation files](/kb/i18n-without-translation-files). + +## Key takeaways + +- gt-react, react-i18next, Lingui, and React Intl all render and format translations in React. Only gt-react also generates them. +- gt-react keeps copy inline in JSX and can wrap existing JSX automatically at build time. +- react-i18next, Lingui, and React Intl rely on catalogs that your team or a translation service fills in. +- You can keep react-i18next and still use General Translation to translate its resources. diff --git a/kb/multilingual-app-launch-checklist.mdx b/kb/multilingual-app-launch-checklist.mdx new file mode 100644 index 000000000..e4f4010de --- /dev/null +++ b/kb/multilingual-app-launch-checklist.mdx @@ -0,0 +1,145 @@ +--- +title: What should I check before launching a multilingual app? +summary: "Before launching a multilingual app, test nine things in every supported locale: complete user journeys including errors, language detection and switching, variables and plurals, currency and time zones, text expansion, right-to-left layout, accessibility, production translation delivery, and crawlable language URLs for SEO. Each check needs an explicit pass condition." +date: 2026-10-16 +category: internationalization +authors: [jackie] +related: [best-react-i18n-library, i18n-without-translation-files, how-to-handle-plurals-in-react, multilingual-nextjs-seo] +--- + +A translated homepage can look finished while the checkout, validation errors, or password-reset flow still uses the source language. Start with the paths a user needs to complete, and test those paths in the production build. + +Before launching a multilingual web app, check these nine things in every supported locale: + +1. **Whole journeys are translated**, including loading, empty, and error states, emails, and notifications. +2. **Language selection is consistent** on first visits, after switching, and on return visits. +3. **Variables and plurals are correct** for every plural category your languages use. +4. **Currency and time zone are set explicitly**, separately from the UI language. +5. **Layouts survive text expansion** on narrow screens and with browser zoom. +6. **Right-to-left languages** have the correct direction, layout, and mixed-direction text. +7. **Assistive technology gets the right language**, labels, and accessible names. +8. **The production build ships complete translations**, and new copy gets translated before release. +9. **Public pages have crawlable language URLs** with translated metadata and `hreflang`. + +Test content in every supported locale, and test layout and interaction on representative devices and browsers. Check 9 applies only to public pages you want indexed. + +## Check content and language behavior + +### 1. Translate the whole journey, including failure states + +Choose a critical journey such as creating an account or completing a purchase. Exercise its loading, empty, success, and error states. Check buttons, dialogs, form validation, placeholders, accessible labels, and any email or notification the journey sends. + +**Pass condition:** Each surface uses reviewed target-language content, apart from intentional exceptions such as a product name. No raw keys, missing-message markers, or unexplained source-language fallbacks appear. + +**With General Translation:** Confirm that the output of [`gt translate`](/docs/cli/reference/commands/translate) is the version included in the release. ([`gt generate`](/docs/cli/reference/commands/generate) only writes source templates, which still need translated text.) Reviewers correct wording in the [Dashboard Translation Editor](/docs/platform/dashboard/guides/reviewing-translations); if your team edits the generated files in the repository instead, run [`gt save-local`](/docs/cli/reference/commands/save-local) so later runs keep those edits. To keep product terms consistent across your app, docs, and notifications, assign shared Glossary terms and Custom Prompts with [Context Groups](/docs/platform/dashboard/guides/defining-context-for-translations) before generating translations. Context changes apply to new translations; they don't rewrite existing ones. + +### 2. Verify first visits, switching, and return visits + +Test a fresh browser, an explicit language choice, a reload, a deep link, and an unsupported browser language. If the app stores a language preference on the account, also test sign-in and sign-out. Write down which setting wins when the URL, saved preference, and browser disagree. + +**Pass condition:** The app follows that policy consistently, and a user can recover from an unwanted language choice. For server-rendered apps, inspect the initial response as well as the hydrated page. + +Locale identifiers follow BCP 47: `es-MX` and `es-AR` can be separate targets for Mexican and Argentine Spanish. Test each regional target you ship. A cookie-based switcher and locale-prefixed routes serve different needs; check the app's actual URL behavior. + +**With General Translation:** Define supported languages, including regional targets, in your [locale configuration](/docs/react/guides/managing-locales). + +### 3. Exercise variables and plural categories + +Test counts including 0, 1, 2, and larger values, then add cases for your target languages' plural categories. Include names with punctuation, long account names, and sentences whose variables can appear in a different order after translation. Avoid building a sentence by concatenating independently translated fragments. + +**Pass condition:** Values remain intact, the sentence reads correctly, and every required plural form has usable content. Unicode's [plural rules](https://cldr.unicode.org/index/cldr-spec/plural-rules) describe why one English singular/plural test pair is insufficient. + +**With General Translation:** [``](/docs/react/reference/components/plural) follows the locale's plural rules: English has two cardinal categories, while Arabic has six. Use [``](/docs/react/reference/components/branch) when wording depends on an explicit value such as a person's grammatical gender, and [``](/docs/react/reference/components/var) for dynamic text such as a name. Values inside that variable wrapper are not sent to the translation API. The [React pluralization article](/kb/how-to-handle-plurals-in-react) covers the implementation pattern. + +## Check formatting, layout, and accessibility + +### 4. Separate language, currency, and time zone + +Test the same amount and instant with multiple locales. Set the intended currency and time zone explicitly where your product requires them. A language preference alone does not tell you which currency a customer pays in or which time zone an appointment uses. + +For example, this formats an existing USD amount using German conventions: + +```js +new Intl.NumberFormat('de-DE', { + style: 'currency', + currency: 'USD', +}).format(1234.5); +``` + +It formats the supplied amount without converting currencies. Formatting can round displayed digits, so check the precision your product requires. Likewise, use an explicit time zone when showing an instant in a chosen location: + +```js +new Intl.DateTimeFormat('en-GB', { + dateStyle: 'medium', + timeStyle: 'short', + timeZone: 'Europe/Paris', +}).format(new Date('2026-01-15T12:00:00Z')); +``` + +**Pass condition:** Separators, currency identity, calendar date, and displayed time match the product's intended meaning. Include a daylight-saving transition when your supported time zones observe one. The [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) and [date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat) APIs expose these choices separately. + +**With General Translation:** [``](/docs/react/reference/components/currency), [``](/docs/react/reference/components/datetime), and [``](/docs/react/reference/components/num) format values using the active locale. Set the currency and time zone for the business meaning you intend; changing the UI language should not change the amount owed or the instant an event occurs. + +### 5. Test text expansion across screen sizes + +Open translated screens on narrow viewports and with browser zoom. Check navigation, buttons, tables, dialogs, and validation messages. Include long translated strings and long user-provided values; a short heading doesn't stress the same layout as a full address. + +**Pass condition:** Essential text stays readable and controls remain usable. No action disappears behind clipped text or overlapping elements. Review truncation deliberately instead of using it to hide every overflow. + +[Pseudolocalization](https://lingui.dev/guides/pseudolocalization) can help expose layout and unmarked-string problems before translations are ready. It supplements testing real languages and does not establish translation quality. + +### 6. Test right-to-left content as a layout requirement + +If you support Arabic, Hebrew, or another right-to-left language, test the document direction, navigation order, forms, and mixed-direction content such as email addresses and order IDs. Use logical CSS properties where layout should follow text direction, and review directional icons individually. + +**Pass condition:** The layout follows the intended reading direction while embedded left-to-right values remain readable. Setting a language code does not set text direction; follow the W3C's [HTML direction guidance](https://www.w3.org/International/questions/qa-html-dir). + +### 7. Check the language that assistive technology receives + +Verify the page's `lang` attribute, language changes within content where applicable, visible labels, accessible names, and alternative text. Try keyboard navigation and a screen reader on the main journey. A translated placeholder cannot replace a persistent form label. + +**Pass condition:** The document declares the correct language, controls have meaningful localized names, and the translated layout remains operable. The W3C explains [language declarations](https://www.w3.org/International/questions/qa-html-language-declarations), and its [form-label guidance](https://www.w3.org/WAI/tutorials/forms/labels/) covers label associations. + +## Check the release that users will receive + +### 8. Verify production assets and translation updates + +Build from a clean checkout using the release process, then run that build with each supported locale. Confirm that translation assets are present and load successfully. Change a source message in a test branch and verify that the translation pipeline notices it before the next release. + +**Pass condition:** The app serves the intended translations without relying on files left over from development. Missing assets or failed generation cause a visible failure or an explicitly accepted fallback, rather than a silent partial release. + +**With General Translation:** Run [`gt validate`](/docs/cli/reference/commands/validate) to catch supported inline-translation errors before an API call, then put the [incremental translation step before the build](/docs/cli/guides/generating-translations). It reuses earlier translations and translates changed content, so new copy can ship translated in the release that adds it. [Branch tracking](/docs/cli/guides/branching) keeps parallel development branches separate. [Locadex automations](/docs/platform/locadex/guides/configuring-automations) can handle translation updates through pull requests. Test the configured trigger with a source-copy change as part of this release check. + +With the [local storage workflow](/docs/react/guides/storing-translations), the app serves bundled translations without runtime calls to General Translation; updates ship in a new build. With CDN delivery, published edits can reach the app without a redeploy, subject to its translation version and cache settings. Test that update path. Keep private translation tooling credentials in the build environment, outside browser bundles. + +### 9. Verify discovery for public language versions + +For pages intended for search, check that each language version has a crawlable URL, translated titles and descriptions, the intended canonical URL, and valid language alternates. Check reciprocal `hreflang` references, including the current page. If alternates are declared in a sitemap, verify that it represents the same page relationships. + +**Pass condition:** Each public language URL loads the expected content, is indexable when intended, and points to real alternate versions. Google's [localized-page guidance](https://developers.google.com/search/docs/specialty/international/localized-versions) describes the supported annotation methods. For a Next.js implementation, use the [multilingual SEO guide](/kb/multilingual-nextjs-seo). + +Path prefixes, language subdomains, and country domains can provide separate language URLs. An `Accept-Language` header or cookie can select a visitor's language, but does not by itself create a crawlable URL for each version. Google's [multilingual-site guidance](https://developers.google.com/search/docs/specialty/international/managing-multi-regional-sites) recommends separate URLs. An authenticated dashboard may not need indexed language URLs; choose the routing policy for the product's audience. + +## Keep a release record + +Record the build, locale, journey, browser or device, expected result, actual result, and reviewer. Assign an owner to failures. Treat a missing checkout translation or unreadable primary action differently from a minor wording preference, and make any accepted fallback explicit. + +For a quick first pass on a public site, General Translation's free [Report Card](https://generaltranslation.com/report-card) scores its localization. It can't see authenticated flows or failure states, so pair it with the journey checks above. + +## Common questions + +### How do you test a multilingual app? + +Run complete user journeys in every supported locale on a production build, including error states, emails, and notifications. Check plurals, variables, currency, time zones, layout, right-to-left text, and accessibility, and record a pass condition for each check. + +### What is the most commonly missed localization bug? + +Untranslated failure states. Error messages, form validation, empty states, and transactional emails are often still in the source language after the main screens are translated. Test them deliberately in check 1. + +### What is pseudolocalization? + +Pseudolocalization replaces source text with longer, accented placeholder text so you can find layout overflow and unmarked strings before real translations exist. It doesn't replace testing real languages. See [Lingui's pseudolocalization guide](https://lingui.dev/guides/pseudolocalization). + +### Do multilingual pages need separate URLs for SEO? + +Yes, for pages you want indexed. Google recommends a [separate URL for each language version](https://developers.google.com/search/docs/specialty/international/managing-multi-regional-sites), linked with `hreflang`. A cookie or `Accept-Language` header alone doesn't create crawlable versions. For Next.js, see [multilingual Next.js SEO](/kb/multilingual-nextjs-seo).