About odoo-i18n
odoo-i18n is a skill for developers managing Odoo translation and localization across versions 14–19. It provides workflows to extract strings, validate and report on .po files, and import or export translations through Odoo CLI.
Key features
- 01Extracts translatable strings
- 02Validates .po syntax and content
- 03Reports translation gaps
- 04Merges and converts .po files
- 05Imports and exports with Odoo CLI
- 06Checks language-specific rules
Use cases
- 01Generate translation files for an Odoo module
- 02Validate .po files before release
- 03Track incomplete translation coverage
- 04Load translations into an Odoo database
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ ahmed-lakosha/ odoo-plugins --skill odoo-i18nAfter installing, type/odoo-i18nin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
scripts/__init__.py_common.pyi18n_converter.pyi18n_extractor.pyi18n_reporter.pyi18n_validator.pyDetails
From the project READMEOdoo i18n Skill
Provides deep expertise in Odoo internationalization (i18n) and localization (l10n) across Odoo 14-19.
Critical Translation Rules
- NEVER use f-strings inside
_()-- use%formatting:_('Record %s') % name - NEVER concatenate strings inside
_()-- give the translator the full sentence - Use
_lt()for class-level strings (selection values, class attributes) - NEVER wrap
string=field attributes in_()-- they are auto-translated by Odoo - Save
.pofiles as UTF-8 without BOM - Arabic
.pofiles must havenplurals=6in Plural-Forms header - Always update the module after editing
.pofiles:-u module --stop-after-init
Workflow 1: Extract Translatable Strings
Scans an Odoo module for all translatable strings and generates .pot (template) and .po (language) files.
Usage
python ${CLAUDE_PLUGIN_ROOT}/odoo-i18n/scripts/i18n_extractor.py --module <path> --lang <code> [--output <dir>] [--no-pot] [--verbose]
| Argument | Required | Description |
|---|---|---|
--module | Yes | Path to the Odoo module directory |
--lang | Yes | Target language code (e.g., ar, fr, tr) |
--output | No | Custom output directory (default: module/i18n/) |
--no-pot | No | Skip generating .pot template |
--verbose | No | Show all extracted strings |
What Gets Extracted
Python (*.py) -- _('...') and _lt('...'):
# Extracted:
raise UserError(_('Record %s not found') % name)
state = fields.Selection([('draft', _lt('Draft'))])
# NOT extracted: _(variable), _(f'Hello {name}')
XML (*.xml) -- string=, help=, placeholder= attributes; name= on menus/actions; HTML text:
<field name="state" string="Status"/> <!-- extracted -->
<h1>Welcome to our website</h1> <!-- extracted -->
<record id="view_my_form" model="ir.ui.view"> <!-- NOT extracted -->
JavaScript (*.js) -- _t('...') and _lt('...'):
const msg = _t("Save Changes"); // extracted
const msg = _t(someVariable); // NOT extracted
Generated File Structure
# Translation template for my_module
msgid ""
msgstr ""
"Project-Id-Version: Odoo Module my_module\n"
"Content-Type: text/plain; charset=UTF-8\n"
#: models/my_model.py:45
#, python-format
msgid "Record %s not found"
msgstr ""
Alternative: Odoo's Built-in Extractor
python odoo-bin -c conf/myproject.conf -d mydb \
--i18n-export --modules=my_module --language=ar \
--output=my_module/i18n/ar.po --stop-after-init
The Odoo CLI extractor includes database strings (model names, action names) that the plugin extractor does not. For production, the built-in extractor is more complete.
After Extraction
- Open the
.pofile in a translation editor (Poedit, Virtaal, or text editor) - Fill in all
msgstrentries - Validate: run
i18n_validator.py --po-file path/to/ar.po - Check coverage: run
i18n_reporter.py --module path/ --lang ar - Load into Odoo: update module or use export/import workflow
Workflow 2: Validate .po Files
Validates a .po file for syntax errors, encoding issues, empty translations, fuzzy entries, format specifier mismatches, and Arabic/RTL-specific problems.
Usage
python ${CLAUDE_PLUGIN_ROOT}/odoo-i18n/scripts/i18n_validator.py --po-file <path> [--lang <code>] [--strict] [--output <file>]
| Argument | Required | Description |
|---|---|---|
--po-file | Yes | Path to the .po file to validate |
--lang | No | Language code for language-specific checks (auto-detected from filename) |
--strict | No | Treat untranslated strings as errors instead of warnings |
--output | No | Write report to a file instead of stdout |
What is Validated
Syntax: UTF-8 encoding (no BOM/Latin-1), header with Content-Type/charset/Language/MIME-Version, properly quoted strings, no parse errors.
Translations: Empty msgstr (untranslated), fuzzy flags needing review, duplicate msgid, obsolete (#~) entries.
Format Specifiers: %s/%d/%f count must match; %(name)s named specifiers must all appear:
# WRONG - missing second %s:
msgid "Invoice %s due on %s"
msgstr "fatura %s"
# CORRECT:
msgid "Invoice %s due on %s"
msgstr "fatura %s vadesi %s"
Arabic-Specific (--lang ar): Arabic characters present, encoding artifacts from Latin-1, direction control chars, nplurals=6 required, BIDI overrides flagged.
Common Errors and Fixes
"Charset must be UTF-8":
python ${CLAUDE_PLUGIN_ROOT}/odoo-i18n/scripts/i18n_converter.py --action convert --po ar.po --output ar_fixed.po
"Fuzzy translation (needs review)" -- remove the fuzzy flag after verifying:
# Before: # After:
#, fuzzy, python-format #, python-format
msgid "Record %s not found" msgid "Record %s not found"
msgstr "record not found" msgstr "record not found"
"Arabic should have nplurals=6":
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
CI/CD Integration
- name: Validate Arabic translations
run: python odoo-i18n/scripts/i18n_validator.py --po-file my_module/i18n/ar.po --lang ar --strict
Exit code 0 = passed (may have warnings), 1 = failed (errors found).
Workflow 3: Find Missing Translations
Compares translatable strings from a module's source files against an existing .po file to report what is missing or incomplete.
Usage
python ${CLAUDE_PLUGIN_ROOT}/odoo-i18n/scripts/i18n_reporter.py --module <path> --lang <code> [--format text|json|csv] [--output <file>] [--min-pct <N>]
| Argument | Required | Description |
|---|---|---|
--module | Yes | Path to the Odoo module directory |
--lang | Yes | Language code to check (e.g., ar, fr) |
--format | No | Output format: text (default), json, csv |
--output | No | Write report to a file instead of stdout |
--min-pct | No | Exit code 1 if completion below threshold |
Understanding the Report
- Missing: String in source code but NO entry in
.po - Empty in .po: Entry exists but
msgstris""(not yet translated) - Fuzzy: Auto-matched, needs human review; NOT shown to users (Odoo falls back to source)
- Completion % = Translated / Total Active Strings * 100 (non-empty, non-fuzzy, non-obsolete)
Workflow: Fixing Missing Translations
- Run
i18n_reporter.pyto identify gaps - Open
.poin Poedit, Virtaal, or text editor - Fill in missing
msgstrentries - If strings are entirely absent from
.po, runi18n_extractor.pyfirst - Validate with
i18n_validator.py - Re-run
i18n_reporter.pyto confirm coverage
Updating .po After Source Changes
# 1. Re-extract new strings
python ${CLAUDE_PLUGIN_ROOT}/odoo-i18n/scripts/i18n_extractor.py --module /path/ --lang ar --no-pot
# 2. Merge (preserves existing translations)
python ${CLAUDE_PLUGIN_ROOT}/odoo-i18n/scripts/i18n_converter.py \
--action merge --base /path/i18n/ar.po --new /path/i18n/ar.po --output /path/i18n/ar_merged.po
# 3. Check remaining gaps
python ${CLAUDE_PLUGIN_ROOT}/odoo-i18n/scripts/i18n_reporter.py --module /path/ --lang ar
Workflow 4: Export/Import via Odoo CLI
Requires PostgreSQL and a valid database.
Exporting Translations
# Single module
python odoo-bin -c conf/myproject.conf -d mydb \
--i18n-export --modules=my_module --language=ar \
--output=my_module/i18n/ar.po --stop-after-init
# Export .pot template (no --language = empty msgstr)
python odoo-bin -c conf/myproject.conf -d mydb \
--i18n-export --modules=my_module \
--output=my_module/i18n/my_module.pot --stop-after-init
# Multiple modules / all installed
python odoo-bin -c conf/myproject.conf -d mydb \
--i18n-export --modules=mod1,mod2 --language=ar --output=combined_ar.po --stop-after-init
Importing Translations
# Import .po
python odoo-bin -c conf/myproject.conf -d mydb \
--i18n-import=my_module/i18n/ar.po --language=ar --modules=my_module --stop-after-init
# Import and overwrite existing
python odoo-bin -c conf/myproject.conf -d mydb \
--i18n-import=my_module/i18n/ar.po --language=ar \
--modules=my_module --i18n-overwrite --stop-after-init
# Multiple languages
for lang in ar fr tr; do
python odoo-bin -c conf/myproject.conf -d mydb \
--i18n-import=my_module/i18n/${lang}.po --language=${lang} \
--modules=my_module --stop-after-init
done
Loading Languages
python odoo-bin -c conf/myproject.conf -d mydb --load-language=ar --stop-after-init
python odoo-bin -c conf/myproject.conf -d mydb --load-language=ar,fr,tr --stop-after-init
Check/activate via shell:
# python odoo-bin shell -d mydb
langs = self.env['res.lang'].with_context(active_test=False).search([])
for lang in langs:
print(f"[{'ACTIVE' if lang.active else 'inactive'}] {lang.code}: {lang.name}")
# Activate
lang = self.env['res.lang'].with_context(active_test=False).search([('code', '=', 'ar')])
lang.active = True
self.env.cr.commit()
Module Update (Simplest Reload)
python -m odoo -c conf/myproject.conf -d mydb -u my_module --stop-after-init
Odoo 17+ Specifics
Translations stored as JSON terms internally; export format still .po. Clear cache:
# python odoo-bin shell -d mydb
self.env['ir.translation'].clear_caches()
self.env.cr.commit()
Website Translations
python odoo-bin -c conf/myproject.conf -d mydb \
--i18n-export --modules=website,my_theme_module --language=ar \
--output=website_ar.po --stop-after-init
Translation File Locations
module/i18n/
module.pot <- Template (not loaded by Odoo)
ar.po <- Arabic
ar_SA.po <- Saudi variant (overrides ar.po entries)
fr.po <- French
tr.po <- Turkish
Utility: Merge and Clean .po Files
python ${CLAUDE_PLUGIN_ROOT}/odoo-i18n/scripts/i18n_converter.py --action merge --base ar.po --new ar_new.po --output ar_merged.po
python ${CLAUDE_PLUGIN_ROOT}/odoo-i18n/scripts/i18n_converter.py --action clean --po ar.po
python ${CLAUDE_PLUGIN_ROOT}/odoo-i18n/scripts/i18n_converter.py --action stats --po ar.po
python ${CLAUDE_PLUGIN_ROOT}/odoo-i18n/scripts/i18n_converter.py --action convert --po ar.po --output ar_fixed.po
Supported Languages Quick Reference
| Code | Language | RTL? | Plural Forms |
|---|---|---|---|
ar / ar_SA / ar_AE | Arabic (variants) | Yes | 6 |
en / en_US | English | No | 2 |
fr / fr_FR | French | No | 2 |
tr | Turkish | No | 2 |
de | German | No | 2 |
es | Spanish | No | 2 |
Odoo Version Differences
| Feature | Odoo 14-15 | Odoo 16-17 | Odoo 18-19 |
|---|---|---|---|
| JS translation import | import { _t } from "web.core" | import { _t } from "@web/core/l10n/translation" | Same as 16 |
| Bootstrap version | 4.x | 5.1.3 (RTL built-in) | 5.1.3+ |
| Translation storage | ir.translation table | JSON terms (internal) | JSON terms |
| View type for lists | <tree> | <tree> | <list> (19 only) |
| Controller type | type='json' | type='json' | type='jsonrpc' (19 only) |
| Visibility attrs | attrs={'invisible': ...} | attrs={'invisible': ...} | Inline invisible="expr" (19 only) |
Common Issues
Translations not showing: Update the module with -u module --stop-after-init, then clear browser cache.
Arabic text garbled (mojibake): File saved as Latin-1. Run i18n_converter.py --action convert to fix encoding.
Format specifier error: Ensure %s/%d/%(name)s count matches between msgid and msgstr.
Website shows English for Arabic users: Check (1) Arabic language installed, (2) user language set, (3) module updated after adding translations.
Fuzzy entries: Open .po, verify translation, remove #, fuzzy flag from approved entries.
Configurable Branding
Generated .pot/.po files use configurable author/email. Set environment variables:
export ODOO_I18N_COPYRIGHT="Your Company"
export ODOO_I18N_BUGS_EMAIL="[email protected]"
Detailed Reference (Memory Files)
For comprehensive patterns beyond this skill summary, the plugin includes memory files:
memories/translation_patterns.md-- Python_()/_lt()patterns, XML attribute translation, JS_t()patterns, .po file structure, email template translationmemories/rtl_patterns.md-- RTL activation, Bootstrap RTL, CSS logical properties, SCSS overrides, Flexbox in RTL, JS RTL detection, QWeb report RTL, common RTL fixesmemories/language_codes.md-- Language codes, date/time formats, number formats, currency display, Hijri calendar notes, timezone reference