Skip to Content

PMIS Translation Overrides

Maintain translations for multiple languages for Odoo 18 / OCB in one repository. Slovenian (sl) and Italian (it) are configured initially. Edit complete PO catalogs; the tooling reconciles upstream updates, generates sparse overrides, and distributes them to the original modules' i18n_extra directories. No wrapper addons or changes to an upstream Python/XML source are needed.

Structure

i18n-extra.json                Source checkout, addon paths, languages, module selection
translations/
  upstream/<module>/          Shared template.pot and per-language PO snapshots
  working/<module>/<lang>.po   Complete catalogs to edit (sl.po, it.po, ...)
  state.json                  Upstream revision and content fingerprints
  report.json                 Conflicts and retired overrides
  generated/<module>/<lang>.po Generated sparse overrides (committed)
  generated/manifest.json      Generated file hashes and ownership
  generated/build.json         Portable build input fingerprints
tools/i18n_extra.py            Discovery, synchronization, build, deployment
tools/i18n-extra               Shell command launcher
tools/completion/              Bash tab completion
tests/                        Synthetic catalog and Git-repository tests

LICENSE contains this repository's existing license. Upstream catalog snapshots originate from the selected OCB checkout; retain its applicable licensing terms.

Requirements and configuration

Use Python 3.12 or newer, polib from requirements.txt, Git, and GNU gettext's msgfmt for building. Deployment needs Python, polib, and Git, but does not run gettext. The existing PMIS Odoo interpreter already supplies polib; no Odoo import, server, database connection, or additional Python package is needed here.

The commands below run from this repository and use the local suite interpreter:

I18N_PYTHON=/srv/pmiverse/_pmisuite18/venv/bin/python
"$I18N_PYTHON" tools/i18n_extra.py discover

The checked-in configuration selects base, web, and sale as a starting set. Change modules to ["*"] to discover every addon in the configured addon paths, or supply a list of module technical names. Module selection is configuration, not a separate setup step per module. Installed modules are not queried from a database in this version.

source is relative to this repository; addon_paths are relative to that Git checkout. Both odoo/addons and addons are included. Duplicate module names are rejected instead of silently choosing one.

Configure languages as a list in i18n-extra.json:

{
  "languages": ["sl", "it"]
}

All commands process every configured language for every selected module. To add another language, append its PO language code and run sync; existing corrections are retained. If an upstream PO does not exist, the new working catalog starts with untranslated template entries. Language codes must be distinct; their list order does not affect the result. The original single-language state is upgraded by sync without moving existing PO files.

Removing a language from configuration preserves its working catalogs and snapshots. After sync, build retires its generated files and deploy previews removal of its owned deployed files. Re-adding the language resumes from its preserved baseline. Database retirement still requires the reconciliation described below.

The defaults sl and it match upstream Slovenian and Italian catalogs. If deployment uses a more specific locale with its own upstream PO, configure that locale, for example sl_SI: the tool merges that language's base and locale translations and writes the override under the specific locale. Spanish locales also use Odoo's es_419 fallback where applicable. Odoo can otherwise load a specific-locale catalog after a base-language override.

Bash shortcut and tab completion

The i18n-extra launcher and Bash completion follow the _pmiconfig shortcut pattern. From this repository, enable them for the current shell:

export I18N_PYTHON=/srv/pmiverse/_pmisuite18/venv/bin/python
export PATH="$PWD/tools:$PATH"
source /usr/share/bash-completion/bash_completion
source tools/completion/i18n-extra.bash

This requires the bash-completion package. If it is already loaded, only the repository completion file needs sourcing. To enable this in future terminals, place the corresponding commands in your Bash profile using absolute repository paths instead of $PWD and relative paths. No shell profile or system files are modified by this repository's tools.

i18n-extra sy<TAB>             → sync
i18n-extra deploy --a<TAB>     → --apply
i18n-extra --root <TAB>        → directories

Completion offers the four subcommands, their supported flags, and directory paths for --root (including --root=...). Root options precede the subcommand. Completing an argument does not run Python, inspect catalogs, or perform deployment operations.

The launcher uses the exported I18N_PYTHON executable, or python3 from PATH when unset; Python 3.12 or newer and polib are still required. It works from any directory, including when symlinked into a directory on PATH. Completion supports the shortcut and direct tools/i18n-extra invocation. Existing explicit Python commands remain available.

The launcher and every subcommand accept --help or -h:

i18n-extra --help
i18n-extra discover --help
i18n-extra sync --help
i18n-extra build --help
i18n-extra deploy --help

Help includes command descriptions, supported options, and examples. It does not load project configuration or perform synchronization, generation, or deployment.

Initial setup and editing

"$I18N_PYTHON" tools/i18n_extra.py sync

This creates full editable catalogs in translations/working. They include existing upstream translations and untranslated template entries. Edit those files with a PO editor, for example translations/working/sale/sl.po or translations/working/sale/it.po. Keep source text, Odoo extraction comments, and references intact; the tool gets deployment references from the upstream snapshots.

