Skip to content
Closed
10 changes: 5 additions & 5 deletions kb/general-translation-vs-django-i18n.mdx
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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 %}
<p>{% blocktranslate count counter=count %}{{ counter }} file{% plural %}{{ counter }} files{% endblocktranslate %}</p>
```

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 && "
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down
6 changes: 2 additions & 4 deletions kb/general-translation-vs-flask-babel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
6 changes: 3 additions & 3 deletions kb/general-translation-vs-gettext.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down
12 changes: 6 additions & 6 deletions kb/general-translation-vs-i18n-tasks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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.

Expand All @@ -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.

Expand All @@ -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.
2 changes: 1 addition & 1 deletion kb/general-translation-vs-in-house-i18n.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading