i18n with Yii 2 Advanced Template

In this post, I will show you my workflow for internationalization of Yii based projects.

We will configure sane paths, logically dividing the frontend and backend. We will also use the yii cli tool to generate the translation files for us. Let’s get started.

  1. Create messages directory inside the common directory.
  2. Create a file called i18n.php inside common/config directory.
  3. Paste the following block of code inside i18n.php.
    <?php
    return [
        'sourcePath' => __DIR__. '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR,
        'languages' => ['pt-PT'], //Add languages to the array for the language files to be generated.
        'translator' => 'Yii::t',
        'sort' => false,
        'removeUnused' => false,
        'only' => ['*.php'],
        'except' => [
            '.svn',
            '.git',
            '.gitignore',
            '.gitkeep',
            '.hgignore',
            '.hgkeep',
            '/messages',
            '/vendor',
        ],
        'format' => 'php',
        'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'messages',
        'overwrite' => true,
    ];
    
    • Make sure to add all required languages to ‘languages’ array. In the above example I have added Portuguese.
  4. Add the i18n component to your common/main.php configuration as follows:
    'components' => [
        ...
        'i18n' => [
            'translations' => [
                'frontend*' => [
                    'class' => 'yii\i18n\PhpMessageSource',
                    'basePath' => '@common/messages',
                ],
                'backend*' => [
                    'class' => 'yii\i18n\PhpMessageSource',
                    'basePath' => '@common/messages',
                ],
            ],
        ],
        ...
    ],
  5. You can now:
    • Set the language in common configuration i.e. ‘language’ => ‘pt-PT’ inside common/main.php.
    • Set the language at runtime i.e. Yii::$app->language = ‘pt-PT’
    • See the Yii 2.0 Guide on Internationalization for more details on setting a language.
  6. In your frontend and backend code, use the following method to create i18n supported strings respectively.
    Yii::t('frontend', 'Translatable String');
    Yii::t('backend', 'Translatable String');
  7. Run yii message/extract @common/config/i18n.php to generate the translation files inside common/messages.
  8. Yii message will generate the translation files as follows:
    common/
      ...
      messages/
        pt-PT/
          backend.php
          frontend.php
      ...
  9. The files frontend.php and backend.php are now ready to edit the Portuguese translations 🙂

There you have it! Once set up correctly, just run step #7 every time you add new translatable strings to your app. Yii CLI tool automatically and safely merges existing translations with the new strings and you can translate the new strings in the generated files.

Further Reading

http://www.yiiframework.com/doc-2.0/guide-tutorial-i18n.html
http://www.yiiframework.com/doc-2.0/yii-baseyii.html#t()-detail

Bonus Tip

You can further organize the translatable files i.e. Yii::t(‘frontend/post’, ‘Translatable String’); will generate common/messages/pt-PT/frontend/post.php with all translatable strings inside.

34 thoughts on “i18n with Yii 2 Advanced Template”

  1. Hello Coding Ninja,

    I was trying out Yii2, I have done this setup before in Yii and I never had this problem.

    But if I just straight up follow your guide, and many other implementations, I always get this:

    Invalid Call – yii\base\InvalidCallException
    Setting read-only property: yii\web\Application::i18n

    Stack trace:
    #0 *****\\yiisoft\yii2\BaseYii.php(518): yii\base\Compo
    nent->__set(‘i18n’, Array)
    #1 *****\\yiisoft\yii2\base\Object.php(105): yii\BaseYi
    i::configure(Object(yii\console\Application), Array)
    #2 *****\yiisoft\yii2\base\Application.php(206): yii\b
    ase\Object->__construct(Array)
    #3 *****\\yiisoft\yii2\console\Application.php(79): yii
    \base\Application->__construct(Array)
    #4 *****\\yii(30): yii\console\Application->__construct(Array)

    I’m using the Advanced Template.

  2. Hi Andre,

    It appears that your error is originating in the common/config/main.php. Check step 4 and make sure that the i18n section is nested inside the components and not outside of it.

    See my main.php file below.

     'pt-PT',
        'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
        'components' => [
            'i18n' => [
                'translations' => [
                    'frontend*' => [
                        'class' => 'yii\i18n\PhpMessageSource',
                        'basePath' => '@common/messages',
                    ],
                    'backend*' => [
                        'class' => 'yii\i18n\PhpMessageSource',
                        'basePath' => '@common/messages',
                    ],
                ],
            ],
        ],
    ];
    1. You are correct, what a miss. Thank you.

      You tell people to add it to the common main file, but the backend isn’t reading AFAIK the configurations from the common main file, which causes it to not load the il8n configuration present in there, and it doesn’t translate

      The message file for category ‘app’ does not exist: *****\backend/messages/pt-PT/app.php

      Aren’t we suppose to add il8n to the backend and frontend individually? Because they are not accessing the common il8n settings.

  3. Hi Andre,

    The backend loads the common config, as seen in backend/web/index.php

    $config = yii\helpers\ArrayHelper::merge(
        //Common config loaded here
        require(__DIR__ . '/../../common/config/main.php'), 
        require(__DIR__ . '/../../common/config/main-local.php'),
        // Backend config loaded here
        require(__DIR__ . '/../config/main.php'),
        require(__DIR__ . '/../config/main-local.php')
    );
  4. I see what’s up, I just don’t understand it.

    This works:

    'i18n' => [
    'translations' => [
    'app*' => [
    'class' => 'yii\i18n\PhpMessageSource',
    'basePath' => '@common/messages',
    ]
    ],
    ],

    This, as I wanted it, doesn’t:

    'i18n' => [
    'translations' => [
    '*' => [
    'class' => 'yii\i18n\PhpMessageSource',
    'basePath' => '@common/messages',
    ]
    ],
    ],

    Although it is documented here: http://www.yiiframework.com/doc-2.0/guide-tutorial-i18n.html#specifying-default-translation

      1. Sorry about this but as I tried many times at last it worked – always with Chrome – I can’t see why it was not working before but now its ok – so thanks for your reply !
        Now I applied things on this blog and its working fine 🙂

  5. The translation directory path:

    'sourcePath' => __DIR__. '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR,

    Is incorrect, it shoud be:

    'sourcePath' => __DIR__. DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR,

    notice the extra DIRECTORY_SEPARATOR at the start and a single directory level .. removed. With your directory indirection Yii scans also the directories below the main Yii2 installation path.

    I have fresh installation of Yii2 advanced with the default directory structure.

    1. Hi Bartosz,

      Thanks for the correction. My folder structure might be a little off standard, hence the discrepancy.
      I will update the post to reflect the correct path.

  6. Hello Ninja,
    this command doesn’t work with me
    yii message/extract @common/config/i18n.php
    it gets command not found

  7. This is working in my code.. But class yii\i18n\GettextMessageSource is not loaded the po files. so how to load text from .po files.

  8. I got this error when generate i18n file at step 7:

    esoftcard:~/workspace/idoc $ php yii message/extract @common/config/i18n.php
    Error: The source path /home/ubuntu/workspace/idoc/common/config../../../ is not a valid directory.

    How to fix this please

    1. @esoftcard
      in file i18n.php change this line

      ‘sourcePath’ => __DIR__. ‘..’ . DIRECTORY_SEPARATOR . ‘..’ . DIRECTORY_SEPARATOR . ‘..’ . DIRECTORY_SEPARATOR,

      with

      ‘sourcePath’ => __DIR__. DIRECTORY_SEPARATOR . ‘..’ . DIRECTORY_SEPARATOR . ‘..’ . DIRECTORY_SEPARATOR,

      work for me.

Leave a Reply

Your email address will not be published. Required fields are marked *