The new module translation system is based on the new system introduced in PrestaShop 1.7 for the Core and Native modules. It aims to harmonize translation systems throughout PrestaShop while providing backwards compatibility.
Similarly to the classic system, the new translation system works in two steps.
PrestaShop provides functions that allow PHP files and Smarty/Twig templates to display translated wordings. By leveraging a list of translation sources (including dictionary files from classic translation), a module can use this feature display a wording in another language during runtime.
The Back Office’s Translation page (International > Translations > Modify Translations) is used to generate module dictionaries. It extracts the module’s wordings by analyzing its source code, then inspects a list of sources to compile a list of translations, and finally displays a form that allows you to customize or complete those translations in a given language. Once saved, this information is stored in the Database.
To make your module translatable, you need to adapt your module’s source code. Find any wording you want to make translatable, then wrap it using the appropriate method as explained below. Once wrapped, your wordings will be ready to be translated through the translation interface.
By default, wordings are displayed as originally written.
Don’t worry if you don’t translate everything to all languages right away. Any wording left untranslated will be shown in its original language. Because of this, we suggest writing all your wordings in English, and then translating them to other languages.
An important part of the new translation system is Translation Domains, which replaces the classic system’s contextualization. In the new translation system, all wordings must be linked to at least one translation domain.
While the Core and Native modules have clearly defined translation domain naming scheme, non-native modules must respect a specific naming convention:
Modules.Nameofthemodule.Specificpart
Translation Domain names are always made of three parts, separated by dots:
The first part must always be “Modules”
“Nameofthemodule” is the name of your module, with some rules:
_
) or any other unsupported symbol, or else translation may not work.ps_
, that part must be removed. This is an exception to the previous rule.“Specificpart” allows for contextualization and can be set to whatever you like, following this rules:
If you want your module to be compatible with previously-generated classic translation dictionary files, then the third component of the Translation domain must be set to the name of the file where the wording is used, respecting the following rules:
.tpl
, the extension must be removedAssuming your module is called “my_module”:
File where the wording is used | Expected translation domain |
---|---|
my_module.php | Modules.Mymodule.My_module.php |
SomeFile.php | Modules.Mymodule.Somefile.php |
a_certain_template.tpl | Modules.Mymodule.A_certain_template |
ps_somefile.tpl | Modules.Mymodule.Ps_somefile |
another-template.html.twig | Modules.Mymodule.Another-template.html.twig |
You can find more examples in the test fixtures for DomainHelper.
In PHP files, translation is performed using the module’s trans()
method.
This method takes four parameters:
$id
– The wording you want to translate.$parameters
– An array of replacements, if any. (Learn more about translation placeholders).$domain
– The translation domain for that wording, as explained above.$locale
– (optional) The locale identifier (eg. “en-US”) if you want to translate in a different language than the current one.Now let’s see some examples on how to use it.
When translating wordings in the module’s main class, since it extends the Module
class, you can simply call $this->trans()
.
<?php
// file: mymodule.php
class MyModule extends Module
{
public function __construct()
{
$this->version = '1.0.0';
$this->author = 'Me';
$this->displayName = $this->trans('My module', [], 'Modules.Mymodule.Mymodule');
$this->description = $this->trans('Description of my module. Made by: %author%, Current Version: %version%', ['%version%' => $this->version ,'%author%' => $this->author], 'Modules.Mymodule.Mymodule');
}
}
Since the module is called MyModule, the translation domain should be Modules.Mymodule.Mymodule
. The third part matches the file name, which is also “mymodule”.
ModuleAdminController
and ModuleFrontController
can access the module instance and translator via the $this->module
property and getTranslator()
public accessor.
<?php
// file: controllers/front/something.php
class MyModuleSomethingModuleFrontController extends ModuleFrontController
{
public function initContent()
{
$this->title = $this->module->getTranslator()->trans('My module title', [], 'Modules.Mymodule.Something');
}
}
Symfony controllers work exactly the same as the Core’s. Just use $this->trans()
method.
$replacements
optional.<?php
namespace PrestaShop\Module\MyModule;
class SomeAdminController extends FrameworkBundleAdminController
{
public function someAction()
{
$this->text = $this->trans('Some text being translated', 'Modules.Mymodule.Admin', []);
}
}
Other classes will need to retrieve the module’s translator instance somehow. We recommend passing it as a parameter in the constructor and storing it for later use.
<?php
class CustomModuleClass
{
private $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator
}
public function foo()
{
$this->text = $this->translator->trans('My text to translate', [], 'Modules.Mymodule.Custommoduleclass');
}
}
// from within the module:
$customModuleClass = new _NAMESPACE_\CustomModuleClass($this->getTranslator());
$module = Module::getInstanceByName('mymodulename')
, and then access the translator
with $module->getTranslator()
. This should be avoided though, as it’s not a good practice.Wordings in Twig .twig files can be translated using the trans
filter. It works similarly as the trans()
method described above for PHP files:
{# file: something.twig #}
{{ 'Welcome to this page!'|trans({}, 'Modules.Mymodule.Admin') }}
The first parameter can be used to replace tokens in your wording after it’s translated:
{{ 'Hello %username%!'|trans({'%username%': 'John'}, 'Modules.Mymodule.Admin') }}
Wordings in Smarty .tpl files can be translated using the {l}
function call, which Smarty will replace by the translation in the current language.
This function accepts three parameters:
s
– The wording to be translated.d
– The translation domain.sprintf
– Optional, it can be used to interpolate variables in your wording.For instance, translating the string “Welcome to this page!” can be done like this:
{* file: somefile.tpl *}
{l s='Welcome to this page!' d='Modules.Mymodule.Somefile'}
You can replace placeholders in your translated wordings using the sprintf
parameter:
{l s='Hello %username%!' sprintf=['%username%' => 'John'] d='Modules.Mymodule.Somefile'}
Modules must opt-in to become translatable using the new Back Office translation interface. This can be done by declaring the following function on your module’s main class:
<?php
public function isUsingNewTranslationSystem()
{
return true;
}
After this:
You will be presented with a page that displays all the wordings for the selected module, grouped by translation domain.
Once saved, translations are stored in the database in the table ps_translations
.
The translation interface relies on code analysis to “discover” wordings for translation. Therefore, when declaring wordings in your code, some care is needed in order to make sure they can be discovered.
The translation interface only detects wordings used through the trans()
function, the {l}
Smarty tag, and the trans
Twig filter. Therefore, they must be declared in a PHP, TPL, or TWIG file. They will be detected regardless of whether that code is actually used in runtime or not.
Always use literal values, not variables, with the trans()
function, the {l}
Smarty tag, and trans
Twig filter. Although variables are interpreted at runtime, they won’t be understood by the code analyzer, which only supports literals. Passing variables to these methods will prevent those wordings from appearing in the translation interface.
Example:
<?php
// literal values will work
$this->trans('Some wording', [], 'Modules.Mymodule.Something');
// dynamic content can be injected using placeholders & replacements
$this->trans('Some wording with %foo%', ['%foo%' => $dynamicContent], 'Modules.Mymodule.Bar');
// this won't work, the interpreter will ignore variables
$wording = 'Some wording';
$domain = 'Modules.Mymodule.Foo';
$this->trans($wording, [], $domain);
// this will yield unexpected results
$this->trans('Some '. $var . ' wording', [], 'Modules.Mymodule.Foo');
// dynamic behavior, like aliasing the trans() function, won't work well either
function translate($wording) {
$this->trans($wording, [], 'Modules.Mymodule.Foo');
}
In Twig files, you can use trans_default_domain
to set up your default domain. Keep in mind this works on a per-file basis:
{% trans_default_domain 'Modules.Mymodule.Foo' %}
{{ 'Hello world'|trans }}
{{ 'Something else'|trans }}
Since PrestaShop 1.7.8, you can export all the module’s translations into XLF files that you can distribute with your module.
To export the translation files:
Go to the “Translations” page under the “International” menu,
In the “Export translations” section:
You can distribute the downloaded dictionaries by placing the extracted files in your module’s translations
folder, like this:
.
└── mymodule/
└── translations/
├── fr-FR/
│ ├── ModulesMymoduleFoo.fr-FR.xlf
│ └── ModulesMymoduleBar.fr-FR.xlf
└── en-US/
├── ModulesMymoduleFoo.en-US.xlf
└── ModulesMymoduleBar.en-US.xlf
For module developers, if you want to incorporate your final translations into your module without creating the xlf file by hand, the recommended practice is to:
These translations will be set up in PrestaShop during the module’s installation.
If you need to distribute backward-compatible translations, you can either write classic dictionary files manually, or export your module’s wordings from the database into a file, then import it during the module’s install process.
If you choose to export wordings from the database, you can easily extract your module’s wordings from the ps_translation
table by filtering domains that start with ModulesYourmodulename*
. When inserting the translations in the destination shop, remember to set the appropriate value for id_lang
according to the destination shop’s language configuration (see table ps_lang
) .