Multilingual support is a fundamental requirement for any business application serving users across different regions. Odoo addresses this with a built-in internationalization (i18n) mechanism that allows the entire interface to be translated without writing any custom translation logic.
Module Structure
Every Odoo module stores its translation files inside an i18n folder, using the .po (Portable Object) format named after ISO language codes.
reward_addons/
├── models/
│ └── reward_program.py
├── views/
│ └── reward_program_views.xml
├── i18n/
│ └── id.po
├── __manifest__.py
└── __init__.py
When a user selects a language in their account settings, Odoo automatically reads the corresponding .po file and renders the interface in that language.
What Odoo Can Translate
Odoo scans the module source code during installation or update and collects all eligible text from these sources: field labels, help text, selection values, XML view text, validation error messages, and SQL constraint messages.
1. Field Labels
Defined via the string attribute on field declarations:
reward_type = fields.Selection(
selection=[('points', 'Reward Points'), ('cashback', 'Cashback')],
string='Reward Type',
)
Odoo detects "Reward Type" and generates this entry in the .po file:
#: model:ir.model.fields,field_description:reward_addons.field_reward_program__reward_type
msgid "Reward Type"
msgstr "Tipe Reward"
2. Selection Values
Each option label inside a selection field is also detected and exported separately:
msgid "Reward Points"
msgstr "Poin Reward"
msgid "Cashback"
msgstr "Cashback"
Once translated, users will see the localized label in the dropdown automatically and no changes needed in the application logic.
3. Help Text
Defined via the help attribute and displayed as a tooltip on hover:
min_transaction_amount = fields.Float(
string='Minimum Transaction Amount',
help='Minimum purchase amount to earn reward points.\n0 = No minimum.',
)
Odoo picks this up and generates:
#: model:ir.model.fields,help:reward_addons.field_reward_program__min_transaction_amount
msgid "Minimum purchase amount to earn reward points.\n0 = No minimum."
msgstr "Jumlah pembelian minimum untuk mendapatkan poin reward.\n0 = Tidak ada minimum."
The \n newline character must be preserved in the translated string.
4. Validation Error Messages
Unlike field attributes, error messages written directly in Python code are not detected automatically. The developer must explicitly wrap them with the _() function:
from odoo import _, api, models
from odoo.exceptions import ValidationError
class RewardProgram(models.Model):
_name = 'reward.program'
@api.constrains('min_transaction_amount')
def _check_min_transaction_amount(self):
for record in self:
if record.min_transaction_amount < 0:
raise ValidationError(
_("Minimum transaction amount cannot be negative for program '%s'.")
% record.name
)
Without _(), Odoo will not detect the string during scanning. The exported entry will include the #, python-format flag because of the %s placeholder. This placeholder must be kept intact in the translation:
#, python-format
msgid "Minimum transaction amount cannot be negative for program '%s'."
msgstr "Jumlah transaksi minimum tidak boleh negatif untuk program '%s'."
Exporting and Applying Translations
Once development is complete, the workflow is straightforward.
- Update the module by navigating to Apps, search for your module (
reward_addons), click the three-dot menu ( ⋮ ) on the module card, and select Upgrade. - In developer mode, export the translation file by going to Settings → Translations → Export Translation, then select the language and module to download the
.pofile. - Fill in the translations. Save the file to
reward_addons/i18n/id.poand fill in the emptymsgstrfields. - Import the translations back into Odoo. Go to Settings → Translations → Import Translation, upload your
.pofile, and make sure Overwrite Existing Terms is checked. Then upgrade the module from the app list.
Conclusion
Odoo automatically detects and translates most user-facing text, including field labels, help texts, selection values, and SQL constraint messages. However, validation error messages written in Python must be wrapped with the _() function to ensure they are included in the translation export process.