Muffin-Babel
Muffin-Babel is an extension for the Muffin web framework that adds internationalization (i18n) support using Babel.
Requirements
- Python >= 3.11
Installation
Install via pip:
pip install muffin-babel
Usage
Basic Setup
import muffin
import muffin_babel
app = muffin.Application("example")
# Initialize the plugin
babel = muffin_babel.Plugin()
babel.setup(app, locale_folders=["src/locale"])
Inside a Route
@app.route("/")
async def index(request):
assert babel.current_locale
return babel.gettext("Hello World!")
Locale Selector
The locale is detected for every request by the plugin's selector. By default it uses the
Accept-Language header (muffin_babel.select_locale_by_request), but it can be replaced:
@babel.locale_selector
async def get_locale(request):
return request.query.get("lang") or await muffin_babel.select_locale_by_request(request)
The selector receives the request and returns a locale name (or None to fall back to the
default_locale). Detection is only active when auto_detect_locale is enabled.
Current Locale
babel.current_locale # -> babel.Locale, default_locale until a request sets it
babel.current_locale = "ru" # unknown locales are silently ignored
with babel.locale_ctx("ru"):
babel.gettext("Hello World!")
Translation Functions
| Function | Description |
|---|---|
gettext(string, domain=None, **variables) |
Translate a string with the current locale |
ngettext(singular, plural, num, domain=None, **variables) |
Translate with plural forms, num chooses the form |
pgettext(context, string, domain=None, **variables) |
Like gettext but with a context |
npgettext(context, singular, plural, num, domain=None, **variables) |
Like ngettext but with a context |
get_translations(domain=None, locale=None) |
Load the babel.support.Translations catalog itself |
babel.gettext("Hello %(user)s", user="John") # -> "Hello John"
babel.ngettext("%(num)s apple", "%(num)s apples", 2) # -> "2 apples"
Variables are interpolated with the %-style mapping syntax. A missing or unusable variable is
logged and the untranslated string is returned. Catalogs are loaded on demand and cached per
(domain, locale) for the process lifetime, so recompiled .mo files require a restart.
Jinja2 Integration
With the muffin-jinja2 plugin installed, Muffin-Babel
adds the jinja2.ext.i18n extension to the Jinja2 environment on startup and installs
gettext (with %(name)s interpolation) and ngettext as template globals. Both use the current
domain and locale:
<h1>{{ gettext('Welcome!') }}</h1>
Set configure_jinja2=False (or BABEL_CONFIGURE_JINJA2 = False in the app config) to leave the
templates untouched.
Plugin Options
| Option | Default | Description |
|---|---|---|
auto_detect_locale |
True |
Middleware for automatic locale detection |
configure_jinja2 |
True |
Enable i18n in the muffin-jinja2 plugin |
default_locale |
"en" |
Default fallback locale |
domain |
"messages" |
Default domain name for translation files |
locale_folders |
["locales"] |
Folders to search for translation files |
sources_map |
[("**.py", "python"), ("**.html", "jinja2")] |
File pattern to extractor method mapping |
options_map |
{"**.html": {"encoding": "utf-8"}} |
Options for the extractors |
Options can be passed directly during setup:
babel.setup(app, default_locale="fr")
Or set via Muffin application config using the BABEL_ prefix:
BABEL_DEFAULT_LOCALE = "fr"
Note: Muffin config keys are case-insensitive. Unknown options are ignored silently, typos in option names are not reported.
Commands
The plugin registers commands for the Muffin manage CLI.
Extract Messages
Scan your sources and write (or update) a .po catalog in the first locale_folders entry —
<locale_folders[0]>/<locale>/LC_MESSAGES/<domain>.po:
$ muffin app_package babel-extract-messages [OPTIONS] [DIRNAMES ...]
DIRNAMES— directories to scan (sources_mapdecides which extractor is used)--project— project name in the header (default: the application name)--domain— catalog domain (default:messages)--no-locations— do not write source line references--charset— output charset (default:utf-8)--locale— locale of the catalog to write (default:default_locale)
Compile Messages
Compile every <locale>/LC_MESSAGES/<domain>.po of all locale_folders into .mo binaries:
$ muffin app_package babel-compile-messages [OPTIONS]
--use-fuzzy— compile fuzzy entries as well--domain— catalog domain (default:messages)
Export as CSV
Print a .po catalog as CSV (id,string,context,comment) to stdout:
$ muffin app_package babel-export-csv [OPTIONS]
--domain— catalog domain (default:messages)--locale— locale to export (default:default_locale)
This helps with sending strings to translators or spreadsheets.
Workflow
babel-extract-messagescollects strings from the sources into a.pofile.- Translators fill in the
.pofile (or work on the exported CSV). babel-compile-messagesproduces the.mofiles the application loads on demand.- Restart the application — the compiled catalogs are cached per process.
Example
The example/ directory contains a runnable application with Russian translations:
uv run uvicorn example:app --port 5000
Then open http://localhost:5000/?lang=ru. The same command is available as make example.
Contributing
Development happens at: https://github.com/klen/muffin-babel
Feel free to open issues or pull requests.
Bug Tracker
Found a bug? Have a suggestion? Report it here: 👉 https://github.com/klen/muffin-babel/issues
License
Licensed under the MIT license
Author
klen (Kirill Klenov)
Metadata
Release files for muffin-babel 1.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| muffin_babel-1.9.0.tar.gz | 8.0 kB | Without provenance | Details for muffin_babel-1.9.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| muffin_babel-1.9.0-py3-none-any.whl | Python 3 | none | any | Details for muffin_babel-1.9.0-py3-none-any.whl |
Total release size: 15.8 kB
Release files / muffin_babel-1.9.0.tar.gz
| Download URL | muffin_babel-1.9.0.tar.gz |
|---|---|
| Size | 8.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
67c6e155b047f6203eddb6ccc16902b99d3c09b9fafc12a4a53dc581b28df751
|
|
BLAKE2b-256 checksum How to use checksums |
b3c71ebf64141e8a5dfe0316c43175c301e604fea0d1df82124b2dbf35e05b6e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / muffin_babel-1.9.0-py3-none-any.whl
| Download URL | muffin_babel-1.9.0-py3-none-any.whl |
|---|---|
| Size | 7.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2a81cb2af33bdf7e63284dd458b96ecde9605fae5c00d178ca6cfcf7c79a2095
|
|
BLAKE2b-256 checksum How to use checksums |
564e5972c683fe12eaa821aa7aa0edb44927952178f01e00affefa6761afd5e7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for muffin-babel 1.9.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.