diff --git a/kb/general-translation-vs-django-i18n.mdx b/kb/general-translation-vs-django-i18n.mdx index d439ff355..689a873b1 100644 --- a/kb/general-translation-vs-django-i18n.mdx +++ b/kb/general-translation-vs-django-i18n.mdx @@ -1,6 +1,6 @@ --- title: "General Translation vs. Django's translation framework: do you need both?" -summary: Django's translation framework marks strings in code and templates, extracts them into PO files, and serves the right language on each request, but every translation is yours to write. General Translation has no Django SDK, so it doesn't replace the framework. Its CLI fills in Django's PO files, plural forms included, for every language. +summary: Django's translation framework extracts messages into PO files and serves the right language on each request. General Translation adds catalog generation with a Glossary, Custom Prompts, and Dashboard review. Keep Django's extraction, compilation, and language selection; use GT's CLI to translate the PO files. date: 2026-10-16 category: internationalization authors: [kevin-wu, jackie] @@ -19,14 +19,14 @@ Django ships a complete internationalization (i18n) framework: you mark strings ## What Django leaves to you -[Django's own docs](https://docs.djangoproject.com/en/5.2/topics/i18n/translation/#compiling-message-files) describe the step it doesn't cover: once translators have filled in the message file, you compile it. Here's a template with a plural: +Django's docs pick up [after the translating is done](https://docs.djangoproject.com/en/5.2/topics/i18n/translation/#compiling-message-files): once someone has filled in the message file, you compile it. Filling it in is the step Django leaves to you. Here's a template with a plural: ```html title="shop/templates/shop/inbox.html" {% load i18n %}

{% blocktranslate count counter=count %}{{ counter }} file{% plural %}{{ counter }} files{% endblocktranslate %}

