Translation Management Guide¶
This document describes how to manage translations in the Coati Payroll system.
Overview¶
The system uses Flask-Babel for internationalization (i18n). Source strings are in Spanish and are translated to English.
Supported languages:
- es - Español (Spanish) - Source language
- en - English - Translation target
Directory Structure¶
coati_payroll/
├── translations/ # Translation files directory
│ ├── en/ # English translations
│ │ └── LC_MESSAGES/
│ │ ├── messages.po # Editable translation catalog
│ │ └── messages.mo # Compiled binary (auto-generated)
│ └── es/ # Spanish translations
│ └── LC_MESSAGES/
│ ├── messages.po # Editable translation catalog
│ └── messages.mo # Compiled binary (auto-generated)
├── babel.cfg # Babel extraction configuration
└── messages.pot # Translation template (source)
Translation Workflow¶
1. Extract Translatable Strings (Create .pot template)¶
When you add new translatable strings to the code or templates, extract them to create/update the translation template:
# From the project root directory
pybabel extract -F babel.cfg -k _ -k _l -k _n:1,2 -o messages.pot .
What this does:
- Scans all Python files (**.py) and Jinja2 templates (**/templates/**.html)
- Looks for translation functions: _(), _l(), _n()
- Creates/updates messages.pot with all found strings
2. Initialize New Language (One-time only)¶
To add a new language (only needed once per language):
# Initialize Spanish catalog (already done)
pybabel init -i messages.pot -d coati_payroll/translations -l es
# Initialize English catalog (already done)
pybabel init -i messages.pot -d coati_payroll/translations -l en
Note: This step is only needed when adding a completely new language.
3. Update Translation Catalogs (After extracting strings)¶
After extracting new strings to messages.pot, update the language-specific .po files:
# Update Spanish catalog
pybabel update -i messages.pot -d coati_payroll/translations -l es
# Update English catalog
pybabel update -i messages.pot -d coati_payroll/translations -l en
What this does:
- Merges new strings from messages.pot into existing .po files
- Marks new strings as needing translation
- Preserves existing translations
- Marks removed strings as obsolete (but keeps them commented)
4. Translate Strings¶
Edit the .po files manually to add translations:
For Spanish (es):
# Edit: coati_payroll/translations/es/LC_MESSAGES/messages.po
# Spanish is the source language, so msgstr should match msgid
For English (en):
# Edit: coati_payroll/translations/en/LC_MESSAGES/messages.po
# Translate Spanish strings (msgid) to English (msgstr)
Example .po file format:
# Spanish string (msgid) → English translation (msgstr)
msgid "Usuario"
msgstr "User"
msgid "Contraseña"
msgstr "Password"
msgid "Guardar"
msgstr "Save"
5. Compile Translations (Create .mo files)¶
After editing translations, compile them to binary .mo files:
# Compile all languages
pybabel compile -d coati_payroll/translations
# Or compile specific language
pybabel compile -d coati_payroll/translations -l en
pybabel compile -d coati_payroll/translations -l es
What this does:
- Converts human-readable .po files to binary .mo files
- .mo files are used by the application at runtime
- Must be done before testing translations
Important: Always compile after editing .po files!
Complete Workflow Example¶
When you modify code and add new translatable strings:
# 1. Extract new strings from source code
pybabel extract -F babel.cfg -k _ -k _l -k _n:1,2 -o messages.pot .
# 2. Update language catalogs
pybabel update -i messages.pot -d coati_payroll/translations -l es
pybabel update -i messages.pot -d coati_payroll/translations -l en
# 3. Edit the .po files to add translations
# For English: coati_payroll/translations/en/LC_MESSAGES/messages.po
nano coati_payroll/translations/en/LC_MESSAGES/messages.po
# 4. Compile to binary .mo files
pybabel compile -d coati_payroll/translations
# 5. Restart the application to see changes
# (or just reload the page if using Flask debug mode)
Translation Functions in Code¶
Use these functions to mark strings as translatable:
Python Code¶
from coati_payroll.i18n import _, _l, _n
# Regular translation
message = _("Usuario creado exitosamente")
# With variable interpolation
message = _("Bienvenido %(name)s", name=user.nombre)
# Lazy translation (for forms and module-level strings)
field_label = _l("Nombre de usuario")
# Plural forms
message = _n("%(num)d empleado", "%(num)d empleados", count)
Jinja2 Templates¶
<!-- Simple translation -->
<h1>{{ _('Bienvenido') }}</h1>
<!-- With variable -->
<p>{{ _('Usuario: %(username)s', username=current_user.usuario) }}</p>
<!-- In attributes -->
<input type="text" placeholder="{{ _('Ingrese su nombre') }}">
Configuration¶
babel.cfg¶
The extraction configuration is in babel.cfg:
Flask-Babel Setup¶
In coati_payroll/__init__.py:
# Configure Flask-Babel
app.config["BABEL_DEFAULT_LOCALE"] = "en"
app.config["BABEL_TRANSLATION_DIRECTORIES"] = "translations"
babel.init_app(app, locale_selector=get_locale)
Language Selection¶
Default Language¶
The default language is English (en), but the initial language can be set using the COATI_LANG environment variable:
# Set initial language to Spanish
export COATI_LANG=es
# Set initial language to English (default)
export COATI_LANG=en
Database Configuration¶
The user can change the language through the web interface at Configuración → Configuración Global. The selected language is stored in the configuracion_global table and cached for performance.
Caching¶
Language settings are cached to avoid repeated database queries. The cache is automatically invalidated when the user changes the language.
Troubleshooting¶
Translations not appearing¶
-
Check if .mo files exist:
-
Recompile translations:
-
Restart the application:
- In development, Flask should auto-reload
- In production, restart the web server
New strings not extracted¶
- Check babel.cfg paths are correct
- Verify translation functions are used:
_(),_l(),_n() - Re-run extraction command
Language not switching¶
- Check database: Ensure
configuracion_globaltable exists - Check cache: Language cache might need invalidation
- Check locale selector: Verify
get_locale()function in__init__.py
Development Tips¶
- Always extract first: Run
pybabel extractbeforepybabel update - Update, then translate: Use
pybabel updateto merge new strings, then edit.pofiles - Compile before testing: Always run
pybabel compileafter editing.pofiles - Use msgfmt for validation: Check
.pofiles for errors:
Quick Reference¶
| Task | Command |
|---|---|
| Extract strings | pybabel extract -F babel.cfg -k _ -k _l -k _n:1,2 -o messages.pot . |
| Update catalogs | pybabel update -i messages.pot -d coati_payroll/translations |
| Compile translations | pybabel compile -d coati_payroll/translations |
| Initialize new language | pybabel init -i messages.pot -d coati_payroll/translations -l <code> |
| Check .po file | msgfmt --check <path-to-messages.po> |