Magento 2.3.0 javascript translations bug

If you have been working with Magento 2.3.0 version you may have noticed weird behavior for js translations. In this version there was a bug introduced which was somewhat difficult to identify. For some strings the translations work but for other they do not.

The first thing to check of course is if the localstorage mage-translation-storage variable contains the string you wish to translate. If it does not then you may want to look into js-translations.json file in your theme and backtrace if the phrase is added to the dictionary.

If the string is available in the dictionary but is still not translating then the next place you should look at is if the translations are initialized early enough for you script to pick it up. Here is how it works:

module-translation/view/base/templates/translate.phtml is initializing the translations. This happens in theme module default xml:

      <referenceContainer name="after.body.start">
            <block class="Magento\RequireJs\Block\Html\Head\Config" name="requirejs-config"/>
            <block class="Magento\Translation\Block\Html\Head\Config" name="translate-config"/>
            <block class="Magento\Translation\Block\Js" name="translate" template="Magento_Translation::translate.phtml"/>
            <block class="Magento\Framework\View\Element\Js\Cookie" name="js_cookies" template="Magento_Theme::js/cookie.phtml"/>
            <block class="Magento\Theme\Block\Html\Notices" name="global_notices" template="Magento_Theme::html/notices.phtml"/>
        </referenceContainer>
module-theme/view/frontend/layout/default.xml

In Magento 2.3.0 the translate.phtml file looks like this:

<?php if ($block->dictionaryEnabled()): ?>

<?php
  $version = $block->getTranslationFileVersion();
  $fileName = Magento\Translation\Model\Js\Config::DICTIONARY_FILE_NAME;
    ?>
    <script type="text/x-magento-init">
        {
            "*": {
                "mage/translate-init": {
                    "dictionaryFile": "text!<?= $block->escapeJs($fileName); ?>",
                    "version": "<?= $block->escapeJs($version) ?>"
                }
            }
        }
    </script>
<?php endif; ?>
module-translation/view/base/templates/translate.phtml

So the file is loaded in header and initializes a new js component translate-init. This leads to a situation where the component is initialized later then some of the other js components. The files that are loaded before can suffer from incorrect translations. For example, in customer address component we have the following code:

define([
    'jquery',
    'Magento_Ui/js/modal/confirm',
    'jquery/ui',
    'mage/translate'
], function ($, confirm) {
    'use strict';

    $.widget('mage.address', {
        /**
         * Options common to all instances of this widget.
         * @type {Object}
         */
        options: {
            deleteConfirmMessage: $.mage.__('Are you sure you want to delete this address?')
        },
        ...

Notice the options declaration. The deleteConfirmMessage is translated on component initialization. Since the translation-init has not yet completed the translation dictionary is empty and the message will use the default string.

We can test this by adding a few console logs. First add logs into mage/translate:

this.translate = function (text) {    
    console.log(_data);
    console.log(text);
    return _data[text] ? _data[text] : text;
};

This will show us each translation action and the contents of dictionary at that moment. Secondly add logs to mage/translate-init:

callback: function (string) {
                if (typeof string === 'string') {
                	console.log("Initializing translations.");
                    $.mage.translate.add(JSON.parse(string));
                    $.localStorage.set('mage-translation-storage', string);
                    $.localStorage.set(
                        'mage-translation-file-version',
                        {
                            version: pageOptions.version
                        }
                    );
                } else {
                    console.log("Initializing translations.");
                    $.mage.translate.add($.localStorage.get('mage-translation-storage'));
                }
            }

After adding this you can see the following in your console:

So we can see that the problem is that the translations are initialized too late and some of the files are already translated at that point. If the file is translating string in init functions then they will never complete correctly.

Currently this is fixed in latest Magento versions. If you are still on a version with this bug a fix for this can be found here. We need to revert the commit that refactored the translation initialization into its own file and replace with the old implementation.

Hopefully this helps you avoid a headache while working with javascript translations.