sync reads only checked-in, unmodified source catalogs. It never pulls Git, extracts translations, or writes to OCB. Missing templates fail with the affected path before any catalog writes; provide a current upstream template or explicitly narrow module selection. A checked-in POT is the source of truth for this version: the tool cannot prove that it reflects all source-code changes. Regenerating stale templates requires a separately authorized Odoo extraction workflow.

Upstream snapshots are normalized to remove translator identities and unrelated header churn. Source byte hashes still record the exact input files used. Commit the baseline, working catalogs, state, and report together after review; the tools never stage, commit, or push changes.

After an upstream update

Run these steps after your existing deployment process updates the OCB checkout:

"$I18N_PYTHON" tools/i18n_extra.py sync
"$I18N_PYTHON" tools/i18n_extra.py build
"$I18N_PYTHON" tools/i18n_extra.py deploy

Synchronization compares the previous upstream snapshot, your working catalog, and the current upstream catalog:

Change Result
No local correction Adopt the upstream translation
Local correction, unchanged upstream text Preserve the correction and refresh references
Upstream now matches the correction Stop generating that override
Both translations changed differently Preserve the correction, mark fuzzy, report both values
Source message disappears or changes Preserve the old correction as obsolete; use the new upstream entry
Retired source reappears Restore the correction as fuzzy for explicit review
Module deselected Preserve its working files; retire its deployed overrides
Language deselected Preserve its working files; retire only that language's deployed overrides

There are no automatic fuzzy matches or machine translations. Resolve competing translations in the working PO and clear the fuzzy flag after review. Conflicts remain flagged across repeated synchronization until resolved. Obsolete corrections remain available for reference and are excluded from generated output. New untranslated entries do not block a build. The report groups results by module and language. Any unresolved conflict blocks the complete build/deployment before output writes; corrections in other languages remain intact.

build refuses unresolved fuzzy entries, changed source keys, empty overrides, missing Odoo references, invalid percent placeholders, and gettext format errors. Gettext plural/context overrides are explicitly rejected because this tool targets Odoo 18's translation loader. It checks only your overrides, not the linguistic quality or format correctness of unchanged upstream translations. HTML/XML content and actual rendered behavior still need application acceptance.

Generated output contains only translated entries that differ from upstream. Unchanged outputs are not rewritten. Changes to configured languages or upstream catalog hashes require synchronization before build or deployment; unrelated upstream commits do not.

Commit translations/generated/ together with the working catalogs, snapshots, state, and report after a successful build. Include both manifest.json and build.json, even when there are no overrides. They record artifact hashes and portable input fingerprints; do not edit generated files by hand.

After updating this repository and the corresponding OCB checkout on a remote instance, the only translation file preparation command needed there is:

i18n-extra deploy --apply

No remote sync or build is needed. The configured source path must resolve to the target checkout, whose selected upstream catalogs must match the synchronized baseline. If they differ, synchronize and build on the authoring instance and publish the updated files first. Database activation remains a separate step.

Deployment ownership

deploy is a read-only preview. To write the reviewed deployment:

"$I18N_PYTHON" tools/i18n_extra.py deploy --apply

Deployment verifies build input fingerprints, the manifest, and generated file hashes, then copies the exact built bytes without regenerating or running gettext. Missing or changed artifacts and stale working catalogs block deployment; run build and publish the outputs first. It writes the configured source checkout only; deployments elsewhere should run this repository's tooling alongside that checkout with the corresponding configuration.

The tool refuses tracked upstream destinations, existing unowned files, symlinks, and modifications to files from its previous deployment. Existing upstream i18n_extra files are not merged or taken over. A local receipt under .cache/ records hashes of files the tool owns. Keep that receipt: without it, existing files are deliberately treated as unowned. Retired files are removed only when their bytes still match the receipt. Unselected working catalogs are preserved.

When overrides are present, deploy --apply adds **/i18n_extra/ once to the source repository's local Git info/exclude file. Existing rules and comments are preserved; upstream .gitignore and global Git settings are never changed. Git resolves the metadata location, so linked worktrees use their shared exclude file. The JSON git_exclude field previews append or unchanged with the path and rule; preview mode writes nothing. It is null when no overrides remain. The rule stays in place after retirement and covers all untracked i18n_extra directories in that repository, not only files owned by this tool. Higher-priority .gitignore rules can override local excludes. Ignoring a file does not protect it from future upstream ownership; tracked and unowned destination checks still apply. Symlinked excludes and detected concurrent edits are refused. An exclusion write failure prevents PO deployment; if a later PO write fails, the rule may already be present and can safely remain for the next run. Run one synchronization/deployment process per project and avoid concurrent PO edits during synchronization. File writes are atomic individually, not a transaction over the entire checkout.

Maintainer: apply translation updates from the command line

