Skip to content

Commit 7df92da

Browse files
dabrtmnoconjulitafalcondusza
authored
IBX-7912: Describe automated translation feature (#2414)
* IBX-7912: Describe automated translation feature * Fix wording * Update the feature description * Tiny wording fixes * Command name fix * Implement reviewer comments * TMP * Added examples * Added missing links * Before review fixes * Fixes before review * Applied suggestions from code review Co-authored-by: julitafalcondusza <[email protected]> * Apply suggestions from code review Co-authored-by: Tomasz Dąbrowski <[email protected]> * Fixed highlight --------- Co-authored-by: Marek Nocoń <[email protected]> Co-authored-by: julitafalcondusza <[email protected]>
1 parent ef8ed97 commit 7df92da

File tree

7 files changed

+298
-1
lines changed

7 files changed

+298
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
services:
2+
# default configuration for services in *this* file
3+
_defaults:
4+
autowire: true # Automatically injects dependencies in your services.
5+
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
6+
7+
# makes classes in src/ available to be used as services
8+
# this creates a service per class whose id is the fully-qualified class name
9+
App\:
10+
resource: '../src/'
11+
exclude:
12+
- '../src/DependencyInjection/'
13+
- '../src/Entity/'
14+
- '../src/Kernel.php'
15+
16+
App\AutomatedTranslation\AiClient:
17+
tags:
18+
- ibexa.automated_translation.client
19+
20+
App\AutomatedTranslation\ImageFieldEncoder:
21+
tags:
22+
- ibexa.automated_translation.field_encoder
23+
24+
ibexa_automated_translation:
25+
system:
26+
default:
27+
configurations:
28+
aiclient:
29+
languages:
30+
- 'en_GB'
31+
- 'fr_FR'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace App\AutomatedTranslation;
4+
5+
use Ibexa\AutomatedTranslation\Exception\ClientNotConfiguredException;
6+
use Ibexa\Contracts\AutomatedTranslation\Client\ClientInterface;
7+
use Ibexa\Contracts\ConnectorAi\Action\DataType\Text;
8+
use Ibexa\Contracts\ConnectorAi\Action\RuntimeContext;
9+
use Ibexa\Contracts\ConnectorAi\ActionConfigurationServiceInterface;
10+
use Ibexa\Contracts\ConnectorAi\ActionServiceInterface;
11+
12+
final class AiClient implements ClientInterface
13+
{
14+
/** @var array<string> */
15+
private array $supportedLanguages;
16+
17+
private ActionServiceInterface $actionService;
18+
19+
private ActionConfigurationServiceInterface $actionConfigurationService;
20+
21+
public function __construct(ActionServiceInterface $actionService, ActionConfigurationServiceInterface $actionConfigurationService)
22+
{
23+
$this->actionService = $actionService;
24+
$this->actionConfigurationService = $actionConfigurationService;
25+
}
26+
27+
public function setConfiguration(array $configuration): void
28+
{
29+
if (!array_key_exists('languages', $configuration)) {
30+
throw new ClientNotConfiguredException('List of supported languages is missing in the configuration under the "languages" key');
31+
}
32+
$this->supportedLanguages = $configuration['languages'];
33+
}
34+
35+
public function translate(string $payload, ?string $from, string $to): string
36+
{
37+
$action = new TranslateAction(new Text([$payload]));
38+
$action->setRuntimeContext(
39+
new RuntimeContext(
40+
[
41+
'from' => $from,
42+
'to' => $to,
43+
]
44+
)
45+
);
46+
$actionConfiguration = $this->actionConfigurationService->getActionConfiguration('translate');
47+
$actionResponse = $this->actionService->execute($action, $actionConfiguration)->getOutput();
48+
49+
assert($actionResponse instanceof Text);
50+
51+
return $actionResponse->getText();
52+
}
53+
54+
public function supportsLanguage(string $languageCode): bool
55+
{
56+
return in_array($languageCode, $this->supportedLanguages, true);
57+
}
58+
59+
public function getServiceAlias(): string
60+
{
61+
return 'aiclient';
62+
}
63+
64+
public function getServiceFullName(): string
65+
{
66+
return 'Custom AI Automated Translation';
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace App\AutomatedTranslation;
4+
5+
use Ibexa\Contracts\AutomatedTranslation\Encoder\Field\FieldEncoderInterface;
6+
use Ibexa\Contracts\Core\Repository\Values\Content\Field;
7+
use Ibexa\Core\FieldType\Image\Value;
8+
9+
final class ImageFieldEncoder implements FieldEncoderInterface
10+
{
11+
public function canEncode(Field $field): bool
12+
{
13+
return $field->fieldTypeIdentifier === 'ezimage';
14+
}
15+
16+
public function canDecode(string $type): bool
17+
{
18+
return $type === 'ezimage';
19+
}
20+
21+
public function encode(Field $field): string
22+
{
23+
/** @var \Ibexa\Core\FieldType\Image\Value $value */
24+
$value = $field->getValue();
25+
26+
return $value->alternativeText ?? '';
27+
}
28+
29+
/**
30+
* @param string $value
31+
* @param \Ibexa\Core\FieldType\Image\Value $previousFieldValue
32+
*/
33+
public function decode(string $value, $previousFieldValue): Value
34+
{
35+
$previousFieldValue->alternativeText = $value;
36+
37+
return $previousFieldValue;
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace App\AutomatedTranslation;
4+
5+
use Ibexa\Contracts\ConnectorAi\Action\TextToText\Action;
6+
7+
final class TranslateAction extends Action
8+
{
9+
public function getActionTypeIdentifier(): string
10+
{
11+
return 'translate';
12+
}
13+
}

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
}
1313
],
1414
"require-dev": {
15+
"ibexa/automated-translation": "5.0.x-dev",
1516
"ibexa/code-style": "^1.0",
1617
"friendsofphp/php-cs-fixer": "^3.30",
1718
"phpstan/phpstan": "^2.0",
@@ -58,7 +59,6 @@
5859
"ibexa/activity-log": "5.0.x-dev",
5960
"ibexa/workflow": "5.0.x-dev",
6061
"ibexa/checkout": "5.0.x-dev",
61-
"ibexa/oauth2-server": "5.0.x-dev",
6262
"ibexa/elasticsearch": "5.0.x-dev",
6363
"ibexa/oauth2-client": "5.0.x-dev",
6464
"ibexa/form-builder": "5.0.x-dev",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
---
2+
description: With the automated translation add-on, users can translate content items into multiple languages with Google Translate or DeepL.
3+
---
4+
5+
# Automated content translation
6+
7+
With the automated translation add-on package, users can translate their content items into multiple languages automatically by using either Google Translate or DeepL external translation engine.
8+
The package integrates with [[= product_name =]], and allows users to [request from the UI]([[= user_doc =]]/content_management/translate_content.md#add-translations) that a content item is translated.
9+
However, you can also run a Console Command to translate a specific content item.
10+
Either way, as a result, a new version of the content item is created.
11+
12+
The following field types are supported out of the box:
13+
14+
- [TextLine](textlinefield.md)
15+
- [TextBlock](textblockfield.md)
16+
- [RichText](richtextfield.md)
17+
- [Page](pagefield.md): the content of `text` and `richtext` [block attributes](page_block_attributes.md#block-attribute-types)
18+
19+
See [adding a custom field or block attribute encoder](##add-a-custom-field-encoder) for more information on how you can extend this list.
20+
21+
## Configure automated content translation
22+
23+
### Install package
24+
25+
The automated content translation feature comes as an additional package that you must download and install separately:
26+
27+
```bash
28+
composer require ibexa/automated-translation
29+
```
30+
31+
!!! caution "Modify the default configuration"
32+
33+
Symfony Flex installs and activates the package.
34+
However, you must modify the `config/bundles.php` file to change the bundle loading order so that `IbexaAutomatedTranslationBundle` is loaded before `IbexaAdminUiBundle`:
35+
36+
```php
37+
<?php
38+
39+
return [
40+
// ...
41+
Ibexa\Bundle\AutomatedTranslation\IbexaAutomatedTranslationBundle::class => ['all' => true],
42+
Ibexa\Bundle\AdminUi\IbexaAdminUiBundle::class => ['all' => true],
43+
// ...
44+
];
45+
```
46+
47+
### Configure access to translation services
48+
49+
Before you can start using the feature, you must configure access to your Google and/or DeepL account.
50+
51+
1\. Get the [Google API key](https://developers.google.com/maps/documentation/javascript/get-api-key) and/or [DeepL Pro key](https://support.deepl.com/hc/en-us/articles/360020695820-API-Key-for-DeepL-s-API).
52+
53+
2\. Set these values in the YAML configuration files, under the `ibexa_automated_translation.system.default.configurations` key:
54+
55+
``` yaml
56+
ibexa_automated_translation:
57+
system:
58+
default:
59+
configurations:
60+
google:
61+
apiKey: "google-api-key"
62+
deepl:
63+
authKey: "deepl-pro-key"
64+
```
65+
66+
The configuration is SiteAccess-aware, therefore, you can configure different engines to be used for different sites.
67+
68+
## Translate content items with CLI
69+
70+
To create a machine translation of a specific content item, you can use the `ibexa:automated:translate` command.
71+
72+
The following arguments and options are supported:
73+
74+
- `--from` - the source language
75+
- `--to` - the target language
76+
- `contentId` - ID of the content to translate
77+
- `serviceName` - the service to use for translation
78+
79+
For example, to translate the root content item from English to French with the help of Google Translate, run:
80+
81+
``` bash
82+
php bin/console ibexa:automated:translate --from=eng-GB --to=fre-FR 52 google
83+
```
84+
85+
## Extend automated content translations
86+
87+
### Add a custom machine translation service
88+
89+
By default, the automated translation package can connect to Google Translate or DeepL, but you can configure it to use a custom machine translation service.
90+
You would do it, for example, when a new service emerges on the market, or your company requires that a specific service is used.
91+
92+
The following example adds a new translation service.
93+
It uses the [AI actions framework](ai_actions_md) and assumes a custom `TranslateAction` AI Action exists.
94+
To learn how to build custom AI actions see [Extending AI actions](extend_ai_actions.md#custom-action-type-use-case).
95+
96+
1. Create a service that implements the [`\Ibexa\AutomatedTranslation\Client\ClientInterface`](../../api/php_api/php_api_reference/classes/Ibexa-Contracts-AutomatedTranslation-Client-ClientInterface.html) interface:
97+
98+
``` php hl_lines="35-52"
99+
[[= include_file('code_samples/multisite/automated_translation/src/AutomatedTranslation/AiClient.php') =]]
100+
```
101+
102+
2\. Tag the service as `ibexa.automated_translation.client` in the Symfony container:
103+
104+
``` yaml
105+
[[= include_file('code_samples/multisite/automated_translation/config/services.yaml', 15, 18) =]]
106+
```
107+
108+
3\. Specify the configuration under the `ibexa_automated_translation.system.default.configurations` key:
109+
110+
``` yaml
111+
[[= include_file('code_samples/multisite/automated_translation/config/services.yaml', 23, 32) =]]
112+
```
113+
114+
### Create custom field or block attribute encoder
115+
116+
You can expand the list of supported field types and block attributes for automated translation, adding support for even more use cases than the ones built into [[= product_name =]].
117+
118+
The whole automated translation process consists of 3 phases:
119+
120+
1. **Encoding** - data is extracted from the field types and block attributes and serialized into XML format
121+
1. **Translating** - the serialized XML is sent into specified translation service
122+
1. **Decoding** - the translated response is deserialized into the original data structures for storage in [[= product_name =]]
123+
124+
The following example adds support for automatically translating alternative text in image fields.
125+
126+
1. Create a class implementing the [`FieldEncoderInterface`](../../api/php_api/php_api_reference/classes/Ibexa-Contracts-AutomatedTranslation-Encoder-Field-FieldEncoderInterface.html) and add the required methods:
127+
128+
``` php hl_lines="11-14 16-19 21-27 33-38"
129+
[[= include_file('code_samples/multisite/automated_translation/src/AutomatedTranslation/ImageFieldEncoder.php') =]]
130+
```
131+
In this example, the methods are responsible for:
132+
133+
- `canEncode` - deciding whether the field to be encoded is an [Image](imagefield.md) field
134+
- `canDecode` - deciding whether the field to be decoded is an [Image](imagefield.md) field
135+
- `encode` - extracting the alternative text from the field type
136+
- `decode` - saving the translated alternative text in the field type's value object
137+
138+
2\. Register the class as a service.
139+
If you're not using [Symfony's autoconfiguration]([[= symfony_doc =]]/service_container.html#the-autoconfigure-option), use the `ibexa.automated_translation.field_encoder` service tag.
140+
141+
``` yaml
142+
[[= include_file('code_samples/multisite/automated_translation/config/services.yaml', 19, 22) =]]
143+
```
144+
145+
For custom block attributes, the appropriate interface is [`BlockAttributeEncoderInterface`](../../api/php_api/php_api_reference/classes/Ibexa-Contracts-AutomatedTranslation-Encoder-BlockAttribute-BlockAttributeEncoderInterface.html) and the service tag is `ibexa.automated_translation.block_attribute_encoder`.

mkdocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ nav:
418418
- Languages: multisite/languages/languages.md
419419
- Language API: multisite/languages/language_api.md
420420
- Back office translations: multisite/languages/back_office_translations.md
421+
- Automated content translation: multisite/languages/automated_translations.md
421422
- Permissions:
422423
- Permissions: permissions/permissions.md
423424
- Permission overview: permissions/permission_overview.md

0 commit comments

Comments
 (0)