Skip to content

Commit

Permalink
Merge pull request #429 from eternoendless/explain-adv-translation
Browse files Browse the repository at this point in the history
Add more information on how to make wordings discoverable
  • Loading branch information
matks committed Dec 30, 2019
2 parents 776684b + ad642df commit 4f9f083
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ One of the main needs for localization is translating wordings to the another la
PrestaShop 1.7 features a new translation system, based on the [Symfony Translation component](https://symfony.com/doc/3.4/translation.html).

{{% notice warning %}}
**This system is only available for Core and Native modules.**
**This documentation is intended for Core and Native module translation.**

See [here]({{< ref "1.7/modules/creation/module-translation/_index.md" >}}) for 3rd party modules.
If you are a module developer, read the [module translation documentation]({{< ref "1.7/modules/creation/module-translation/_index.md" >}}) instead.
{{% /notice %}}

## Overview
Expand Down Expand Up @@ -53,16 +53,24 @@ echo $translator->trans('This product is no longer available.', [], 'Shop.Notifi

The `trans()` method takes three arguments:

1. The wording to translate. Keep in mind that it has to be _exactly_ the same as the one in the default catalogue, or the translation won't work.
2. An array of replacements, if any ([Learn more here](https://symfony.com/doc/3.4/components/translation/usage.html#component-translation-placeholders)).
3. The [translation domain][translation-domains] for that wording.
1. `$id` – The wording you want to translate. Keep in mind that it has to be _exactly_ the same as the one in the default catalogue, or the translation won't work.
2. `$parameters` – An array of replacements, if any. ([Learn more about translation placeholders](https://symfony.com/doc/3.4/components/translation/usage.html#component-translation-placeholders)).
3. `$domain` – The [translation domain][translation-domains] for that wording.

{{% notice warning %}}
Be aware that in Symfony-based Admin controllers, the second and third arguments have been swapped in order to allow `$replacements` to be optional. For more, see [FrameworkBundleAdminController](https://github.com/PrestaShop/PrestaShop/blob/1.7.6.0/src/PrestaShopBundle/Controller/Admin/FrameworkBundleAdminController.php#L275).
{{% /notice %}}

##### Inside controllers

Controllers include a helper method named `trans()` that calls the translator internally:

```php
// legacy controllers
$this->trans('This product is no longer available.', [], 'Shop.Notifications.Error');

// Symfony-based controllers (FrameworkBundleAdminController)
$this->trans('This product is no longer available.', 'Shop.Notifications.Error', []);
```

##### Outside controllers
Expand Down Expand Up @@ -135,7 +143,10 @@ In `.twig` files, you can use the `trans` filter from Twig:
<div>{{ 'Sort by'|trans({}, 'Admin.Actions') }}</div>
```

For information on more advanced features, head on to the [Official documentation](https://symfony.com/doc/current/translation.html#twig-templates).
For information on more advanced features, head on to the [Symfony translator component documentation](https://symfony.com/doc/current/translation.html#twig-templates).

## Read more

{{% children %}}

[translation-domains]: {{< relref "translation-domains.md" >}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
title: Tips and tricks
---

# Translation tips and tricks

## Adding new wordings

Wordings for the Core and Native modules can only be translated if they are declared in PrestaShop's default translation catalogue. Therefore, whenever a new wording is added to the core or to a native module, it must be added to the default catalogue as well.

Normally you would have to manually add each wording the appropriate default catalogue files (located in the `app/Resources/translations/default` folder). Thankfully, this task has been automated by the Core team!

Before every minor release, the whole source code for PrestaShop and Native Modules is analyzed using [TranslationToolsBundle](https://github.com/PrestaShop/TranslationToolsBundle), and all newly discovered wordings are automatically added to the default catalogue.

{{% notice note %}}
Incidentally, the same technique is also used to detect wordings used by third-party modules and make them translatable through the Back Office interface.
{{% /notice %}}

### Making wordings discoverable for automated addition to the catalogue

TranslationToolsBundle uses static analysis to extract wordings from source code. Therefore, this means that you can simply use new wordings in the code, and they will be magically added to the catalogue later.

However, due to limitations of this technique, the following guidelines must be followed when declaring new wordings:

1. This tool 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.

2. **Always use literal values, not variables, with the `trans()` function, the `{l}` Smarty tag, and `trans` Twig filter.** Although variables are interpolated at runtime, they won't be understood by the code analyzer, which only supports literals. Passing variables as arguments to these functions will prevent those wordings from being added to the catalogue.

{{% notice warning %}}
Failure to comply with these guidelines will result in the wording not being added to the catalogue and not being translatable!
{{% /notice %}}

Examples:

```php
// literal values will work
$this->trans('Some wording', [], 'Admin.Catalog.Feature');

// dynamic content can be injected using placeholders & replacements
$this->trans('Some wording with %foo%', ['%foo%' => $dynamicContent], 'Admin.Catalog.Feature');

// this won't work, the interpreter will ignore variables
$wording = 'Some wording';
$domain = 'Admin.Catalog.Feature';
$this->trans($wording, [], $domain);

// this will yield unexpected results
$this->trans('Some '. $var . ' wording', [], 'Admin.Catalog.Feature');

// dynamic behavior, like aliasing the trans() function, won't work well either
function translate($wording) {
$this->trans($wording, [], 'Admin.Catalog.Feature');
}
```

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:

```twig
{% trans_default_domain 'Admin.Catalog.Feature' %}
{{ 'Hello world'|trans }}
{{ 'Something else'|trans }}
```

#### Form ChoiceTypes

When declaring Symfony form types, you declare choices for `ChoiceType` fields as literal (untranslated) strings:

```php
use PrestaShopBundle\Form\Admin\Type\TranslatorAwareType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;

class SomeFormType extends TranslatorAwareType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('a_select_box', ChoiceType::class, [
'choices' => [
'First option' => 0,
'Second option' => 1,
'Third option' => 2,
],
'required' => false,
'label' => $this->trans('This is a select box', 'Admin.Catalog.Feature'),
'choice_translation_domain' => 'Admin.Some.Domain',
])
->add('another_select_box', ChoiceType::class, [
'choices' => [
'Another first option' => 0,
'Another second option' => 1,
'Another third option' => 2,
],
'required' => false,
'label' => $this->trans('This is a different select box', 'Admin.Catalog.Feature'),
]);
}

/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'translation_domain' => 'Admin.Catalog.Feature',
]);
}

}
```

The form above declares two Choice fields (select boxes), with three different options each.

* The first Choice declares a specific `choice_translation_domain`: this explicit translation domain will be used to translate choices from this field.
* The second Choice does not declare a translation domain. Therefore, it will inherit the one set in `translation_domain` within the `configureOptions()` method.

**Note:** you must be careful when using this pattern:

* The analyzer expects the `ChoiceType` declaration to be inside a call to the `add()` method, using `ChoiceType::class` and not a FQCN.
* The default translation domain expects a call to the `setDefaults()` method within `configureOptions()`.

{{% notice tip %}}
To be safe, just do it exactly as the example above. If in doubt, have a look at [ChoiceExtractor](https://github.com/PrestaShop/TranslationToolsBundle/blob/master/Translation/Extractor/Visitor/Translation/FormType/ChoiceExtractor.php) and [DefaultTranslationDomainExtractor](https://github.com/PrestaShop/TranslationToolsBundle/blob/master/Translation/Extractor/Visitor/Translation/FormType/DefaultTranslationDomainExtractor.php).
{{% /notice %}}

#### Array literals

You can declare wordings as arrays as well. This obviously won't translate the wordings at runtime but it will make them discoverable by the extractor.

```php
[
'key' => 'This is a sample text',
'domain' => 'Admin.Some.Feature',
'parameters' => [],
];
```
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ Don't worry if you don't translate everything to all languages right away. Any w

### Translation domain

An important part of the new translation system is **Translation Domains**. Essentially, translation domains replace the classic system's [contextualization][contextualization], which provides more flexibility for translators to translate the same wording differently depending on the context where it's used.
An important part of the new translation system is **Translation Domains**, which replaces the classic system's [contextualization][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][core-translation-domains], non-native modules must respect a specific convention:
While the Core and Native modules have clearly defined [translation domain naming scheme][core-translation-domains], non-native modules must respect a specific naming convention:

```
Modules.Nameofthemodule.Specificpart
```

Translation Domains are always made of three parts, separated by dots:
Translation Domain names are always made of three parts, separated by dots:

1. The first part must always be **"Modules"**
2. **"Nameofthemodule"** is the name of your module, with some rules:
Expand Down Expand Up @@ -278,7 +278,7 @@ Example:
$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->trans('Some wording with %foo%', ['%foo%' => $dynamicContent], 'Modules.Mymodule.Bar');

// this won't work, the interpreter will ignore variables
$wording = 'Some wording';
Expand All @@ -289,7 +289,7 @@ $this->trans($wording, [], $domain);
$this->trans('Some '. $var . ' wording', [], 'Modules.Mymodule.Foo');

// dynamic behavior, like aliasing the trans() function, won't work well either
public function translate($wording) {
function translate($wording) {
$this->trans($wording, [], 'Modules.Mymodule.Foo');
}
```
Expand Down

0 comments on commit 4f9f083

Please sign in to comment.