``` -With Django 5.2, `python manage.py makemessages -l ru` copies Russian's four-form plural rule from Django's own catalog into the header, but writes only two empty slots for the entry: +With Django 5.2 LTS, `python manage.py makemessages -l ru` copies Russian's four-form plural rule from Django's own catalog into the header, but writes only two empty slots for the entry: ```text title="locale/ru/LC_MESSAGES/django.po" "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " @@ -85,7 +85,7 @@ This is where the full stack helps. A Glossary and Custom Prompts in a [Context For database content and other text that only exists at run time, General Translation's API translates strings on request ([`POST /v2/translate`](/docs/platform/openapi/reference/translation/translate-runtime)); you store the results yourself, for example in django-modeltranslation's per-language fields. -The tradeoff: you add a Node.js step (`npx gt`) to a Python project, and there's no General Translation template tag, middleware, or model integration for Django. Django keeps doing all of that. General Translation is usage-based, with a $0 platform fee and unlimited users, projects, and languages on Starter ([pricing](https://generaltranslation.com/pricing)). +The tradeoff: you add a Node.js step (`npx gt`) to a Python project, and there's no General Translation template tag, middleware, or model integration for Django. Django keeps doing all of that. ## Django's translation framework vs. General Translation at a glance @@ -98,7 +98,7 @@ The tradeoff: you add a Node.js step (`npx gt`) to a Python project, and there's | Context for translation | `# Translators:` comments and `msgctxt` in the PO file | The same, plus Glossary and Custom Prompts | | Language per request and URL prefixes | Yes, `LocaleMiddleware` and `i18n_patterns` | No; keeps using Django's | | Database content | Third-party packages such as django-modeltranslation | Run-time API translation; you store the results | -| Cost | Free, open source ([BSD](https://docs.djangoproject.com/en/5.2/faq/general/#how-is-django-licensed)) | Usage-based, $0 platform fee on Starter | +| Cost | Free, open source ([BSD](https://docs.djangoproject.com/en/5.2/faq/general/#how-is-django-licensed)) | [Usage-based translation](https://generaltranslation.com/pricing) | ## When to use which diff --git a/kb/general-translation-vs-flask-babel.mdx b/kb/general-translation-vs-flask-babel.mdx index 69853a67c..1f5a736d5 100644 --- a/kb/general-translation-vs-flask-babel.mdx +++ b/kb/general-translation-vs-flask-babel.mdx @@ -96,9 +96,7 @@ t("You have {count, plural, one {# unread message} other {# unread messages}}.", t("Archive", _context="email verb") ``` -Both are at version 0.1.0, require Python 3.10 or later, and are marked experimental in the [Python docs](/docs/python/quickstart), with breaking changes possible between releases. They translate strings in Python code only: there are no Jinja template tags, and the docs cover no date, number, or currency formatting, so Babel stays the tool for those. They're source-available under the Functional Source License (FSL-1.1-ALv2), not BSD or MIT. For a production Flask app today, translate Flask-Babel's catalogs with the CLI, and treat `gt-flask` and `gt-fastapi` as early previews. - -General Translation is usage-based, with a $0 platform fee and unlimited users, projects, and languages on Starter ([pricing](https://generaltranslation.com/pricing)). +The Python SDKs require Python 3.10 or later and are marked experimental in the [Python docs](/docs/python/quickstart). They support inline strings in Python code; keep Flask-Babel for Jinja template translation and Babel for date, number, and currency formatting. The SDKs are source-available under the Functional Source License (FSL-1.1-ALv2). For an existing Flask-Babel app, GT's CLI can translate the PO catalogs while you retain your rendering and formatting setup. ## Babel and Flask-Babel vs. General Translation at a glance @@ -111,7 +109,7 @@ General Translation is usage-based, with a $0 platform fee and unlimited users, | Jinja templates | Yes | No template integration in `gt-flask` | | Frameworks | Babel anywhere; Flask-Babel for Flask | CLI for any PO files; `gt-flask` and `gt-fastapi` | | Maturity and license | Stable, BSD | CLI stable (MIT); Python libraries 0.1.0, experimental (FSL-1.1-ALv2) | -| Cost | Free | Usage-based, $0 platform fee on Starter | +| Cost | Free | [Usage-based translation](https://generaltranslation.com/pricing) | ## When to use which diff --git a/kb/general-translation-vs-gettext.mdx b/kb/general-translation-vs-gettext.mdx index 77b03f438..608b81da4 100644 --- a/kb/general-translation-vs-gettext.mdx +++ b/kb/general-translation-vs-gettext.mdx @@ -59,7 +59,7 @@ msgstr[1] "Masz %d nieprzeczytane wiadomości." msgstr[2] "Masz %d nieprzeczytanych wiadomości." ``` -`msgfmt` [leaves fuzzy entries out](https://www.gnu.org/software/gettext/manual/html_node/msgfmt-Invocation.html) (it reports "2 translated messages, 1 fuzzy translation"), so the same command now prints "You have 5 new messages." in English, in the middle of a Polish screen, until a translator reviews the entry and removes the flag. That's the right default, since the old Polish still says "unread", and it's also the ongoing cost of gettext: every new or changed string waits in every language for a person to translate or review it. +`msgfmt` [leaves fuzzy entries out](https://www.gnu.org/software/gettext/manual/html_node/msgfmt-Invocation.html) (with `--statistics`, it reports "2 translated messages, 1 fuzzy translation"), so the same command now prints "You have 5 new messages." in English, in the middle of a Polish screen, until the entry is updated and its fuzzy flag removed. That protects readers from the old Polish, which still says "unread". This example uses a manual workflow; gettext's local LLM tools can draft the update too. GT adds a managed generation and review workflow around the same catalogs. ## Translating gettext catalogs with General Translation @@ -104,7 +104,7 @@ When a `msgid` changes, General Translation translates the new text on the next What General Translation adds beyond filling in `msgstr` is the rest of the stack. A Glossary and Custom Prompts in a [Context Group](/docs/platform/dashboard/guides/defining-context-for-translations) keep terms and tone consistent across your PO files and every other surface you translate, reviewers can [check and edit](/docs/platform/dashboard/guides/reviewing-translations) each PO entry, with the target language's plural forms, in the Dashboard, and the CLI runs in CI so each release ships with current catalogs. -The tradeoffs: the CLI is a Node.js tool, so a C or PHP project adds `npx` to its build, and General Translation doesn't read or write `.mo` files, so `msgfmt` (or your platform's equivalent) still compiles the output. Translations are AI-generated, so review the ones that matter in the Dashboard; approval workflows require an Enterprise plan. Pricing is usage-based, with a $0 platform fee and unlimited users, projects, and languages on Starter ([pricing](https://generaltranslation.com/pricing)). +The tradeoffs: the CLI is a Node.js tool, so a C or PHP project adds `npx` to its build, and General Translation doesn't read or write `.mo` files, so `msgfmt` (or your platform's equivalent) still compiles the output. Translations are AI-generated, so review the ones that matter in the Dashboard; approval workflows require an Enterprise plan. ## GNU gettext vs. General Translation at a glance @@ -117,7 +117,7 @@ The tradeoffs: the CLI is a Node.js tool, so a C or PHP project adds `npx` to it | Plural forms | `msginit` writes the header; you fill each slot | Writes the header and fills every slot | | Context for translation | `msgctxt`, translator comments, references, flags | The same, plus Glossary and Custom Prompts | | Compiles and looks up translations at run time | Yes, `msgfmt` and the gettext runtime | No; keeps using gettext's | -| Cost | Free software | Usage-based, $0 platform fee on Starter | +| Cost | Free software | [Usage-based translation](https://generaltranslation.com/pricing) | ## When to use which diff --git a/kb/general-translation-vs-i18n-tasks.mdx b/kb/general-translation-vs-i18n-tasks.mdx index f004e6335..3bb160a8e 100644 --- a/kb/general-translation-vs-i18n-tasks.mdx +++ b/kb/general-translation-vs-i18n-tasks.mdx @@ -38,7 +38,7 @@ Here's `missing` and `unused` on a small Rails app with English, Polish, and Spa That report is accurate and useful. Filling the gaps is where the work stays with you: -- **The translator sees values, not context.** In a run with the Google backend's network call stubbed out, `translate-missing es` sent this list and nothing else: no keys, no file, no view. +- **The Google backend sends values.** In a run with that backend's network call stubbed out, `translate-missing es` sent this list: string values with placeholders masked, without keys, the file, or the view. ```text ["Inbox", "You have X__0 unread message.", "You have X__0 unread messages.", "Archived"] @@ -50,7 +50,7 @@ That report is accurate and useful. Filling the gaps is where the work stays wit ## Where General Translation fits -General Translation replaces the `translate-missing` step, not the checks. Point the `gt` CLI at your English file and run [`npx gt translate`](/docs/cli/reference/commands/translate) in CI. It writes every target locale, and on later runs it reuses translations for unchanged entries and retranslates the ones whose English changed. Each run uses your project's [Glossary and Custom Prompts](/docs/platform/dashboard/guides/defining-context-for-translations), so terms and tone stay the same in every file, and your team can [review and edit translations](/docs/platform/dashboard/guides/reviewing-translations) in the Dashboard. Pricing is usage-based, with a $0 platform fee and unlimited users, projects, and languages on Starter ([pricing](https://generaltranslation.com/pricing)). +General Translation replaces the `translate-missing` step, not the checks. Point the `gt` CLI at your English file and run [`npx gt translate`](/docs/cli/reference/commands/translate) in CI. It writes every target locale, and on later runs it reuses translations for unchanged entries and retranslates the ones whose English changed. Each run uses your project's [Glossary and Custom Prompts](/docs/platform/dashboard/guides/defining-context-for-translations), so terms and tone stay the same in every file, and your team can [review and edit translations](/docs/platform/dashboard/guides/reviewing-translations) in the Dashboard. Keep i18n-tasks for Ruby source scanning and catalog validation. Run General Translation's Node.js CLI for translation generation, and map the resulting YAML to Rails' locale-rooted format as shown in [General Translation vs. Rails I18n](/kb/general-translation-vs-rails-i18n). Use that workflow for catalogs with matching source and target key structures. @@ -63,10 +63,10 @@ Keep i18n-tasks for Ruby source scanning and catalog validation. Run General Tra | Plural validation | Checks required locale categories | Pair generation with i18n-tasks checks | | Sorts, moves, and renames keys | Yes | No | | Machine translation | `translate-missing`, through a provider you choose | [`npx gt translate`](/docs/cli/reference/commands/translate) | -| What the translator sees | String values in batches, placeholders masked | Your file's strings, plus your Glossary and Custom Prompts | +| Translation context | Backend-dependent: string values, with configurable DeepL glossaries or OpenAI prompts | File content and assigned Glossary and Custom Prompts, managed with Dashboard review | | When English changes | Existing translations stay | Changed entries are retranslated | | Review | Git diff | Dashboard and Git diff | -| Cost | Free gem; provider bills per character or token | Usage-based | +| Cost | Free gem; provider bills per character or token | [Usage-based translation](https://generaltranslation.com/pricing) | Choose i18n-tasks alone when translators write your locale files and you need CI to catch missing and unused keys, or when you only want a quick machine draft for a few strings. Use General Translation when you want every locale regenerated as the English changes, with consistent terminology and a review step, and keep i18n-tasks for the checks General Translation doesn't do. @@ -82,10 +82,10 @@ bundle exec i18n-tasks unused bundle exec i18n-tasks check-consistent-interpolations # %{name} matches the source ``` -Each `i18n-tasks` command exits with status 1 on a problem, so the job fails before a broken file merges. The CLI needs a project ID and an API key with **Files > Write** and **Translation queue > Enabled** permissions, set as `GT_PROJECT_ID` and `GT_API_KEY`. (See [Generating translations](/docs/cli/guides/generating-translations).) +Each `i18n-tasks` command exits with status 1 on a problem, so the job fails before a broken file merges. The CLI needs a project ID and an API key with **Files > Write**, **Context > Write**, and **Translation queue > Enabled** permissions, set as `GT_PROJECT_ID` and `GT_API_KEY`. (See [Generating translations](/docs/cli/guides/generating-translations).) ## Key takeaways - i18n-tasks finds missing, unused, and incomplete plural keys in Rails apps and fails CI when it does. That's work General Translation doesn't do. -- Its `translate-missing` sends bare string values to a provider, fills only keys that don't exist yet, and can write English into a locale file if the provider call fails. +- `translate-missing` fills keys that don't exist yet; context options depend on the provider. In the tested Google backend failure, it wrote English into the locale file and exited successfully. - General Translation translates whole locale files with your Glossary and Custom Prompts and keeps them current as English changes; run it before i18n-tasks checks in the same CI job. diff --git a/kb/general-translation-vs-in-house-i18n.mdx b/kb/general-translation-vs-in-house-i18n.mdx index b033f5749..1e749dc72 100644 --- a/kb/general-translation-vs-in-house-i18n.mdx +++ b/kb/general-translation-vs-in-house-i18n.mdx @@ -155,7 +155,7 @@ At the other end of the scale, Profound ships its platform and docs in 47+ local | Missing translations | Whatever your helper does | [Falls back to a configured language, then shows the key](https://www.i18next.com/principles/fallback) | Renders the source content | | Keeping languages in sync | By hand or with your own script | Extraction tools add new keys; someone fills them in | Each [`npx gt translate`](/docs/cli/reference/commands/translate) translates new and changed content | | Who translates | You or someone you hire | You, translators, or a translation management system | AI with your code's context, Glossary, and Custom Prompts; your team reviews in the Dashboard | -| Cost | Your time | Free library; translation is separate | Free libraries; translation is [usage-based](https://generaltranslation.com/pricing), with a $0 platform fee and unlimited users, projects, and languages on Starter | +| Cost | Your time | Free library; translation is separate | Free JavaScript libraries; [usage-based translation](https://generaltranslation.com/pricing) | ## When to build it yourself diff --git a/kb/general-translation-vs-intlayer.mdx b/kb/general-translation-vs-intlayer.mdx index a75b3a7b7..c577d8d66 100644 --- a/kb/general-translation-vs-intlayer.mdx +++ b/kb/general-translation-vs-intlayer.mdx @@ -1,24 +1,24 @@ --- title: "General Translation vs. Intlayer: which should you use?" -summary: Intlayer keeps each component's content in a typed declaration file next to it, with every language written side by side, and can fill missing languages with an AI provider you configure. General Translation keeps copy inline in your components in one language and generates the other languages with the context of your code. +summary: General Translation keeps source copy inline in JSX and generates the other languages with code context, shared terminology, and Dashboard review. Intlayer uses typed content declarations, combined or split by locale, with AI filling and an on-page editor. Choose GT to connect component edits directly to translation and release automation. date: 2026-10-16 category: internationalization authors: [kevin-wu, jackie] related: [general-translation-vs-i18n-libraries, general-translation-vs-paraglide, i18n-without-translation-files, how-to-handle-plurals-in-react, general-translation-vs-tms] --- -Intlayer is an [open-source i18n library](https://github.com/aymericzip/intlayer) that puts content in a declaration file next to each component, such as `inbox.content.ts`, where each string lists its translations in one `t({ en, es, pl })` call. A hook like `useIntlayer('inbox')` returns that content, typed, in the current locale. Intlayer fills missing languages with an AI provider you configure and ships a free visual editor and an optional hosted CMS, but every language of every string still lives in your source files, for you to write, fill, and review. General Translation takes the other path: you write copy once, inline, in the source language, and General Translation generates the other languages with context from your code. +Intlayer puts translations in typed content declarations, read by components through `useIntlayer('inbox')`. You can group languages together or use [per-locale files](https://intlayer.org/doc/concept/per-locale-file). General Translation keeps source copy in the component and generates the other languages from its marked JSX, with review in the Dashboard and ongoing updates through CI or Locadex. ## What Intlayer does well - **Content next to the component.** One declaration file per component keeps text close to the code that renders it. -- **Type safety.** `useIntlayer` keys are typed: in version 9.7.0, `useIntlayer('inbx')` is a TypeScript error. A `t()` call missing one of your configured locales is flagged too (a type error in a 9.7.0 test, with `strictMode` setting how strict the check is), so a gap shows up before you ship. +- **Type safety.** `useIntlayer` keys are typed: in version 9.7.0, `useIntlayer('inbx')` is a TypeScript error. A [`t()`](https://intlayer.org/doc/concept/content/translation) call missing one of your configured locales is flagged too (a type error in a 9.7.0 test, with `strictMode` setting how strict the check is), so a gap shows up before you ship. - **Rich content types.** Besides per-locale text, declarations support [plurals by CLDR category](https://intlayer.org/doc/concept/content/plural) (`plural({ one, few, many, other })`), enumerations, conditions, gender, Markdown, HTML, nesting, and insertion. - **Bring your own model.** `intlayer fill` translates missing locales with OpenAI, Anthropic, Mistral, Gemini, DeepSeek, Ollama, OpenRouter, and other providers, set under [`ai` in `intlayer.config.ts`](https://intlayer.org/doc/concept/configuration) or with `--provider` and `--api-key`. With your own key and the `@intlayer/ai` package installed, [it runs locally](https://intlayer.org/doc/concept/cli/fill) and you pay the provider directly. - **Visual editing.** The `intlayer-editor` package loads your running app in an editor where writers select and edit text, then writes the change back to your declaration files. [Intlayer's docs say](https://intlayer.org/doc/concept/editor) the editor is Apache 2.0, free for commercial use, and meant for development and staging. - **Framework reach.** [Guides](https://intlayer.org/doc/get-started) cover Next.js, React with Vite, React Router, TanStack Start, React Native, Vue and Nuxt, Svelte and SvelteKit, Solid, Preact, Astro, Angular, and server frameworks such as Express, NestJS, Fastify, and Hono. -## What Intlayer leaves to you +## How do content declarations compare with inline JSX? Here's an inbox's content file with three locales: @@ -69,14 +69,14 @@ export function Inbox({ count }: { count: number }) { } ``` -That renders correctly in every locale, and the file grows with each one you add: at 20 locales, each string carries 20 entries. A new string needs all of them too. Adding `archive: t({ en: 'Archive' })` produced a type error naming the locales still missing: +This example uses Intlayer's combined declaration format. Per-locale declarations can keep additional translations in separate files. In the strict combined configuration used here, adding `archive: t({ en: 'Archive' })` produced a type error naming the locales still missing: ```text title="Output" Type '{ en: string; }' is missing the following properties from type 'Required>': es, pl ``` -At run time, the missing locales fell back to English. To close the gap, you write the translations or run `intlayer fill`. Fill sends the dictionary's content, [its description](https://intlayer.org/doc/concept/content), and an `applicationContext` string from your config to the model. It doesn't see the component or the JSX around a string. [Intlayer's compiler](https://intlayer.org/doc/compiler) can extract plain JSX text into content files at build time, so you can skip writing declarations by hand, but translations still end up in those files. +At run time, the missing locales fell back to English. Fill them by running [`intlayer fill`](https://intlayer.org/doc/concept/cli/fill) with a configured provider or Intlayer service access. The [`dictionary.fill`](https://intlayer.org/doc/concept/configuration) setting chooses the output destination: `true` uses the source declaration, a path pattern selects separate files, and `false` disables filling. Fill sends dictionary content, [its description](https://intlayer.org/doc/concept/content), and configured application context to the model. [Intlayer's compiler](https://intlayer.org/doc/compiler) can extract JSX text into declarations. ## General Translation keeps one language in the code @@ -103,9 +103,9 @@ export function Inbox({ count }: { count: number }) { There's no content file and no key. [`npx gt translate`](/docs/cli/reference/commands/translate) sends each [``](/docs/react/reference/components/t) block, with its element structure, to the General Translation API and writes translations for every target locale to separate files, so adding a locale changes `gt.config.json`, not your components. You write the plural forms English uses, and [``](/docs/react/reference/components/plural) picks a branch with the reader's locale's rules. [`npx gt validate`](/docs/cli/reference/commands/validate) checks the project without calling the API, and [`gt generate`](/docs/cli/reference/commands/generate) writes templates for teams that translate by hand. -The context is what changes the result. Each block is translated with the JSX around it, and your Glossary and Custom Prompts apply across every project (see [Defining context for translations](/docs/platform/dashboard/guides/defining-context-for-translations)). Reviewers edit in the Dashboard instead of in source files, and the CLI runs in CI, or Locadex, General Translation's cloud agent, opens pull requests for React and Next.js apps. Teams that don't want to add [``](/docs/react/reference/components/t) by hand can turn on [automatic JSX injection](/docs/cli/guides/using-auto-jsx), which is off by default. +Code context helps GT translate the meaning of a component. Each marked block includes its text, element structure, and explicit context notes; a note can distinguish saving a document from saving money. [Context Groups](/docs/platform/dashboard/guides/defining-context-for-translations) keep your Glossary and Custom Prompts consistent across assigned projects. Reviewers edit translations in the Dashboard, and CI or Locadex keeps them current as copy changes. [Automatic JSX injection](/docs/cli/guides/using-auto-jsx) can add the wrappers during the build when enabled. -The tradeoffs: translations aren't next to the component, so a code review sees them in per-locale files, and there's no on-page visual editor like Intlayer's. An edited sentence becomes a new entry that the next run translates and bills. The libraries are MIT-licensed and work without an account, and General Translation's pricing is usage-based, with a $0 platform fee and unlimited users, projects, and languages on Starter ([pricing](https://generaltranslation.com/pricing)). +The tradeoffs: translations aren't next to the component, so a code review sees them in per-locale files, and there's no on-page visual editor like Intlayer's. An edited sentence becomes a new entry that the next run translates and bills. The JavaScript libraries are MIT-licensed and work without an account. Cursor uses General Translation for 14+ locales across web and docs. (See [production deployments](https://generaltranslation.com/enterprise).) @@ -113,23 +113,17 @@ Cursor uses General Translation for 14+ locales across web and docs. (See [produ | Feature | Intlayer | General Translation | | --- | --- | --- | -| How you write copy | `t({ en, es, ... })` in a declaration file per component, read with `useIntlayer` | JSX inside [``](/docs/react/reference/components/t), strings through [`useGT`](/docs/react/reference/hooks/use-gt), or keyed dictionaries | +| How you write copy | Typed content declarations, combined or per locale, read with `useIntlayer`; optional compiler extraction | JSX inside [``](/docs/react/reference/components/t), strings through [`useGT`](/docs/react/reference/hooks/use-gt), or keyed dictionaries; optional JSX injection | | Where translations live | In the declaration files, or per-locale content files | Separate per-locale files, or General Translation's CDN | | AI translation | `intlayer fill` with your provider key, or through Intlayer's service | [`npx gt translate`](/docs/cli/reference/commands/translate) through the General Translation API | -| Context the model gets | Dictionary content, description, and an application context string | The surrounding JSX, your Glossary, and Custom Prompts | +| Context the model gets | Dictionary content, description, and an application context string | In the inline example: marked JSX structure, explicit context, and assigned Glossary and Custom Prompts | | Missing locales | Flagged, as a type error in a 9.7.0 test; [`intlayer test`](https://intlayer.org/doc/concept/cli/test) reports them | Generated by [`npx gt translate`](/docs/cli/reference/commands/translate); untranslated content renders in the source language | | Editing | Free local visual editor; optional hosted CMS | Translation editing and review in the Dashboard | | Frameworks | React, Next.js, Vue, Svelte, Solid, Angular, Astro, React Native, and Node.js servers | React, Next.js, Vue, and Node.js; React Native, TanStack Start, and Python are experimental | -| License and cost | Apache 2.0; hosted CMS plans scale by projects, seats, and storage ([pricing](https://app.intlayer.org/pricing), checked October 2026) | MIT libraries; usage-based translation | +| License and cost | Apache 2.0; hosted CMS plans scale by projects, seats, and storage ([pricing](https://app.intlayer.org/pricing), checked October 2026) | MIT-licensed JavaScript libraries; [usage-based translation](https://generaltranslation.com/pricing) | -Choose Intlayer when you want every language visible next to the component, type errors for missing locales, an on-page editor for writers, or a framework General Translation doesn't support, such as Svelte, Solid, or Angular. Use General Translation when you'd rather write copy once in the source language and have every other language generated with the context of your code, your Glossary, and Custom Prompts. +Choose Intlayer when typed content declarations, an on-page editor, or support for Svelte, Solid, or Angular matter most. Choose General Translation when you want to write copy once in JSX, translate it with code context and shared terminology, and keep languages current through CI or Locadex while reviewers work in the Dashboard. ## Moving from Intlayer to General Translation The `gt` CLI doesn't read `.content.ts` files, and General Translation has no importer for Intlayer dictionaries. You can move one component at a time, because each library has its own provider: in a server render with `react-intlayer` 9.7.0 and `gt-react` 11.4.12, an Intlayer component and a [``](/docs/react/reference/components/t) component rendered side by side under `IntlayerProvider` and [`GTProvider`](/docs/react/reference/components/gt-provider). For each component, copy the `en` values into JSX inside [``](/docs/react/reference/components/t), delete its content file, and run [`npx gt translate`](/docs/cli/reference/commands/translate), which translates the moved copy fresh. Start with the [Next.js Quickstart](/docs/react/nextjs-quickstart) or the [React Quickstart](/docs/react/react-quickstart), and see [Migrating i18n libraries](/docs/react/guides/migrating-i18n-libraries) for running two libraries during a move. - -## Key takeaways - -- Intlayer keeps each component's content in a typed declaration file with every language side by side, catches missing locales as type errors, and fills them with an AI provider you configure. -- General Translation keeps only the source language in your components and generates every other language with context from your code, your Glossary, and Custom Prompts, with review in the Dashboard. -- Intlayer covers more frameworks and offers on-page visual editing; you can move to General Translation one component at a time, since both libraries render side by side. diff --git a/kb/general-translation-vs-locize.mdx b/kb/general-translation-vs-locize.mdx index 8ddd0522e..657fb1e9a 100644 --- a/kb/general-translation-vs-locize.mdx +++ b/kb/general-translation-vs-locize.mdx @@ -1,13 +1,13 @@ --- title: "General Translation vs. Locize: which should you use?" -summary: Locize is a translation management system from the creators of i18next that delivers translations from a CDN, collects new keys from your running app, and adds AI translation and an in-context editor. You still maintain the i18n library and its keys. General Translation can be your i18n library and translate it, or translate the i18next files you already have. +summary: General Translation lets i18next teams keep their catalogs or move to inline JSX with code context, automatic translation, and Dashboard review. Locize pairs with i18next and offers translation versioning and an in-context editor. Choose GT for an integrated authoring-to-release workflow; keep Locize when its editing and versioning tools are central to your process. date: 2026-10-16 category: workflow-tools authors: [kevin-wu, jackie] related: [general-translation-vs-i18n-libraries, general-translation-vs-tms, general-translation-vs-tolgee, general-translation-vs-weblate, i18n-without-translation-files] --- -Locize is a translation management system (TMS) built by the team behind i18next. Your app keeps using i18next (or another i18n library), Locize stores the keys and their translations, AI or your translators fill in each language, and the app loads the results from Locize's CDN, so a fixed translation goes live without a redeploy. Locize pairs with your i18n library; it doesn't replace it, so the library setup, the keys, and the wiring stay with your engineers. General Translation can be both: its own open-source i18n libraries plus the translation, or just the translation for i18next files you already have. +Locize is the translation management system built by the team behind i18next. It manages keyed catalogs, translation, and delivery alongside your i18n library. General Translation combines its own open-source libraries with context-aware translation and release automation. You can adopt that full workflow or keep i18next and let GT translate the catalogs you already have. ## What Locize does well @@ -19,7 +19,7 @@ Locize is a translation management system (TMS) built by the team behind i18next - **Plural keys per language.** For i18next JSON, Locize [generates the plural keys](https://www.locize.com/docs/integration/i18n-formats/pluralization) each target language needs (such as Polish `_few` and `_many`) and shows every form in its editor. - **Beyond i18next.** [`locize-cli`](https://github.com/locize/locize-cli) imports and exports formats including XLIFF, Android XML, Apple `.strings`, PO, RESX, Fluent, and Java properties, and Locize publishes examples for react-intl, vue-i18n, and Lingui. There's also a GitHub Action, an MCP server, and a Figma plugin. -## What Locize still leaves to your engineers +## How does new copy reach Locize? Here's `saveMissing` with `i18next` 26.4.7 and `i18next-locize-backend` 10.0.3, rendering Spanish for a key Locize hasn't seen yet. The request function stands in for Locize's servers and logs each call: @@ -65,18 +65,20 @@ No messages yet. POST https://api.lite.locize.app/missing/demo-project/latest/en/translation {"inbox.empty":"No messages yet."} ``` -The new key reaches Locize, but only as an English key and value, and the Spanish reader sees English. The loop is still: +The recorded request shows how a new key reaches Locize. [Automatic translation](https://www.locize.com/docs/automatic-translation) can translate it when enabled with a configured service. Collection, review, and delivery fit together as follows: 1. Set up i18next, name a key for each string, and call `t()` with it. 2. Send new keys to Locize with `saveMissing` in development (it's limited to `localhost` by default, and [the write key must never ship](https://www.locize.com/docs/guides/going-to-production)) or with a sync command in CI. -3. Wait while AI or your translators fill in each language, review the results, and publish them to the version your app reads. -4. Load translations from the CDN at run time, or download them into your build, and register each new language in the app. +3. Configure automatic translation or human translation, along with the review and publishing policy for the version your app reads. +4. Load published translations from the CDN, or download them into your build. Register the supported languages in the app. + +Locize's [GitHub Actions](https://www.locize.com/docs/integration/github-action) can synchronize catalogs and trigger configured translation in CI before deployment. The context that reaches a translator or the AI is the key, its English value, any context note, and [screenshots matched to it](https://www.locize.com/docs/context), not the component that renders the string. Locize plans have a monthly base price that includes words, CDN downloads, languages, namespaces, and versions, with extra blocks of words and downloads on larger plans and AI and machine translation priced per token or character. (See [Locize pricing](https://www.locize.com/pricing), checked October 2026.) -## General Translation for i18next teams +## Can I keep i18next with General Translation? You don't have to rewrite anything to try General Translation. The CLI detects `i18next` in your `package.json` and translates your JSON files while keeping i18next's interpolation syntax (or ICU syntax, if you use `i18next-icu`): @@ -98,7 +100,11 @@ You don't have to rewrite anything to try General Translation. The CLI detects ` [`npx gt translate --dry-run`](/docs/cli/reference/commands/translate) finds `locales/en/translation.json`, and [`npx gt translate`](/docs/cli/reference/commands/translate) writes `locales/es/translation.json`. Use this file workflow for messages with matching source and target key structures. A companion metadata file can attach a `context` note or `maxChars` limit to any key. See [Keyed metadata](/docs/cli/reference/keyed-metadata). -Or go further and let General Translation be the library too. [``](/docs/react/reference/components/t) wraps JSX and translates it in place, and [`useGT`](/docs/react/reference/hooks/use-gt) covers strings, so there are no keys to name. [`useTranslations`](/docs/react/reference/hooks/use-translations) keeps a keyed dictionary for teams that want one. `gt-next`, `gt-react`, and `gt-vue` are MIT-licensed, have no i18next underneath, and work without a General Translation account. +Your existing catalogs gain GT's shared terminology and review workflow: file content and key metadata guide translation, [Context Groups](/docs/platform/dashboard/guides/defining-context-for-translations) apply Glossary terms and Custom Prompts, and reviewers edit the results in the Dashboard. + +### What changes with inline authoring? + +[``](/docs/react/reference/components/t) wraps source JSX, and [`useGT`](/docs/react/reference/hooks/use-gt) covers strings, so you don't need a key for each sentence. The CLI extracts marked content, including the element structure inside each block, for translation. [`useTranslations`](/docs/react/reference/hooks/use-translations) also supports keyed dictionaries. `gt-next`, `gt-react`, and `gt-vue` are MIT-licensed, have no i18next underneath, and work without a General Translation account. ## Locize vs. General Translation at a glance @@ -107,20 +113,20 @@ Or go further and let General Translation be the library too. [``](/docs/reac | What it is | A TMS that pairs with i18next or another i18n library | Open-source i18n libraries plus AI translation, or translation for your existing files | | Where strings live | Keys in your code; values in Locize | In your code inside [``](/docs/react/reference/components/t), or in the resource files you already have | | How new copy gets in | `saveMissing` in development, or sync commands in CI | [`npx gt translate`](/docs/cli/reference/commands/translate) in your build, or [Locadex](/docs/platform/locadex/quickstart) pull requests | -| Who translates | Locize's AI, your own AI or machine translation key, or your translators | AI with context from your code, Glossary, and Custom Prompts, reviewed by your team in the Dashboard | -| Context for each string | Key, English value, context notes, screenshots, glossary, and style guide | The component and elements around the string, its variables, context notes, Glossary, and Custom Prompts | -| Delivery | CDN by default, with versions; or downloaded into your build | Files in your repository, or General Translation's CDN when you turn on publishing | +| Who translates | Locize's AI, your own AI or machine translation key, or your translators | AI guided by the content and product context; review and editing in the Dashboard | +| Context for translation | Key, English value, context notes, screenshots, glossary, and style guide | Inline blocks: marked element structure and explicit context. Catalogs: file content and supplied metadata. Both use assigned Glossary and Custom Prompts | +| Delivery | CDN with translation versions, or downloaded into your build | Bundled files, or published CDN translations that can update without an app redeploy, subject to loading and caching | | i18next plural keys | Generated for each target language | Translates the plural keys in your source file | -| Pricing | Plan tiers with words, CDN downloads, languages, and versions; AI and machine translation as top-ups | Usage-based; $0 platform fee and unlimited users, projects, and languages on Starter | +| Pricing | Plan tiers with words, CDN downloads, languages, and versions; AI and machine translation as top-ups | [Usage-based translation](https://generaltranslation.com/pricing) | ## Why teams choose General Translation over Locize -General Translation translates as part of your release, with context from the code, instead of collecting keys and publishing translations afterward. +General Translation connects copy in your components to translation generation, review, and delivery. Its inline workflow removes separate source-key maintenance and carries code context into translation. -- **Faster.** Translation runs in your build or as a Locadex pull request, so new copy ships translated with the release that adds it, not as an English fallback until a translation is published. Adding a language is one line in `gt.config.json`. -- **Cheaper to run.** With [``](/docs/react/reference/components/t), engineers don't name keys or sync them, and General Translation's pricing is usage-based, with a $0 platform fee and unlimited users, projects, and languages on Starter. (See [pricing](https://generaltranslation.com/pricing).) -- **Higher quality.** Locize's AI works from each key, its English value, context notes, screenshots, glossary, and style guide. General Translation sends the context of the code with every translation: the elements around a string, the variables in the sentence, and the branches that choose between versions of it, plus your Glossary and Custom Prompts across every project. -- **Lower risk.** Locize delivers translations from its CDN by default. General Translation's libraries and CLI are open source under the MIT license and work without an account, and translations can ship as files in your build, so the app makes no request to General Translation at run time. +- **Faster.** Edit copy inside [``](/docs/react/reference/components/t) and run translation before the build. The CLI extracts the changed content without a separate key-definition edit, or [Locadex](/docs/platform/locadex/quickstart) prepares a pull request. New copy and its translations can ship together. +- **Cheaper to run.** Inline copy removes the engineering work of naming and maintaining source keys. GT charges for translation and agent usage rather than a monthly catalog allowance; see [pricing](https://generaltranslation.com/pricing). +- **Higher quality.** GT translates each marked block with its text, nested elements, variables, and branches together. Context notes explain ambiguous wording; your Glossary and Custom Prompts keep product terms and tone consistent. That code structure accompanies the content during extraction. +- **Lower risk.** The MIT-licensed JavaScript libraries render supplied translations without an account. Bundle translations to remove GT runtime service calls, or use CDN delivery for published edits between releases. Your rendering setup can keep working if you change how translations are produced. With General Translation, Cursor ships 14+ locales across web and docs. (See [production deployments](https://generaltranslation.com/enterprise).) @@ -131,9 +137,3 @@ Choose Locize when translators want to edit on the live site, when you keep sepa ## Moving from Locize to General Translation Download your current translations with `locize-cli` and keep them in your repository, then point `gt.config.json` at the English files and run [`npx gt upload`](/docs/cli/reference/commands/upload) to send your sources and existing translations to the platform. From there you can keep i18next and let the CLI translate new keys, or move one feature at a time to [``](/docs/react/reference/components/t). (See [Migrating i18n libraries](/docs/react/guides/migrating-i18n-libraries).) Glossary terms can come along too: Context Group imports accept CSV, TSV, JSON, and text files. - -## Key takeaways - -- Locize, from the creators of i18next, is a strong TMS for i18next apps, with CDN delivery, `saveMissing`, versions, in-context editing, and AI translation. -- It pairs with your i18n library, so engineers still name keys, sync them, wait for translations, and publish them before readers see new copy translated. -- General Translation can translate the i18next files you already have, or be your i18n library and translation together, with the context of the code and every language shipped with each release. diff --git a/kb/general-translation-vs-mobility.mdx b/kb/general-translation-vs-mobility.mdx index 50db4aef3..dac0db128 100644 --- a/kb/general-translation-vs-mobility.mdx +++ b/kb/general-translation-vs-mobility.mdx @@ -29,6 +29,8 @@ class Post < ApplicationRecord end ``` +The `fallbacks:` option needs Mobility's `fallbacks` plugin, which is commented out in the initializer that `rails generate mobility:install` creates. Uncomment it in `config/initializers/mobility.rb`, or Mobility raises `InvalidOptionKey`. + ```ruby post = Post.create!(title: "Spring sale", body: "Everything is 20% off this week.") @@ -76,7 +78,7 @@ Mobility.with_locale(:es) { post.update!(translated.compact) } Each entry comes back as `{ "success": true, "translation": "..." }` or `{ "success": false, "error": "...", "code": ... }`, so one failed field doesn't lose the others. (See the [endpoint reference](/docs/platform/openapi/reference/translation/translate-runtime) for every field.) -General Translation does more here than a raw machine translation call: the endpoint translates with your project's Glossary and Custom Prompts, so product names and tone match the rest of your product, and it caches results, so translating the same text again is served from the cache. Call it from a background job after a record is saved, keep the API key on the server, and expect `429` responses above the [rate limits](/docs/platform/openapi/overview#api-basics). If part of your stack runs on Node.js, [`tx`](/docs/node/reference/functions/tx) in `gt-node` and the [Core library](/docs/platform/core/guides/translating-strings) do the same runtime translation from JavaScript. Pricing is usage-based, with a $0 platform fee and unlimited users, projects, and languages on Starter ([pricing](https://generaltranslation.com/pricing)). +General Translation does more here than a raw machine translation call: the endpoint translates with your project's Glossary and Custom Prompts, so product names and tone match the rest of your product, and it caches results, so translating the same text again is served from the cache. Call it from a background job after a record is saved, keep the API key on the server, and expect `429` responses above the [rate limits](/docs/platform/openapi/overview#api-basics). If part of your stack runs on Node.js, [`tx`](/docs/node/reference/functions/tx) in `gt-node` and the [Core library](/docs/platform/core/guides/translating-strings) do the same runtime translation from JavaScript. The tradeoff: these translations go straight into your database the moment the job runs. If editors must approve a translation before readers see it, add that step yourself, for example by saving machine translations as drafts. @@ -90,7 +92,7 @@ The tradeoff: these translations go straight into your database the moment the j | Ruby integration | Native | HTTP calls from Ruby; no Ruby SDK | | Fallbacks, querying, dirty tracking | Yes | Not applicable | | Consistent terms and tone | Up to your translators | Glossary and Custom Prompts applied to every request | -| Cost | Free, MIT | Usage-based | +| Cost | Free, MIT | [Usage-based translation](https://generaltranslation.com/pricing) | ## When Mobility alone is enough diff --git a/kb/general-translation-vs-nestjs-i18n.mdx b/kb/general-translation-vs-nestjs-i18n.mdx index 36c7ecea6..a934ac8ad 100644 --- a/kb/general-translation-vs-nestjs-i18n.mdx +++ b/kb/general-translation-vs-nestjs-i18n.mdx @@ -69,11 +69,11 @@ General Translation's CLI translates nestjs-i18n's catalogs where they are. Poin This example translates an English catalog into Spanish. Use this file workflow for messages with matching source and target key structures, and validate representative plural counts in the rendered app. -For content that only exists at run time, such as a teammate's post or a database record, use General Translation's [`gt-node`](/docs/node/quickstart). Initialize it once, bind each request's locale with [`withGT`](/docs/node/reference/functions/with-gt), and call [`tx`](/docs/node/reference/functions/tx) in a controller: +For content that only exists at run time, such as a teammate's post or a database record, use General Translation's [`gt-node`](/docs/node/quickstart). Initialize it once, then pass nestjs-i18n's resolved language to [`withGT`](/docs/node/reference/functions/with-gt) before calling [`tx`](/docs/node/reference/functions/tx). This example assumes the existing `I18nModule` has its catalogs and [resolvers](https://nestjs-i18n.com/concepts/resolver) configured, and `PostsService` returns the post's English source text. ```ts title="src/main.ts" import { NestFactory } from '@nestjs/core'; -import { getRequestLocale, initializeGT, withGT } from 'gt-node'; +import { initializeGT } from 'gt-node'; import { AppModule } from './app.module'; // Reads GT_PROJECT_ID and GT_API_KEY (a project key with Runtime translation) from the environment. @@ -81,8 +81,6 @@ initializeGT({ defaultLocale: 'en', locales: ['en', 'es', 'pl', 'de'] }); async function bootstrap() { const app = await NestFactory.create(AppModule); - // Bind the request's locale for gt-node. - app.use((req, res, next) => withGT(getRequestLocale(req), () => next())); await app.listen(3000); } bootstrap(); @@ -90,7 +88,8 @@ bootstrap(); ```ts title="src/posts.controller.ts" import { Controller, Get, Param } from '@nestjs/common'; -import { tx } from 'gt-node'; +import { I18n, I18nContext } from 'nestjs-i18n'; +import { tx, withGT } from 'gt-node'; import { PostsService } from './posts.service'; @Controller('posts') @@ -98,15 +97,16 @@ export class PostsController { constructor(private readonly posts: PostsService) {} @Get(':id') - async findOne(@Param('id') id: string) { - const post = await this.posts.findOne(id); - // Translated on demand into the request's locale. - return { ...post, body: await tx(post.body, { $context: 'A post written by a user' }) }; + async findOne(@Param('id') id: string, @I18n() i18n: I18nContext) { + return withGT(i18n.lang, async () => { + const post = await this.posts.findOne(id); + return { ...post, body: await tx(post.body, { $context: 'A post written by a user' }) }; + }); } } ``` -That's all the glue it needs: Nest's default Express adapter runs the middleware, and [`withGT`](/docs/node/reference/functions/with-gt) keeps the locale for the rest of the request alongside nestjs-i18n's own context. [`getRequestLocale`](/docs/node/reference/functions/get-request-locale) reads `Accept-Language`; to follow nestjs-i18n's resolvers instead, pass the language they chose. [`tx`](/docs/node/reference/functions/tx) makes a network request when a string isn't cached, and without credentials it returns the source text, so keep it for dynamic content and let catalogs hold your fixed copy. +The `@I18n()` parameter supplies the language selected by nestjs-i18n. [`withGT`](/docs/node/reference/functions/with-gt) uses it throughout the async callback, including nested service calls. If `QueryResolver` takes priority over `AcceptLanguageResolver`, a request with `?lang=es` and `Accept-Language: en` uses Spanish for both catalog lookups and GT's dynamic translation. Keep the supported locales aligned in both libraries. [`tx`](/docs/node/reference/functions/tx) makes a network request when a string isn't cached, and without credentials it returns the source text, so keep it for dynamic content and let catalogs hold your fixed copy. Keep nestjs-i18n for typed keys, catalog lookups, and validation messages. General Translation supplies translation generation, a shared Glossary and Custom Prompts, and [Dashboard review](/docs/platform/dashboard/guides/reviewing-translations). Use the catalog workflow above for fixed copy and `tx` for dynamic content. @@ -116,11 +116,11 @@ Keep nestjs-i18n for typed keys, catalog lookups, and validation messages. Gener | --- | --- | --- | | Role | Resolves the request language and renders messages from catalogs | Translates catalogs, and translates dynamic content at run time | | Translates your content | No | Yes, with [`npx gt translate`](/docs/cli/reference/commands/translate) and [`tx`](/docs/node/reference/functions/tx) | -| Language detection | Query, header, cookie, `Accept-Language`, GraphQL, gRPC, and custom resolvers | [`getRequestLocale`](/docs/node/reference/functions/get-request-locale) reads `Accept-Language` | +| Language selection in this integration | The configured resolver order selects the language | [`withGT`](/docs/node/reference/functions/with-gt) uses the language selected by nestjs-i18n | | Plurals | Per-language category keys, chosen with `Intl.PluralRules` | Translates the plural keys your source file has | | Type safety | Generated key types and a missing-key check | None for catalogs | | Validation messages | class-validator integration | Translates the catalogs those messages come from | -| Cost | Free and MIT-licensed | Usage-based; $0 platform fee and unlimited users, projects, and languages on [Starter](https://generaltranslation.com/pricing) | +| Cost | Free and MIT-licensed | [Usage-based translation](https://generaltranslation.com/pricing) | ## When to use which @@ -129,12 +129,13 @@ Keep nestjs-i18n for rendering: its resolvers, typed keys, and validation integr ## Using both 1. Leave nestjs-i18n and its folders as they are. -2. Add `gt.config.json` with your catalog path, and run [`npx gt translate`](/docs/cli/reference/commands/translate) in CI with `GT_API_KEY` and `GT_PROJECT_ID` set. (See [Configuring the CLI](/docs/cli/guides/configuring#credentials).) -3. Upload the translations you already have with [`npx gt upload`](/docs/cli/reference/commands/upload) so the Dashboard starts from your current catalogs. -4. Add `gt-node` only where you need runtime translation. +2. Add `gt.config.json` with your catalog path and set `GT_API_KEY` and `GT_PROJECT_ID`. (See [Configuring the CLI](/docs/cli/guides/configuring#credentials).) +3. Run [`npx gt upload`](/docs/cli/reference/commands/upload) to import your existing sources and translations before generating more. Review the imported catalogs in the Dashboard. +4. Run [`npx gt translate`](/docs/cli/reference/commands/translate) after a source edit. Review the diff, including previously approved messages, before enabling generation in CI. Preserve later local edits with [`gt save-local`](/docs/cli/reference/commands/save-local) or the opt-in [`--save-local`](/docs/cli/reference/commands/translate) step. +5. Add `gt-node` only where you need runtime translation, using nestjs-i18n's resolved language as above. ## Key takeaways - nestjs-i18n handles language detection, typed keys, plurals, and validation messages in NestJS, but every catalog in every language is yours to write, including each language's plural forms. -- General Translation's CLI translates nestjs-i18n's JSON catalogs in place, and `gt-node` translates dynamic content per request with a single middleware. +- General Translation's CLI translates nestjs-i18n's JSON catalogs in place; `gt-node` translates dynamic content using the same resolved language. - Use them together: nestjs-i18n renders, General Translation translates. diff --git a/kb/general-translation-vs-paraglide.mdx b/kb/general-translation-vs-paraglide.mdx index 31c398511..9cba24af3 100644 --- a/kb/general-translation-vs-paraglide.mdx +++ b/kb/general-translation-vs-paraglide.mdx @@ -12,10 +12,10 @@ Paraglide JS is a compiler-first i18n library from inlang. You keep messages in ## What Paraglide JS does well - **Type safety.** Message keys and parameters are generated types. With version 2.29.0, a misspelled `m.greting()` and a call to `m.greeting({})` without its `name` parameter are both TypeScript errors. -- **Bundle size.** Messages compile to ES modules, so unused ones are tree-shaken. Paraglide's [benchmark](https://paraglidejs.com/benchmark) reports up to 70% smaller i18n bundles than runtime libraries, for example 47 KB against 205 KB for i18next with 5 locales and 200 messages. +- **Bundle size.** Messages compile to ES modules, so unused ones are tree-shaken. Paraglide's own [benchmark](https://paraglidejs.com/benchmark) reports 47 KB versus 205 KB for i18next with 5 locales, 100 messages used, and 200 messages available. Those figures measure total page transfer size for its test sites; the benchmark does not compare Paraglide with GT. - **Plurals and variants.** The default message format [declares a plural selector](https://paraglidejs.com/variants) and matches on CLDR categories such as `one`, `few`, and `many`. [Plugins](https://paraglidejs.com/file-formats) add ICU MessageFormat 1 and i18next files, and formatters cover numbers, dates, and relative time through `Intl`. - **Locale strategies.** An ordered [`strategy` list](https://paraglidejs.com/strategy) resolves the locale from the URL, a cookie, `localStorage`, the browser's preferred language, a global variable, custom strategies, or the `baseLocale`. In 2.29.0 the [default](https://github.com/opral/paraglide-js/blob/main/src/compiler/compiler-options.ts) is `cookie`, `globalVariable`, then `baseLocale`. [Server middleware](https://paraglidejs.com/middleware) and `localizeUrl` handle localized routes. -- **Framework reach.** Paraglide [works with any Vite app](https://paraglidejs.com/vite) and has guides for React, Vue, Solid, SvelteKit, TanStack Start, React Router, Astro, and Next.js. It's the [add-on behind `npx sv add paraglide`](https://paraglidejs.com/sveltekit) in the Svelte CLI. Its [Next.js guide](https://paraglidejs.com/next-js) covers the App Router through a webpack plugin, run with the `--webpack` flag. +- **Framework reach.** Paraglide [works with Vite](https://paraglidejs.com/vite), including React, Vue, Svelte, and Solid apps, and has guides for SvelteKit, TanStack Start, React Router, Astro, and Next.js. It's the [add-on behind `npx sv add paraglide`](https://paraglidejs.com/sveltekit) in the Svelte CLI. Its [Next.js guide](https://paraglidejs.com/next-js) covers the App Router through a webpack plugin, run with the `--webpack` flag. - **Free, open tooling.** The library is [MIT-licensed](https://github.com/opral/paraglide-js/blob/main/LICENSE). The [inlang CLI](https://inlang.com/m/2qj2w8pu/app-inlang-cli) checks projects in CI, [Sherlock](https://inlang.com/m/r7kp499g/app-inlang-ideExtension) shows translations inline in VS Code, and [Fink](https://fink.inlang.com/) is a free browser editor that commits translators' changes to a GitHub repository. ## What Paraglide JS leaves to you @@ -87,9 +87,9 @@ export function Inbox({ name, count }: { name: string; count: number }) { There's no message file to keep in step: the English is the component. [`npx gt translate`](/docs/cli/reference/commands/translate) sends each [``](/docs/react/reference/components/t) block, with its element structure, to the General Translation API and writes a translation for every target locale, so a new sentence is translated before your build instead of falling back to English. A value in [``](/docs/react/reference/components/var) is never sent to the API, and [``](/docs/react/reference/components/plural) picks a branch with the reader's locale's plural rules. [`npx gt validate`](/docs/cli/reference/commands/validate) checks the project without calling the API, and [`gt generate`](/docs/cli/reference/commands/generate) writes template files for teams that translate by hand. -Context is the reason the translations hold up. Each block is translated with its surrounding JSX, General Translation applies your Glossary and Custom Prompts across every project (see [Defining context for translations](/docs/platform/dashboard/guides/defining-context-for-translations)), and reviewers edit results in the Dashboard. The CLI runs in CI, or Locadex, General Translation's cloud agent, can open the pull requests for React apps. +GT uses code context to translate the content as a whole: text, nested elements, and branches inside each marked block travel together. Explicit notes explain the product meaning, and a shared [Context Group](/docs/platform/dashboard/guides/defining-context-for-translations) applies Glossary terms and Custom Prompts across assigned projects. Reviewers edit in the Dashboard; CI or Locadex keeps translations aligned with source changes. -There are tradeoffs. `gt-react` has no per-message tree-shaking and no generated type for each message, because there are no message keys: translations load per locale, from files in your build or from General Translation's CDN. An edited sentence inside [``](/docs/react/reference/components/t) becomes a new entry that the next run translates and bills. The libraries are MIT-licensed and work without an account, and General Translation's platform pricing is usage-based, with a $0 platform fee and unlimited users, projects, and languages on Starter ([pricing](https://generaltranslation.com/pricing)). +There are tradeoffs. `gt-react` has no per-message tree-shaking and no generated type for each message, because there are no message keys: translations load per locale, from files in your build or from General Translation's CDN. An edited sentence inside [``](/docs/react/reference/components/t) becomes a new entry that the next run translates and bills. The JavaScript libraries are MIT-licensed and work without an account. Sierra ships 7+ locales across its web and go-to-market content with General Translation. (See [production deployments](https://generaltranslation.com/enterprise).) @@ -98,15 +98,15 @@ Sierra ships 7+ locales across its web and go-to-market content with General Tra | Feature | Paraglide JS | General Translation | | --- | --- | --- | | How you write copy | Keys in a JSON file per locale, called as `m.key()` | JSX inside [``](/docs/react/reference/components/t), strings through [`useGT`](/docs/react/reference/hooks/use-gt), or keyed dictionaries | -| Translates your content | No; `inlang machine translate` uses a community service, Google, or DeepL | Yes, with [`npx gt translate`](/docs/cli/reference/commands/translate) | -| What the translator sees | The message | The surrounding JSX, your Glossary, and Custom Prompts | +| Translates your content | No; Paraglide compiles catalogs. The separate `inlang machine translate` tool generates translations | Yes, through the GT service with [`npx gt translate`](/docs/cli/reference/commands/translate) | +| What the translator sees | The message | Text and element structure inside the marked block, explicit context, and assigned Glossary and Custom Prompts | | Type safety | Generated types for every key and parameter | No generated message types; copy is inline, so there are no keys to mistype | | Bundle | Per-message tree-shaking | Translations load per locale | | Missing translation | Falls back to the base locale; `inlang check` reports it | Generated by [`npx gt translate`](/docs/cli/reference/commands/translate); untranslated content renders in the source language | | Frameworks | Vite apps, React, Vue, Solid, SvelteKit, Astro, TanStack Start, React Router, Next.js (webpack) | React, Next.js, Vue, and Node.js; React Native, TanStack Start, and Python are experimental | -| Cost | Free and open source | Libraries free; translation usage-based | +| Cost | Free and open source | Libraries free; [usage-based translation](https://generaltranslation.com/pricing) | -Keep Paraglide JS when you build with SvelteKit, Astro, or Solid, when per-message tree-shaking and typed message functions matter most, or when your team already writes or buys its translations. Use General Translation when you want copy readable in your components and every language generated with the context of your code on each build. +Keep Paraglide JS when you build with SvelteKit, Astro, or Solid, or when per-message tree-shaking and typed message functions matter most. Choose General Translation when you want to edit copy directly in components and generate translations with code context before the build, with shared terminology and review in the Dashboard. ## Using Paraglide JS and General Translation together @@ -116,6 +116,6 @@ Moving one component at a time works. Paraglide's messages are plain functions a ## Key takeaways -- Paraglide JS compiles messages into typed, tree-shakable functions, with strong plural support, locale strategies, and free inlang tooling, but someone still writes every message in every language. +- Paraglide JS compiles messages into typed, tree-shakable functions, with plural support, locale strategies, and separate inlang tooling for generation and checks. - General Translation keeps copy inline in [``](/docs/react/reference/components/t) and generates translations with context from your code, your Glossary, and Custom Prompts, with review in the Dashboard. - Paraglide wins on per-message bundle size, typed messages, and framework reach beyond React and Vue; the `gt` CLI doesn't understand inlang variants, so migrate component by component instead of translating Paraglide's files. diff --git a/kb/general-translation-vs-rails-i18n.mdx b/kb/general-translation-vs-rails-i18n.mdx index b39ad53b8..e334f83e7 100644 --- a/kb/general-translation-vs-rails-i18n.mdx +++ b/kb/general-translation-vs-rails-i18n.mdx @@ -37,7 +37,7 @@ en:

<%= t(".unread", count: @count) %>

``` -Without rails-i18n, the default backend [applies English plural rules](https://guides.rubyonrails.org/i18n.html#pluralization) to every language (`one` for 1, `other` for the rest), and `l` raises for a locale with no date formats: in a test app on Rails 8.1.4, `l(date, format: :long)` in Polish failed with "Translation missing: pl.date.formats.long". With rails-i18n, Polish gets its own rules, where 2 and 22 take the `few` form and 5 takes `many`. If the Polish file has only English-shaped `one` and `other` keys, the I18n backend quietly uses `other` instead: +Without rails-i18n, the default backend [applies English plural rules](https://guides.rubyonrails.org/i18n.html#pluralization) to every language (`one` for 1, `other` for the rest), and, with fallbacks off (the default in development and test), `l` raises for a locale with no date formats: in a test app on Rails 8.1.4, `l(date, format: :long)` in Polish failed with "Translation missing: pl.date.formats.long". With rails-i18n, Polish gets its own rules, where 2 and 22 take the `few` form and 5 takes `many`. If the Polish file has only English-shaped `one` and `other` keys, the I18n backend quietly uses `other` instead: ```yaml title="config/locales/pl.yml" pl: @@ -105,7 +105,7 @@ npx gt translate && ruby script/fix_locale_roots.rb The script only touches the unindented root line, leaves comments and formatting alone, and does nothing to a file that's already correct, so it's safe to run every time. -That extra step buys you more than a raw translation API would. General Translation translates each file with your project's [Glossary and Custom Prompts](/docs/platform/dashboard/guides/defining-context-for-translations), so a product name or a formal tone holds across every locale file, and your team can [review and edit translations](/docs/platform/dashboard/guides/reviewing-translations) in the Dashboard. Unchanged entries reuse earlier translations, so running the CLI in CI on every merge only translates what changed. Pricing is usage-based, with a $0 platform fee and unlimited users, projects, and languages on Starter ([pricing](https://generaltranslation.com/pricing)). +That extra step buys you more than a raw translation API would. General Translation translates each file with your project's [Glossary and Custom Prompts](/docs/platform/dashboard/guides/defining-context-for-translations), so a product name or a formal tone holds across every locale file, and your team can [review and edit translations](/docs/platform/dashboard/guides/reviewing-translations) in the Dashboard. Unchanged entries reuse earlier translations, so running the CLI in CI on every merge only translates what changed. This example translates an English catalog into Spanish. Use this file workflow for messages with matching source and target key structures, and validate representative plural counts in the rendered app. Run `i18n-tasks check-consistent-interpolations` to check Rails `%{count}` placeholders before release. @@ -120,7 +120,7 @@ This example translates an English catalog into Spanish. Use this file workflow | Dates and numbers | Rails formats, with locale data from rails-i18n | Not applicable in Ruby | | Consistency across files | Up to your translators | Glossary and Custom Prompts applied to every file | | Review | Your pull request process | Dashboard plus your pull request process | -| Cost | Free, MIT | Usage-based | +| Cost | Free, MIT | [Usage-based translation](https://generaltranslation.com/pricing) | Use Rails I18n on its own when translators or a vendor already deliver your locale files, or when you support one or two languages and edit them by hand. Add General Translation when English changes often and you want every locale file regenerated in CI, with consistent terms, instead of waiting on a translation round trip. diff --git a/kb/general-translation-vs-symfony-translation.mdx b/kb/general-translation-vs-symfony-translation.mdx index 56c16b3f7..242565147 100644 --- a/kb/general-translation-vs-symfony-translation.mdx +++ b/kb/general-translation-vs-symfony-translation.mdx @@ -15,7 +15,7 @@ Symfony's Translation component is how a Symfony app shows text in the reader's - **Domains and many file formats.** Catalogs are named `domain.locale.format` (`messages.fr.yaml`, `validators.fr.yaml`), and Symfony loads YAML, XLIFF, PHP, JSON, PO and MO, CSV, INI, Qt, and ICU resource bundles. [Its docs recommend YAML for simple projects](https://symfony.com/doc/current/translation.html#translation-resource-file-names-and-locations) and XLIFF when specialized tools or teams produce translations. - **ICU MessageFormat.** [Add `+intl-icu` to a catalog's name](https://symfony.com/doc/current/reference/formats/message_format.html) (`messages+intl-icu.en.yaml`) and its messages use ICU syntax for plurals, selects, and number and date formats. This needs PHP's `intl` extension; Symfony's polyfill covers English only. - **Extraction and checks.** [`translation:extract` finds messages in Twig templates, PHP code, and validation constraints](https://symfony.com/doc/current/translation.html#extracting-translation-contents-and-updating-catalogs-automatically) and adds them to your catalogs, as XLIFF 1.2 unless you pass `--format`. `debug:translation` lists missing, unused, and fallback messages, and `lint:translations` checks catalog contents. -- **Fallbacks.** A missing `es_AR` message [falls back through `es_419` and `es` to your fallback locales](https://symfony.com/doc/current/translation.html#fallback-translation-locales), and the miss is logged. +- **Fallbacks.** A missing `es_AR` message [falls back through `es_419` and `es` to your fallback locales](https://symfony.com/doc/current/translation.html#fallback-translation-locales), and the profiler shows the miss in development. - **Translation providers.** [`translation:push` and `translation:pull` sync catalogs with Crowdin, Loco, Lokalise, and Phrase](https://symfony.com/doc/current/translation.html#translation-providers) through official bridge packages, and you can write your own provider. - **Framework messages included.** The Validator component ships its built-in constraint messages translated into more than 50 languages. @@ -66,7 +66,7 @@ es: Tienes 3 mensajes sin leer. de: You have 3 unread messages. ``` -Spanish works because a person translated it. German has no catalog, so Symfony quietly falls back to English: in a full app the miss is logged and `debug:translation de` lists it, but nothing writes the German. Every new key repeats the cycle in every language. `translation:extract --force` adds the key to each catalog, prefixed with `__` (or empty with `--no-fill`), and someone translates it, either by hand or in a TMS that the provider commands push to and pull from. A provider moves files; the translating, and the TMS plan it runs on, are still separate. +Spanish works because a person translated it. German has no catalog, so Symfony quietly falls back to English: in a full app the profiler shows the miss and `debug:translation de` lists it, but nothing writes the German. Every new key repeats the cycle in every language. `translation:extract --force` adds the key to each catalog, prefixed with `__` (or empty with `--no-fill`), and someone translates it, either by hand or in a TMS that the provider commands push to and pull from. A provider moves files; the translating, and the TMS plan it runs on, are still separate. ## How General Translation fits a Symfony app @@ -89,7 +89,7 @@ General Translation has no PHP SDK, so Symfony stays in charge of rendering, and A few details matter for Symfony: - **ICU messages.** The CLI sends YAML values as plain strings; its ICU-aware mode is reserved for next-intl and i18next projects. General Translation still checks that each translation keeps the source's ICU placeholders and plural structure, including exact branches such as `=0`. It translates the plural branches your English message has, so review languages that need more categories, such as Polish `few` and `many`. -- **Locale codes.** Language codes such as `es` and `fr` match Symfony's file names as they are. For regional variants, the CLI writes the BCP 47 code you configure (`messages.pt-BR.yaml`), while Symfony apps usually use `pt_BR`, and a `pt_BR` request doesn't load a `pt-BR` file. Use the same code on both sides, for example by setting the app's regional locales to `pt-BR`. +- **Locale codes.** Language codes such as `es` and `fr` match Symfony's file names as they are. For underscore-style regional names, map Symfony's `pt_BR` to BCP 47 `pt-BR` with [`customMapping`](/docs/cli/reference/config#custom-mapping): `"pt_BR": { "code": "pt-BR" }`. GT then writes `messages.pt_BR.yaml`, matching your existing Symfony locale. - **Node.js in CI.** The CLI is an npm package, so your CI job needs Node.js, plus `GT_API_KEY` and `GT_PROJECT_ID` in its secrets. (See [Configuring the CLI](/docs/cli/guides/configuring#credentials).) Beyond filling catalogs, General Translation keeps them consistent: a Context Group's Glossary and Custom Prompts apply the same terms and tone to every project it's assigned to, and your team reviews and edits translations in the Dashboard. (See [Defining context for translations](/docs/platform/dashboard/guides/defining-context-for-translations).) The tradeoff: it doesn't read XLIFF or PHP array catalogs or scan PHP and Twig code, so Symfony's extractor stays in the workflow. @@ -100,11 +100,11 @@ Beyond filling catalogs, General Translation keeps them consistent: a Context Gr | --- | --- | --- | | Role | Looks up and renders messages in PHP and Twig | Translates your catalogs; no PHP runtime library | | Translates your content | No; providers sync catalogs with Crowdin, Loco, Lokalise, or Phrase | Yes, with [`npx gt translate`](/docs/cli/reference/commands/translate) | -| Catalog formats | YAML, XLIFF, PHP, JSON, PO and MO, CSV, INI, Qt, ICU resource bundles | YAML and JSON for Symfony catalogs; no XLIFF or PHP arrays | +| Catalog formats | YAML, XLIFF, PHP, JSON, PO and MO, CSV, INI, Qt, ICU resource bundles | YAML, JSON, and PO for Symfony catalogs; no XLIFF or PHP arrays | | Finding new strings | `translation:extract` scans Twig, PHP, and constraints | Translates whatever is in the English catalogs | | ICU MessageFormat | `+intl-icu` catalogs with the `intl` extension | Keeps ICU placeholders and structure in translated YAML | | Missing translations | Fallback chain, logs, `debug:translation` | Writes every configured locale on each run | -| Cost | Free and MIT-licensed; providers bill separately | Usage-based; $0 platform fee and unlimited users, projects, and languages on [Starter](https://generaltranslation.com/pricing) | +| Cost | Free and MIT-licensed; providers bill separately | [Usage-based translation](https://generaltranslation.com/pricing) | ## When to use which @@ -112,9 +112,9 @@ This isn't an either-or choice for rendering: a Symfony app should keep Symfony ## Using both -1. Keep the catalogs you want translated in YAML. If they're XLIFF today, Symfony can dump a locale's catalog as YAML with `translation:extract --force --format=yaml`. -2. Add `gt.config.json` as above, and run [`npx gt translate`](/docs/cli/reference/commands/translate) in CI after extraction. -3. Send the translations you already have with [`npx gt upload`](/docs/cli/reference/commands/upload), which uploads source files and existing translations so the Dashboard starts from your current state. +1. Keep the catalogs you want translated in YAML. For an XLIFF project, work on a branch and run `php bin/console translation:extract --force --format=yaml en`, then repeat for each target locale. Preserve reviewed values, domains, dynamic messages, placeholders, `+intl-icu` messages, and any review metadata your team uses. Replace generated `__` values with source copy. Remove each XLIFF file only after its YAML replacement contains the complete catalog, and test the app with that XLIFF file absent. Keep the originals in Git history. +2. Add `gt.config.json` as above, then run [`npx gt upload`](/docs/cli/reference/commands/upload) to bring your sources and existing translations into GT before generating more. Review the imported catalogs in the Dashboard. +3. Run [`npx gt translate`](/docs/cli/reference/commands/translate) after a source edit. Review the diff, including previously approved messages. Run `php bin/console lint:translations` and `php bin/console debug:translation es` for each target locale, and render representative messages and plural counts before enabling generation in CI after extraction. For later local edits, use [`gt save-local`](/docs/cli/reference/commands/save-local) or opt into [`--save-local`](/docs/cli/reference/commands/translate) to send them back to GT before generating again. ## Key takeaways diff --git a/kb/general-translation-vs-tolgee.mdx b/kb/general-translation-vs-tolgee.mdx index f6f713bfe..c3bc88f3f 100644 --- a/kb/general-translation-vs-tolgee.mdx +++ b/kb/general-translation-vs-tolgee.mdx @@ -1,13 +1,13 @@ --- title: "General Translation vs. Tolgee: which should you use?" -summary: Tolgee is an open-source localization platform you can self-host or use as Tolgee Cloud, with in-context editing in your running app, SDKs, and AI translation. Engineers still create keys, sync them with its CLI, and pull translations back. General Translation translates content in your code with its context and ships every language with each release. +summary: General Translation keeps source copy in your components and uses its code context to generate translations, with Dashboard review and CLI or Locadex release automation. Tolgee connects keyed messages to an in-context editor and can be self-hosted. Choose GT to remove source-key maintenance; choose Tolgee when self-hosting or editing in the running app is essential. date: 2026-10-16 category: workflow-tools authors: [kevin-wu, jackie] related: [general-translation-vs-tms, general-translation-vs-weblate, general-translation-vs-locize, best-localization-software, i18n-without-translation-files] --- -Tolgee is an open-source localization platform: you give each string a key, sync the keys to a Tolgee project with its CLI or SDK, translate them there with machine translation, AI, and your team, and load the results back into the app as files or from Tolgee's CDN. You can run it yourself from a Docker image or use Tolgee Cloud. Its standout feature is in-context editing: hold ALT and click any text in your running app to translate it. What Tolgee leaves to engineers is the key-based loop around that editor. General Translation starts from the code instead: content stays in your components, and translation runs as part of the build. +Tolgee connects keyed messages in your app to a translation editor, automatic translation, and delivery. You can self-host it or use Tolgee Cloud, and reviewers can edit text in the running app. General Translation starts with the copy itself: developers write it inside components, and GT extracts the marked content with its code structure, generates translations, and prepares them for the release. ## What Tolgee does well @@ -18,7 +18,7 @@ Tolgee is an open-source localization platform: you give each string a key, sync - **AI with context.** [Machine translation](https://docs.tolgee.io/platform/translation_process/machine_translation) comes from DeepL, Google, Amazon, or Azure, and the AI Translator uses Claude or OpenAI models on Tolgee Cloud, or your own OpenAI, Azure OpenAI, Anthropic, Gemini, or OpenAI-compatible provider. It draws on screenshots, key and project descriptions, translation memory, and [key relationships the SDK records](https://docs.tolgee.io/platform/translation_process/ai_translator). - **Delivery options.** [Content Delivery](https://docs.tolgee.io/platform/projects_and_organizations/content_delivery) publishes translations to Tolgee's CDN or your own S3 or Azure storage, so a web app can pick up changes without a rebuild. -## What Tolgee still leaves to your engineers +## How does authoring differ? With Tolgee, every string in the UI is a key with a default value: @@ -50,16 +50,28 @@ Running `npx @tolgee/cli extract print` on this file (CLI 2.21.0) finds the two default: {count, plural, one {# unread message} other {# unread messages}} ``` -Extraction finds the keys, but getting new copy into every language is still a loop: +Those keys connect the component to Tolgee's editor and translation workflow: 1. Name a key for each string and write the English as its default value. 2. Create the keys in the Tolgee project with `tolgee push` or [`tolgee sync`](https://docs.tolgee.io/tolgee-cli/extraction/syncing-strings). -3. Wait while machine translation, the AI Translator, or your translators fill in each language, and review the results. +3. Let Tolgee's [automatic translation](https://docs.tolgee.io/platform/projects_and_organizations/machine-translation-settings) fill new keys, including keys the CLI pushes, with machine translation or the AI Translator, or wait for your translators, and review the results. 4. Run [`tolgee pull`](https://docs.tolgee.io/tolgee-cli/push-pull-strings) and commit the files, or set up Content Delivery and point the SDK at it, then add each new language to the app. -Until a key is translated, readers see the fallback language or the English default. And the AI Translator works from screenshots and descriptions that someone attaches, and from which keys appear together on screen, not from the component that renders the string. +These steps can run in an automated workflow. Until a translation is available, the SDK uses its configured fallback. Tolgee's AI context includes screenshots, descriptions, and relationships between keys recorded by the SDK. -Tolgee Cloud is priced mainly by hosted words, which count the base language and each translation separately, with machine translation credits per plan and unlimited seats on current plans. Self-hosted licenses are priced by keys and seats. (See [Tolgee pricing](https://tolgee.io/pricing), checked October 2026.) +Here is the same heading in each library: + +```tsx title="Tolgee" + +``` + +```tsx title="General Translation" +Inbox +``` + +The first uses [`T`](https://docs.tolgee.io/js-sdk/integrations/react/translating) from `@tolgee/react`; the second uses [``](/docs/react/reference/components/t) from `gt-react`, with each library's provider configured. In the GT version, developers edit the source text directly and the CLI extracts the marked content. For richer content, one block can include text and nested elements, preserving their relationship during translation. + +Tolgee Cloud is priced mainly by hosted words, which count the base language and each translation separately, with machine translation credits per plan and unlimited seats on paid Cloud plans (the free plan has 3). Self-hosted licenses are priced by keys and seats. (See [Tolgee pricing](https://tolgee.io/pricing), checked October 2026.) ## Tolgee vs. General Translation at a glance @@ -67,21 +79,21 @@ Tolgee Cloud is priced mainly by hosted words, which count the base language and | --- | --- | --- | | Where strings live | Keys with default values in your code, synced to a Tolgee project | In your code inside [``](/docs/react/reference/components/t), or in the resource files you already have | | How code connects | `@tolgee/cli` `push`, `pull`, and `sync`, or the SDK in development | [`npx gt translate`](/docs/cli/reference/commands/translate) in your build, or [Locadex](/docs/platform/locadex/quickstart) pull requests | -| Who translates | Machine translation, the AI Translator, or your translators | AI with context from your code, Glossary, and Custom Prompts, reviewed by your team in the Dashboard | -| Context for each string | Key and project descriptions, screenshots, translation memory, and key relationships | The component and elements around the string, its variables, context notes, Glossary, and Custom Prompts | +| Who translates | Automatic translation of new keys, batch translation, or your translators | Context-aware AI translation with shared Glossary and Custom Prompts; your team reviews in the Dashboard | +| Context for translation | Key and project descriptions, screenshots, translation memory, and key relationships | Inline blocks include marked element structure and explicit context; catalog translation uses file content and supplied metadata. Both use assigned Glossary and Custom Prompts | | In-app editing | ALT+click editing in the running app, including production with a browser extension | Review and edit in the Dashboard's Translation Editor | | Hosting | Self-hosted with Docker, or Tolgee Cloud | Hosted platform; open-source libraries and CLI | -| Pricing | Free plan; Cloud plans by hosted words; self-hosted licenses by keys and seats | Usage-based; $0 platform fee and unlimited users, projects, and languages on Starter | +| Pricing | Free plan; Cloud plans by hosted words; self-hosted licenses by keys and seats | [Usage-based translation](https://generaltranslation.com/pricing) | | Native mobile apps | iOS and [Android SDKs](https://docs.tolgee.io/android-sdk) with over-the-air updates | The CLI translates iOS and Android string files; no over-the-air SDK | ## Why teams choose General Translation over Tolgee -General Translation starts from the code, so there are no keys to name or sync. Copy stays in your components inside [``](/docs/react/reference/components/t) (which wraps the content itself, unlike Tolgee's key-based component of the same name), and translation runs with each release. +With General Translation, copy stays readable in your components, and its translations follow source changes through CI or Locadex. Developers write the sentence once; reviewers work in the Dashboard. -- **Faster.** [`npx gt translate`](/docs/cli/reference/commands/translate) runs in your build, or Locadex opens a pull request when code changes, so new copy ships translated with the release that adds it. Adding a language is one line in `gt.config.json`, and the next build translates the whole project into it. -- **Cheaper to run.** Engineers don't name keys, write default values, or pull files between systems. General Translation's pricing is usage-based, with a $0 platform fee and unlimited users, projects, and languages on Starter. (See [pricing](https://generaltranslation.com/pricing).) -- **Higher quality.** Tolgee's AI Translator draws on screenshots, key and project descriptions, translation memory, and the key relationships its SDK records. General Translation sends the context of the code with every translation: the elements around a string, the variables in the sentence, and the branches that choose between versions of it, plus your Glossary and Custom Prompts across every project. Nobody has to attach a screenshot first. -- **Lower risk.** There's no Tolgee project to keep in step with your repository through `push`, `pull`, and `sync`. General Translation's libraries and CLI are open source under the MIT license and work without an account, and translations can live as files in your repository, so you can change how translations are produced without rewriting your app. +- **Faster.** A copy edit is a component edit. [`npx gt translate`](/docs/cli/reference/commands/translate) extracts and translates changed content before the build; [Locadex](/docs/platform/locadex/quickstart) can handle the updates through pull requests. +- **Cheaper to run.** Engineers stop creating a key for every sentence. Translators, PMs, and in-country reviewers can use the [Translation Editor](/docs/platform/dashboard/guides/reviewing-translations) without per-seat fees; see [pricing](https://generaltranslation.com/pricing). +- **Higher quality.** GT sends text, nested elements, and branches from each marked block together, so the translation retains how the pieces relate. Explicit context explains meaning, while shared Glossary terms and Custom Prompts keep the app, docs, and marketing content consistent. Code context is available during extraction, without a screenshot step. +- **Lower risk.** GT's JavaScript libraries and CLI are MIT-licensed. Keep translated files in your repository and render them without a GT account or runtime service request. Reviewed translations stay available to your app independently of future generation. The tradeoff: General Translation's platform is a hosted service. If your translations must stay on servers you run, Tolgee's self-hosted edition is the option. @@ -89,18 +101,8 @@ Ramp localizes its dashboard, landing page, and iOS app into 11+ locales with Ge ## When Tolgee is the better fit -Choose Tolgee when you need to self-host the whole platform, when translators or product managers want to edit text by clicking it in the running app, when your frontend is Angular or Svelte, when you exchange [XLIFF, ARB, or CSV files](https://docs.tolgee.io/platform/supported_formats), or when native iOS and Android apps need over-the-air string updates. Choose General Translation when most of your product's text lives in React, Next.js, or Vue code and you want each release to ship in every language without a key-sync step. +Choose Tolgee when you need to self-host the whole platform, when translators or product managers want to edit text by clicking it in the running app, when your frontend is Angular or Svelte, when you exchange [XLIFF, ARB, or CSV files](https://docs.tolgee.io/platform/supported_formats), or when native iOS and Android apps need over-the-air string updates. Choose General Translation when you want readable inline copy, code context for translation, and automatic updates that ship with your releases, with review in the Dashboard. ## Moving from Tolgee to General Translation Move one feature at a time. Replace each `` with General Translation's [``](/docs/react/reference/components/t) around the English content, and each `t('key', 'default')` with [`useGT`](/docs/react/reference/hooks/use-gt). If your team wants to keep keys, put the English values in a dictionary and read them with [`useTranslations`](/docs/react/reference/hooks/use-translations). Keep loading Tolgee's translations until each feature is migrated. (See [Migrating i18n libraries](/docs/react/guides/migrating-i18n-libraries).) Bring your glossary over too: Context Group imports accept CSV, TSV, JSON, and text files. (See [Defining context for translations](/docs/platform/dashboard/guides/defining-context-for-translations).) - -## Is General Translation an alternative to Tolgee? - -Yes, when most of your product's text lives in React, Next.js, or Vue code. General Translation translates that content in your build, with no keys to create or sync. Tolgee remains the better fit when you need to self-host the platform or want translators to edit text in the running app. - -## Key takeaways - -- Tolgee is an open-source, self-hostable localization platform with in-context editing, SDKs for many stacks, and AI that draws on screenshots and descriptions. -- Engineers still name keys, sync them with the CLI, wait for translations, and pull the results back into the app. -- General Translation translates content where it's written, with the context of the code, and ships every language with each release, on MIT-licensed libraries with no lock-in. diff --git a/kb/general-translation-vs-weblate.mdx b/kb/general-translation-vs-weblate.mdx index e23e7bcb8..3457788cb 100644 --- a/kb/general-translation-vs-weblate.mdx +++ b/kb/general-translation-vs-weblate.mdx @@ -13,7 +13,7 @@ Weblate is an open-source, web-based translation platform built around Git. You - **Copyleft open source.** Weblate is [licensed under GPL-3.0-or-later](https://github.com/WeblateOrg/weblate), and you can download and run the whole platform at no cost, with paid support plans if you want help installing and maintaining it. - **Free hosting for libre projects.** Hosted Weblate runs on EU infrastructure, and public libre projects can get its Libre plan at no charge, with the same limits as its 160k-string plan. -- **Git-based continuous localization.** Weblate pulls changes when your forge sends a webhook (or on a daily fetch), commits translations with lazy commits that group changes by author, and pushes them to your branch or [opens pull requests](https://docs.weblate.org/en/latest/vcs.html) on GitHub, GitLab, Gitea, Bitbucket, Azure DevOps, and Pagure. Hosted Weblate also connects through a GitHub App. +- **Git-based continuous localization.** Weblate pulls changes when your forge sends a webhook (or on a daily fetch, when `AUTO_UPDATE` is turned on), commits translations with lazy commits that group changes by author, and pushes them to your branch or [opens pull requests](https://docs.weblate.org/en/latest/vcs.html) on GitHub, GitLab, Gitea, Bitbucket, Azure DevOps, and Pagure. Hosted Weblate also connects through a GitHub App. - **Very broad file support.** Weblate's docs list [about 50 formats](https://docs.weblate.org/en/latest/formats.html), including gettext PO, XLIFF 1.2 and 2.0, Android and Apple strings, ARB, i18next JSON, Fluent, Java properties, .NET RESX, Ruby YAML, PHP and Laravel strings, Qt Linguist, CSV, Excel, and Markdown. - **Add-ons for file chores.** [Built-in add-ons](https://docs.weblate.org/en/latest/admin/addons.html) update PO files from a POT template, generate MO files, squash commits, and run automatic translation. - **Machine translation and LLMs.** [Integrations](https://docs.weblate.org/en/latest/admin/machine.html) include DeepL, Google, Azure, Amazon, and LibreTranslate, plus OpenAI, Azure OpenAI, Anthropic, Mistral, and Ollama, whose prompts include matching glossary terms and your style settings, alongside Weblate's own translation memory. @@ -57,20 +57,20 @@ Self-hosting is free. Hosted Weblate is priced by hosted strings, which are sour | Where strings live | Translation files in your repository, mirrored in Weblate components | In your code inside [``](/docs/react/reference/components/t), or in the resource files you already have | | How code connects | Weblate pulls from Git, then pushes commits or opens pull requests | [`npx gt translate`](/docs/cli/reference/commands/translate) in your build, or [Locadex](/docs/platform/locadex/quickstart) pull requests | | Who translates | Volunteers, your translators, machine translation, or LLMs | AI with context from your code, Glossary, and Custom Prompts, reviewed by your team in the Dashboard | -| Context for each string | Source references, screenshots, glossary, and translation memory | The component and elements around the string, its variables, context notes, Glossary, and Custom Prompts | +| Context for each string | Source references, screenshots, glossary, and translation memory | Inline blocks carry their marked element structure and context notes; catalogs carry file content and metadata. Assigned Context Groups supply Glossary and Custom Prompts | | File formats | About 50, including XLIFF, ARB, Fluent, and Java properties | JSON, YAML, PO, Markdown and MDX, iOS and Android strings, and more; no XLIFF, ARB, Fluent, or properties | | Hosting | Self-hosted for free, or Hosted Weblate | Hosted platform; open-source libraries and CLI | -| Pricing | Self-hosted free; hosted plans by hosted strings; free Libre plan for public libre projects | Usage-based; $0 platform fee and unlimited users, projects, and languages on Starter | -| License | GPL-3.0-or-later | MIT-licensed libraries and CLI | +| Pricing | Self-hosted free; hosted plans by hosted strings; free Libre plan for public libre projects | [Usage-based translation](https://generaltranslation.com/pricing) | +| License | GPL-3.0-or-later | MIT-licensed CLI and JavaScript libraries | ## Why teams choose General Translation over Weblate General Translation starts from the code, so the round trip above goes away: copy stays in your components inside [``](/docs/react/reference/components/t), and translation runs with each release. - **Faster.** [`npx gt translate`](/docs/cli/reference/commands/translate) runs in your build, or Locadex opens a pull request when code changes, so new copy ships translated with the release that adds it instead of waiting for a commit window. Adding a language is one line in `gt.config.json`, and the next build translates the whole project into it. -- **Cheaper to run.** There's no server to install, upgrade, or back up, and engineers don't extract templates or resolve translation merge conflicts. General Translation's pricing is usage-based, with a $0 platform fee and unlimited users, projects, and languages on Starter. (See [pricing](https://generaltranslation.com/pricing).) -- **Higher quality.** Weblate's translators and LLM prompts work from source references, screenshots, glossary terms, and translation memory, not the code that renders the string. General Translation sends the context of the code with every translation: the elements around a string, the variables in the sentence, and the branches that choose between versions of it, plus your Glossary and Custom Prompts across every project. -- **Lower risk.** No second system commits to the same files, so there are no translation merge conflicts to avoid by locking components. General Translation's libraries and CLI are open source under the MIT license and work without an account, and translations can live as files in your repository, so you can change how translations are produced without rewriting your app. +- **Cheaper to run.** There's no server to install, upgrade, or back up, and engineers don't extract templates or resolve translation merge conflicts. +- **Higher quality.** In GT's inline workflow, text, elements, variables, and branches inside each marked block are translated together. Explicit notes clarify meaning, and [Context Groups](/docs/platform/dashboard/guides/defining-context-for-translations) keep terminology and tone consistent across assigned projects. Teams retaining catalogs get the same shared Glossary and Custom Prompts alongside their file metadata. +- **Lower risk.** No second system commits to the same files, so there are no translation merge conflicts to avoid by locking components. General Translation's JavaScript libraries and CLI are open source under the MIT license and work without an account, and translations can live as files in your repository, so you can change how translations are produced without rewriting your app. The tradeoff: General Translation's platform is a hosted service, and it's built for AI translation reviewed by your team, not for a community of volunteer translators. @@ -111,4 +111,4 @@ Yes, for teams whose text lives in React, Next.js, or Vue code, or in JSON, YAML - Weblate is a strong copyleft platform for open-source and community translation, with Git-based continuous localization, free self-hosting, and about 50 file formats. - Engineers still extract strings into translation files, wait for each language, and merge Weblate's commits before readers see new copy translated. -- General Translation translates content where it's written, with the context of the code, and ships every language with each release, on MIT-licensed libraries with no lock-in. +- General Translation translates content where it's written, with the context of the code, and ships every language with each release, on MIT-licensed JavaScript libraries with no lock-in.