No Odoo web login is required. Use the server's existing maintenance account, suite interpreter, configuration, and database list. Start with the normal backup and maintenance window. This workflow treats addon PO files and this repository's overrides as authoritative, so it can replace translations edited in a database.

1. Update repositories and deploy translations

On the authoring instance, synchronize, review corrections, and build:

"$I18N_PYTHON" tools/i18n_extra.py sync
"$I18N_PYTHON" tools/i18n_extra.py build

Commit and publish the synchronized catalogs and complete translations/generated/ directory together. After the usual repository updates on the target instance, run from this repository before upgrading the databases:

"$I18N_PYTHON" tools/i18n_extra.py deploy --apply

Resolve reported conflicts before building. Keep the previous deployed PO files in the release backup when retiring corrections.

2. Include translations in the usual database upgrade

Add --i18n-overwrite to the existing -u all command. For example, set these values for the target instance and repeat the upgrade for each maintained database:

I18N_ODOO_BIN=/srv/pmiverse/_pmisuite18/repos/OCB/odoo-bin
I18N_CONFIG=/path/to/odoo.conf
I18N_DATABASE=example_database

"$I18N_PYTHON" "$I18N_ODOO_BIN" \
  -c "$I18N_CONFIG" -d "$I18N_DATABASE" \
  -u all --i18n-overwrite --stop-after-init --no-http

Odoo upgrades the installed modules and loads their normal and i18n_extra translations for installed languages. Plain -u all does not overwrite existing translations by default. No separate per-module PO import is needed.

For a database that also needs Slovenian and Italian activated, add --load-language=sl_SI,it_IT to that command. These are database language codes; the corresponding PO filenames are sl.po and it.po. Do not activate additional languages on databases that do not need them.

3. Restart the serving workers

Check each upgrade's exit status and log, then restart the Odoo service through the usual server maintenance procedure. The upgrade updates database terms; restarting the serving workers refreshes Python/JavaScript translation caches. Users may need to refresh their browsers.

Reconciliation: only when an old correction is not restored

The upgrade also restores removed overrides when upstream provides a nonempty translation and the target record allows overwrite. It cannot clear an old value using an empty upstream translation, and ordinary overwrite preserves noupdate records.

For those exceptions, the translation maintainer should provide a reviewed, targeted restoration PO for the affected database and language. It must contain only the affected current references, with msgstr set to the desired upstream text, or to msgid to display the source text. Empty msgstr values do nothing. The same method can apply a reviewed correction to a protected record.

Database contents can be obtained without a web login. For example, export the current Slovenian translations for sale to prepare or verify that restoration:

"$I18N_PYTHON" "$I18N_ODOO_BIN" \
  -c "$I18N_CONFIG" -d "$I18N_DATABASE" \
  --i18n-export=/path/to/sale-current-sl.po --modules=sale --language=sl_SI

Compare affected entries with the previous deployed corrections before replacing them; independently changed values and renamed references need a translation maintainer's decision. Keep database exports outside Git.

After the upgrade, apply the reviewed file to the intended database from the shell:

export I18N_RESTORE_PO=/path/to/reviewed-restoration-sl.po
export I18N_RESTORE_LANG=sl_SI

"$I18N_PYTHON" "$I18N_ODOO_BIN" shell \
  -c "$I18N_CONFIG" -d "$I18N_DATABASE" --no-http <<'PY'
import os
from odoo.tools.translate import TranslationImporter

language = os.environ["I18N_RESTORE_LANG"]
if not env["res.lang"].search_count([("code", "=", language), ("active", "=", True)]):
    raise ValueError(f"Language must be active: {language}")
importer = TranslationImporter(env.cr)
with open(os.environ["I18N_RESTORE_PO"], "rb") as translation_file:
    importer.load(translation_file, "po", language)
importer.save(overwrite=True, force_overwrite=True)
env.cr.commit()
PY

Use it_IT for an Italian restoration. This deliberately bypasses noupdate for the supplied entries, so use only the reviewed sparse file, never a full export or upstream catalog. Missing references are not repaired by forcing the import. Re-export the affected terms to verify the result, then complete the worker restart from step 3. Keep restoration files outside working and deployed catalogs.

Current limitation: database upgrades activate translations from the command line, but this repository does not yet generate retirement/restoration plans. Exceptional reconciliation still needs the targeted file described above. These instructions follow the local Odoo 18 implementation; no database operations were executed as part of this documentation change.

Validation

"$I18N_PYTHON" -m unittest discover -s tests -v
bash tests/test_completion.sh

Tests use synthetic temporary Git repositories and catalogs. They cover three-way updates, persistent conflicts, obsolete entries, placeholder validation, locale fallback, language isolation/addition/retirement, single-language state migration, deployment preview/application/retirement, and preservation of unowned, tracked, modified, and symlinked destinations. They do not use an Odoo database.

Review

sl-review.md

TODO

  • Generate targeted restoration plans for retired overrides during database activation.