Skip to content

Background

This library sits at the meeting point of two long stories — one about how software gets translated, one about how Python interpolates strings — that finally intersected in 2025 and then stalled at exactly the point where a small, careful convention was needed. This page tells both stories, with links to the sources, because the design decisions on this site are easier to judge when you can see the questions they answer.

The gettext ecosystem

GNU gettext has been how free software gets translated since the mid-1990s: mark the strings in code, extract them into a template, give translators one catalog file per language, compile, load at runtime. Around that loop grew an entire ecosystem — PO editors, review workflows, and translation platforms that all speak the same file format — and Python has shipped a gettext module in its standard library for more than two decades. The runtime half of translation was never the problem.

The unsettled half was always what the catalog string looks like. A %(name)s message hands translators printf syntax that one deleted letter turns into a production crash; a .format() message hands the catalog attribute access on live objects. (Why t-strings walks through both, with the failures on display.) And f-strings — the syntax most Python code now prefers — cannot participate at all: by the time any library sees one, it is already a finished string. People try anyway, often enough that Babel's issue tracker collects the attempts (#594, #715); the failure is structural, not a missing feature.

Two PEPs, ten years apart

In 2015, Alyssa Coghlan and Nick Humrich wrote PEP 501, proposing interpolation templates whose stated first motivation was i18n — "providing a cleaner syntax for i18n translation", in the PEP's own words. The proposal was deferred, in part because discussion showed the i18n case carried significant extra considerations that simpler use cases did not.

A decade later, PEP 750 — by Jim Baker, Guido van Rossum, Paul Everitt, Koudai Aono, Lysandros Nikolaou, and Dave Peck — revived the idea as t-strings, was accepted in April 2025, and shipped in Python 3.14 in October 2025. PEP 501 was then withdrawn in its favor. One detail matters for this page: i18n is not among PEP 750's stated motivations. The PEP generalized the mechanism — a template type any library can consume — and left the translation question exactly where PEP 501 had parked it ten years earlier: open.

So as of Python 3.14, the language had precisely the data structure a message catalog needs, and no convention for using it as one.

The stdlib discussion

Two months before 3.14 shipped, Adrian Mönnich (ThiefMaster, a maintainer of the Indico project) proposed closing that gap in the standard library itself: the thread Support t-strings in gettext on discuss.python.org, opened in August 2025, came with a working pull request adding t-string support to both gettext and pygettext.

The thread is worth reading in full, because it surfaces every hard question this library later had to answer:

  • What may an interpolation be? A simple name only, or attributes and calls with a derived placeholder name? Every answer trades convenience against msgid stability and catalog safety.
  • What do plural forms require, when the target language's plural system differs from the source's?
  • Is gettext even the right target? Barry Warsaw — who had argued during PEP 750's development that t-strings were not a good fit for i18n — pointed to his flufl.i18n and its $-string style as the friendlier tool; others argued for leaving gettext behind entirely in favor of newer systems such as Fluent.
  • And the meta-question: whatever the standard library ships, it can essentially never change. A convention with this many open choices is a risky thing to freeze on the first try.

No consensus formed. The CPython issue was closed as "not planned" and the pull request was closed unmerged in October 2025, days after 3.14's release. The capability existed in the language; the convention had no home.

Why a package, first

That is the gap this project chose to fill from outside the standard library, on a deliberate wager: a convention matures faster where it can version freely and earn adoption case by case, and the standard library — which must be right the first time — is where a convention should end up, not where it should be worked out.

Concretely, every contested question in the thread has a written-down answer here, each on its own page:

  • Interpolations are simple names only, so msgids stay stable and meaningful — the guide shows the rule, How it works the reasons.
  • Formatting stays out of the catalog entirely (Why t-strings).
  • Plurals follow a union/intersection rule that lets a target language's plural system differ from the source's (spec §4).
  • A broken catalog falls back instead of crashing, keeping gettext's own contract (the guide).
  • And the whole convention is a versioned specification with a machine-readable conformance suite — written so that another implementation, including a future standard-library one, could adopt it unchanged and interoperate.

The discussion has not ended, and this project is a participant in it, not a verdict on it. If you have production gettext experience that bears on these choices, the same thread and this repository's Discussions are where the discussion continues.

Timeline

When What happened
mid-1990s GNU gettext establishes the PO/POT/MO workflow that translators and platforms still speak.
2015 PEP 501 proposes interpolation templates, with i18n as its first motivation; deferred.
2016 f-strings ship in Python 3.6 — interpolation gets its syntax, and translation cannot use it.
Jul 2024 PEP 750 proposes t-strings.
Apr 2025 PEP 750 accepted; PEP 501 withdrawn in its favor.
Aug 2025 The Support t-strings in gettext thread opens, with a stdlib pull request.
Oct 2025 Python 3.14 ships t-strings; the stdlib issue closes as not planned.
2026 gettext-tstrings ships as an alpha, with spec v1 and its conformance suite.