Internationalization¶
The gallery ships translations for Spanish (es), Basque/Euskara (eu), and French (fr). English is the source language — strings live untranslated in the templates and the .po msgid is the English text.
How it works at runtime¶
LocaleMiddleware(inMIDDLEWARE, positioned afterSessionMiddlewareand beforeCommonMiddleware) inspects the user'sAccept-Languageheader.- Django picks the best match from
LANGUAGES(defined in the consumer's settings). - Strings wrapped in
{% trans %}/{% blocktranslate %}are looked up in the.mofor that language. - Misses fall back to the
LANGUAGE_CODEdefault.
The library's translations live under src/django_cotton_gallery/locale/<lang>/LC_MESSAGES/django.{po,mo}. Django auto-discovers them because django_cotton_gallery is in the consumer's INSTALLED_APPS. No LOCALE_PATHS setting required.
Adding a new translatable string¶
When you add a {% trans "X" %} or {% blocktranslate %}...{% endblocktranslate %} block to a template, the .po files don't know about it yet. Three options to add the translation:
Option A — Makefile targets (canonical)¶
The repo ships Makefile targets that wrap Django's makemessages / compilemessages and require system gettext (apt install gettext on Debian/Ubuntu, brew install gettext on macOS):
make messages # extract new msgids into the .po files (es / eu / fr)
# Edit the .po files: replace the empty msgstr "" with the translation.
make compile-messages # compile .po → .mo
Under the hood these cd into src/django_cotton_gallery/ (the directory that contains locale/, so only our package catalogs are scanned) and run python -m django makemessages / compilemessages for es / eu / fr. If you need to drive it by hand:
cd src/django_cotton_gallery
uv run --project ../.. python -m django makemessages -l es -l eu -l fr --extension=html,py,txt
uv run --project ../.. python -m django compilemessages -l es -l eu -l fr
Note: use
python -m django, notdjango-admin— the latter is not onPATHin theuvenvironment.
Option B — polib (optional fallback, no system gettext)¶
If you can't install system gettext, use the polib Python library instead (already in [dev]) to edit and compile the catalogs directly:
import polib
from pathlib import Path
NEW_STRINGS = {
"Click me": {
"es": "Hacé clic",
"eu": "Egin klik",
"fr": "Cliquez ici",
},
}
base = Path("src/django_cotton_gallery/locale")
for lang in ["es", "eu", "fr"]:
po_path = base / lang / "LC_MESSAGES" / "django.po"
mo_path = base / lang / "LC_MESSAGES" / "django.mo"
po = polib.pofile(str(po_path))
for msgid, langs in NEW_STRINGS.items():
if po.find(msgid) is None:
po.append(polib.POEntry(msgid=msgid, msgstr=langs[lang]))
po.save(str(po_path))
po.save_as_mofile(str(mo_path))
Option C — edit the .po directly + recompile¶
Manually edit src/django_cotton_gallery/locale/<lang>/LC_MESSAGES/django.po to add the entry, then recompile:
import polib
po = polib.pofile("src/django_cotton_gallery/locale/es/LC_MESSAGES/django.po")
po.save_as_mofile("src/django_cotton_gallery/locale/es/LC_MESSAGES/django.mo")
Verifying translations applied¶
After updating .mo files, restart the dev server and hit the page with the right Accept-Language:
curl -s -H "Accept-Language: es" http://localhost:8000/django-cotton-gallery/get-started/ | grep -o 'Empezar\|De cero a tu primer componente'
Or open the page in a browser, change the system language preference, and refresh.
For a visual check, capture screenshots in each language with headless Chrome:
google-chrome --headless=new --disable-gpu --hide-scrollbars --window-size=1600,1100 \
--virtual-time-budget=8000 --lang=eu --accept-lang=eu \
--screenshot=/tmp/gallery-eu.png http://localhost:8000/django-cotton-gallery/
Adding a new language¶
- Create the directory:
- Copy an existing
.poas a starting point:
cp src/django_cotton_gallery/locale/es/LC_MESSAGES/django.po \
src/django_cotton_gallery/locale/<code>/LC_MESSAGES/django.po
-
Update the header of the new
.po:Language: <code>\nandContent-Language: <code>. -
Translate every
msgstrin the file — all UI strings. -
Compile:
import polib
po = polib.pofile("src/django_cotton_gallery/locale/<code>/LC_MESSAGES/django.po")
po.save_as_mofile("src/django_cotton_gallery/locale/<code>/LC_MESSAGES/django.mo")
-
Tell consumers about it by adding the language to the
LANGUAGESsetting in their project. Until they add it, Django won't expose it inset_languageeven though the.mois shipped. -
Document the new language in the README and on the docs site.
Common gotchas¶
- Translation doesn't apply in the browser → check that
LocaleMiddlewareis inMIDDLEWARE. Without it, Django never readsAccept-Language. - Translation is in the
.pobut the page still shows English → the.mois stale. Recompile. makemessageserrors with "no .po files found" → run it fromsrc/django_cotton_gallery/(the directory that containslocale/), not the repo root. Themake messagestarget alreadycds there for you.- Plural forms render as singular → check that
Plural-Formsis set in the.poheader (e.g.nplurals=2; plural=(n != 1);for languages like es/en/fr). - Euskera translations need review — the maintainer doesn't speak Basque fluently. Native-speaker contributions welcome.