Recently I had to extend an existing extension, Commerce, and, among other things, I had to add i18n support to the entity Manufacturer (tx_commerce_manufacturer).

If you’re creating your own extension with Kickstarter, adding i18n support for an entity is easy: just check the “Enabled localization features” checkbox and Kickstarter will take care of the rest.

However, in case you need to extend an existing entity, editing it and modifying it with Kickstarter can be troublesome. In addition, modifying extensions directly is never a good idea. In any case, it’s interesting to see what Kickstarter does under the hood and redo those steps directly in code.

To add i18n support to an entity, whether is an entity you’re creating yourself or it’s an already existing entity from an extension, three steps are required:

1) Open ext_tables.sql and add the following statements:

CREATE TABLE tx_commerce_manufacturer (
    sys_language_uid int(11) DEFAULT '0' NOT NULL,
	l18n_parent int(11) DEFAULT '0' NOT NULL,
	l18n_diffsource mediumblob NOT NULL,
);

2) After that, open ext_tables.php and edit *TCA[‘tx_commerce_manufacturer’][‘ctrl’] *to add some extra fields:

'languageField' => 'sys_language_uid',
'transOrigPointerField' => 'l18n_parent',
'transOrigDiffSourceField' => 'l18n_diffsource',

3) Lastly, it’s needed to edit TCA[‘tx_commerce_manufacturer’] and add some fields in the ‘columns’ array. In Commerce this is done in file <commerce_path>/tcas/tx_commerce_manufacturer.tca.php.

'sys_language_uid' => array (
    'exclude' => 1,
    'label'  => 'LLL:EXT:lang/locallang_general.xml:LGL.language',
    'config' => array (
        'type'                => 'select',
        'foreign_table'       => 'sys_language',
        'foreign_table_where' => 'ORDER BY sys_language.title',
        'items' => array(
            array('LLL:EXT:lang/locallang_general.xml:LGL.allLanguages', -1),
            array('LLL:EXT:lang/locallang_general.xml:LGL.default_value', 0)
        )
    )
),
'l18n_parent' => array (
    'displayCond' => 'FIELD:sys_language_uid:>:0',
    'exclude'     => 1,
    'label'       => 'LLL:EXT:lang/locallang_general.xml:LGL.l18n_parent',
    'config'      => array (
        'type'  => 'select',
        'items' => array (
            array('', 0),
        ),
        'foreign_table'       => 'tx_commerce_manufacturer',
        'foreign_table_where' => 'AND tx_commerce_manufacturer.pid=###CURRENT_PID### 
AND tx_commerce_manufacturer.sys_language_uid IN (-1,0)',
    )
),
'l18n_diffsource' => array (
    'config' => array (
        'type' => 'passthrough'
    )
),

Step 1) adds some new columns to the table that allow Typo3 to keep track which rows are a translation of some others. The system_language_uid field stores the lang id for that row, for instance English: 0, Spanish: 1, Galician: 2, etc (the id numbers and languages available depends on the configuration of your system). The* l18n_parent* column is a pointer to the the original row, in case that row is a translation.

The names of the sys_language_uid, l18n_parent and l18n_diffsource columns are not fixed, and can be specified by the user. This is basically what step 2) does.

Lastly, step 3) configures the views in the backend for the system_language_uid and l18n_parent fields and how they operate. This step is essential, and without it, things won’t work properly.

In the example above, I modified the Commerce extension directly. The best way to have done this would be to create my own backend extension and extend the* tx_commerce_manufacturer* table, but for the sake of simplicity I did the changes directly. Although the example is about Commerce, notice that i18n in Typo3 always work the same and the example is applicable either you’re creating your own entities or adding i18n support to some other existing ones.

NOTE: In Typo3 many fields used for localization/internationalization are named l18n_something. This is a, intentional or not, mixing of words localization(l10n) and internationalization(i18n). To keep things simple, I just followed the same trend :)