Magento: Translate dynamic values in KnockOut templates
TLDR;
You can add placeholders in the knockout template
<!-- ko if: false -->
<!-- ko i18n: 'value1' --><!-- /ko -->
<!-- ko i18n: 'value2' --><!-- /ko -->
<!-- ko i18n: 'value3' --><!-- /ko -->
<!-- /ko -->
<ul data-bind="foreach: getValues()">
<li>
<span data-bind="i18n: $data"></span>
</li>
</ul>
Download and install install Mecodeninja_DynamicTranslation
and add dynamic attribute to some translations.
composer require mecodeninja/module-dynamic-translation
"value1","translated value1",dynamic "value2","translated value2",dynamic "value3","value3",dynamic
Let's go over two way in which you can translate dynamic values in knockout templates. First lets go over the background context
Background
I'm going to be writing about two different Magento features and how they interact so let me first describe each feature enough so that we can understand the result.
Knockout
Magento users KockoutJs to render certain presentations on the frontend. The advantage of this method is that Magento can deliver dynamic data to the frontend using AJAX queries which return JSON objects and then hydrate static HTML templates with the data contained within.
Translation
Magento uses a few different methods to allow developers to translate strings within the framework. The one we'll be discussing, the most frequently used, is Dictionary. In the implementation Magento creates a JSON file which has key-value pairs. The original string as the key and the translation as the value.
See: js-translation.json
as an example
{"You have no items in your shopping cart.":"Foo Test"}
When Knockout hydrates the template with the data it will use this mapping to translate the string in the key replacing it with the string in the value.
Issue
The issue you might experience is that when Magento is generating the contents of js-translation.json
it collects all of the knockout templates and JS files, uses a regex pattern to look for "translateable" strings, then collects only those translations into the js-translation.json
dictionary that is served.
Therefore; if you have a knockout template that is going to receive a dynamic value it will not have an entry in the dictionary (it's not found by the regex) and it will not be translated since there is no entry.
Example
You have a knockout component like the following:
Willwright/Demo/view/frontend/templates/demo.phtml
<div id="willwright-demo-component" data-bind="scope:'willwright-demo'">
<!-- ko template: getTemplate() --><!-- /ko -->
<script type="text/x-magento-init">
{
"#willwright-demo-component": {
"Magento_Ui/js/core/app": {
"components": {
"willwright-demo": {
"component": "Willwright_Demo/js/demo",
"template": "Willwright_Demo/demo"
}
}
}
}
}
</script>
</div>
Willwright/Demo/view/frontend/web/template/demo.html
<ul data-bind="foreach: getValues()">
<li>
<span data-bind="i18n: $data"></span>
</li>
</ul>
Willwright/Demo/view/frontend/web/js/demo.js
define(['jquery', 'uiComponent', 'ko'], function ($, Component, ko) {
'use strict';
return Component.extend({
initialize: function () {
this._super();
},
getValues: function(){
return [
'value1',
'value2',
'value3'
];
}
});
}
);
Willwright/Demo/i18n/en_US.csv
"value1","translated value1"
"value2","translated value2"
"value3","value3"
This results in the following output:
js-translation.json
[]
Note that none of the values are translated and that the dictionary returned by Magento is empty.
Solution1: Placeholders
Solution one is to put some placeholders for all the values that could possibly be returned by the JS. This will cause the preprocessor to find the values and include their translations into the dictionary, in the case that they do indeed need to be translated.
All that is needed is to change Willwright/Demo/view/frontend/web/template/demo.html
to the following
<!-- ko if: false -->
<!-- ko i18n: 'value1' --><!-- /ko -->
<!-- ko i18n: 'value2' --><!-- /ko -->
<!-- ko i18n: 'value3' --><!-- /ko -->
<!-- /ko -->
<ul data-bind="foreach: getValues()">
<li>
<span data-bind="i18n: $data"></span>
</li>
</ul>
This produces:
js-translation.json
{"value1":"translated value1","value2":"translated value2"}
This time the preprocessor matched the knockout placeholders and included the translation KVPs into the dictionary.
Solution2: Extend Translation
Solution1 was a little inelegent to me and I didn't like splitting the reponsibility of translation into both the translation area and the knockout template. I wrote a small module that registers a new preprocessor which allows the developer to add a third column to the translation file CSV. When the third column is set, the preprocessor will add the translation KVP to the dictionary no matter whether the key is matched by the regex or not.
To implement do the following:
composer require mecodeninja/module-dynamic-translation
Update Willwright/Demo/i18n/en_US.csv
to:
"value1","translated value1",dynamic
"value2","translated value2",dynamic
"value3","value3",dynamic
This produces: