Translate complete messages
with Python t-strings¶
gettext-tstrings connects Python 3.14+ t-strings to standard gettext
catalogs and Babel tooling. Values and formatting stay in application code;
translators work with complete messages and simple {name} placeholders:
import gettext
from gettext_tstrings import Translator
_ = Translator(gettext.translation("messages", localedir="locales"))
name = "Ada"
print(_(t"Hello {name}")) # with a Japanese catalog: こんにちは Ada
The catalog contains Hello {name}. A translation may move or repeat {name}.
If it removes, renames, or reformats the placeholder, catalog validation reports
the error. If an invalid entry still reaches production, the library logs a
warning and renders the source message instead of crashing.
Start the five-minute tutorial Compare the alternatives
Alpha · Python 3.14+ · standard PO/MO catalogs · no third-party runtime dependencies
This site practices what it documents: every language edition —
navigation, labels, and the plural-aware build report — is rendered from PO
catalogs by
gettext-tstrings itself.
Is this for you?¶
A fit today when your application runs on Python 3.14 or newer; you already use gettext and Babel, or want to adopt their PO/MO workflow; and you want t-string syntax with named placeholders that are checked before they render.
Not yet a fit when you need Python 3.13 or older; you require a stable Python API — this is an alpha, and the specification is the part of it that has settled; or nearly all of your translatable text lives in a template language rather than in Python source.
Already have catalogs? They keep working. _("Hello {name}").format(name=name)
and tr(t"Hello {name}") produce the same msgid, so existing translations
survive the switch — Migration walks the whole move.
What the catalog may say¶
A translation cannot change the structure of the message it translates. That
is the whole promise, and the rest of this site follows from it. A translation may
reorder or repeat {name}, and may rewrite every other word around it. It may
not drop the placeholder, invent a new one, reach through it into your objects,
or attach formatting of its own.
The library checks that on the way in — when catalogs are compiled — and again at render time, which is the difference between a mistake found in review and a mistake found by a user.
New to gettext? The whole workflow in four sentences
gettext is the standard way software gets translated, in Python and
far beyond. Your code marks translatable messages; an extractor collects
them into a template file (.pot); a translator — usually not a
programmer — fills in one catalog file (.po) per language, which is
compiled to a binary .mo that your application loads at runtime. The
conventional name for the translate function is _, so _(t"Hello {name}")
reads as "translate this message". The tutorial walks
the entire path — mark, extract, translate, compile, run — in about five
minutes.
The problem it solves¶
An f-string is already interpolated by the time any library sees it —
f"Hello {name}" has become "Hello Ada", and translating the fragments
around a value breaks the grammar of most languages. A t-string (PEP 750)
keeps the static text, the evaluated values, the source expressions, the
conversions, and the format specs separate — which is exactly the split a
message catalog needs.
What that changes, compared to %(name)s, .format(), and
$-strings.
Nothing in gettext or Babel says how a t-string becomes a message, though. This library makes that choice, writes it down as a versioned specification, and ships the conformance suite to check it.
The design rules¶
- Translate complete messages, never sentence fragments.
- Accept only simple variable names such as
{name}. - Keep
!rand:.2funder application control, out of the catalog. - Allow translations to reorder and repeat known placeholders, while preventing them from reaching attributes or adding formatting.
- Reuse ordinary POT, PO, and MO files, and the tools that already read them.
And the matching list of what it deliberately leaves alone: it does not localize numbers, currencies, or dates — format those first, with Babel; it does not escape rendered output for HTML, a shell, or a terminal; and it cannot judge whether a translation is correct, only whether its placeholders are intact.
Install¶
Python 3.14 or newer. Rendering has no dependencies — it uses the standard
library's gettext and nothing else.
Extraction and catalog validation run through Babel, so install that extra
wherever pybabel runs, which is usually a development or CI environment rather
than a production image:
Where to go next¶
Start here — no gettext experience assumed:
- Tutorial — an empty directory to a running Japanese translation in five steps, every command shown with its output.
- Why t-strings — the same message written four ways, and
what
%(name)s,.format(), and$-strings each hand to the catalog.
Use it — the working references:
- Guide — the runtime API: which entry point to use, plurals, per-request languages, deferred strings, and what happens when a catalog is wrong.
- Extraction — the
pybabelreference: configuration, custom function names, and how existing tools validate these catalogs for free. - In production — the loop as a team runs it: the update cycle, fuzzy entries, CI gates, translation platforms, and shipping.
- Migration — adopting this in a project that already has catalogs, one call site at a time.
- For translators — one page to hand to whoever edits
the
.pofiles.
Understand it — from history to implementation:
- Background — why this library exists: thirty years of gettext, two PEPs, and the stdlib discussion that closed without an answer.
- Pitfalls — what translating this site into thirty-five languages actually broke, and which half a tool can catch.
- How it works — from PEP 750's template object to the rendered string, and the caches that make the checking cheap.
Reference — the contracts:
- API — everything the package exports, on one page.
- Specification — the t-string ↔ msgid convention as a stable, versioned contract, with a machine-readable conformance suite.
Status¶
| Package version | 0.1.0a8 |
| API stability | alpha — the Python API may still change |
| Specification | v1, with a conformance suite |
| Python | 3.14 and newer; tested on 3.14, 3.14t (free-threaded), and 3.15 |
| Babel | 2.18 or newer, and only where pybabel runs |
| Runtime dependencies | none — the standard library's gettext |
| Catalog format | ordinary POT, PO, and MO |
| Changes | CHANGELOG |
An alpha. The contract is small on purpose and the specification is the stable part of it; the Python API may still move. Before a stable release this needs broader language fixtures, sustained performance tracking, API review from people who use gettext and Babel in earnest, and compatibility testing across every supported Python and Babel release.
Issues and pull requests are welcome — an alpha is exactly when the interface is still worth arguing about.
Join the community¶
- Pick a good first issue for a bounded contribution.
- Ask usage questions in Q&A Discussions.
- Bring production gettext workflows and API ideas to Ideas Discussions.
- Read the contribution guide before opening a pull request.