Commerce Commerce 1.x Developer Internationalisation

Commerce 1.3 introduced the ability to add internationalisation to any object type through the Commerce interface. For convenience, this is implemented on the comSimpleObject, so any custom object derived from that will be able to use it.

For basic usage instructions, see the documentation here. This documentation goes into the technical implementation and enabling multilingual fields in custom models.

“i18n” is short for “internationalisation”.

Model description

Primary language strings are stored on the respective model in their normal fields, e.g. a name or description field.

Translations are stored on the comI18n model (commerce_i18n database table) with principal, principal_id, and field keys to identify what the translation is for specifically.

For example, given a product, the comI18n model for its name translations would be identifiable by:

  • principal = 'comProduct'
  • principal_id = <id of product>
  • field = ‘name’`

We do not recommend interfacing with the comI18n model directly, with the exception if you’re looking to programmatically add/update large batches of translations at a time. For other use cases, use the utilities provided on the object itself, described below.

Implementing i18n support

First, make sure you created a custom xPDO Model that extends comSimpleObject. This functionality is only available to derivatives of that. If you extend any Commerce models like comProduct, comStatusChangeAction or comShippingMethod, those also extend comSimpleObject.

Define which of your fields are translatable by adding public static $translatableFields = ['field1', 'field2']; to your object class.

For example:

class myCustomObject extends comSimpleObject
    public static $translatableFields = ['name', 'description'];
    // ...

Next, update your Commerce admin form. On the translatable fields, provide the translations key with the current values as retrieved from the records’ getTranslations($key) utility.

For example, inside a Form class:

use modmore\Commerce\Admin\Widgets\FormWidget;
use modmore\Commerce\Admin\Widgets\Form\TextField;

class Form extends FormWidget
    protected $classKey = 'myCustomObject';
    public $key = 'custom-object-form';
    public $title = 'custom_object.title';

    public function getFields(array $options = array())
        $fields = [];

        $fields[] = new TextField($this->commerce, [
            'name' => 'name',
            'label' => $this->adapter->lexicon(''),
            // add this line:
            'translations' => $this->record->getTranslations('name'),
            'validation' => [
                new Required(),
                new Length(3, 190),

        // ...
        return $fields;

    // ...

Or, inside an extended model (like a custom product) its getModelFields() field definition:

use modmore\Commerce\Admin\Widgets\Form\TextField;

class myCustomObject extends comProduct
    public static $translatableFields = ['extra_name'];

    public function getModelFields()
        $fields = [];
        $fields[] = new TextField($this->commerce, [
            'label' => 'extra_name',
            'name' => 'properties[extra_name]',
            'translations' => $this->getTranslations('extra_name'),
            'value' => $this->getProperty('extra_name'),

        return $fields;

Commerce will take care of the rest from here. You’ll see the language options in the form, and translations will be automatically saved.

Object utilities

Each object in Commerce extends comSimpleObject, which provides the following utilities to more easily interact with translations:

getTranslation(string $field, [string $lang = current language]): string

Returns either the translated value for $field in the provided (or current) language, or the primary value for the field if the translation is not set.

If you’re just looking to access a translation, note that toArray() will automatically add fields such as name_en, name_fr for enabled languages as well (which may be pre-optimized in the query or cached, unlike getTranslation which will always load from the database).

You only need to specifically call getTranslation in specific circumstances where you have an object that wasn’t primed with the translation.

getTranslations(string $field, [string $includePrimary = false]): array

Retrieves all translations for the specific field. If $includePrimary is specified, the value for the primary language (which is stored on the object itself) will also be returned.

The format of the returned array is as follows:

    {languageKey} => [
        'id' => 123, // id of the TRANSLATION - not provided for the primary language if included
        'principal' => 'comProduct', // the class key the translation is for
        'principal_id' => 123, // the ID of the object the translation is for; e.g. the product ID
        'field' => 'name', // the name of the field that is translated
        'lang' => 'lang_key', // e.g. 'en'
        'value' => 'Translation of the field',
        'icon' => 'us', // the icon for the language, matches fontawesome's country flags
    {otherLanguageKey} => [...],

Again, note that toArray() will automatically make available translations with the keys name_{langKey} in most cases, which may be more optimized than calling getTranslations() directly.

setTranslation(string $field, string $lang, string $value): void

Save a translation for the object. You only ever need to call this if you’re not using the default translation saving as part of Commerce’s dashboard forms.

The object must have been saved (have an id > 0) and the value must be non-empty for it to be saved.

static joinTranslation(xPDOQuery $c, array $fields, string $lang, string $principal = current class, string $alias = ’I18N)

Utility method to create a LEFT JOIN on the query that merges the lookup of translations into a single query.

Usage description TBA