-
Notifications
You must be signed in to change notification settings - Fork 82
IBX-7912: Describe automated translation feature #2414
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
da69dae
a5e529d
8fe498d
9aff36f
f9d5757
19f170d
97029c1
db48382
5b122b0
822645b
3cdf047
0b8d7d7
857ad74
aa2bd08
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
services: | ||
# default configuration for services in *this* file | ||
_defaults: | ||
autowire: true # Automatically injects dependencies in your services. | ||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. | ||
|
||
# makes classes in src/ available to be used as services | ||
# this creates a service per class whose id is the fully-qualified class name | ||
App\: | ||
resource: '../src/' | ||
exclude: | ||
- '../src/DependencyInjection/' | ||
- '../src/Entity/' | ||
- '../src/Kernel.php' | ||
|
||
App\AutomatedTranslation\AiClient: | ||
tags: | ||
- ibexa.automated_translation.client | ||
|
||
App\AutomatedTranslation\ImageFieldEncoder: | ||
tags: | ||
- ibexa.automated_translation.field_encoder | ||
|
||
ibexa_automated_translation: | ||
system: | ||
default: | ||
configurations: | ||
aiclient: | ||
languages: | ||
- 'en_GB' | ||
- 'fr_FR' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace App\AutomatedTranslation; | ||
|
||
use Ibexa\AutomatedTranslation\Exception\ClientNotConfiguredException; | ||
use Ibexa\Contracts\AutomatedTranslation\Client\ClientInterface; | ||
use Ibexa\Contracts\ConnectorAi\Action\DataType\Text; | ||
use Ibexa\Contracts\ConnectorAi\Action\RuntimeContext; | ||
use Ibexa\Contracts\ConnectorAi\ActionConfigurationServiceInterface; | ||
use Ibexa\Contracts\ConnectorAi\ActionServiceInterface; | ||
|
||
final class AiClient implements ClientInterface | ||
{ | ||
/** @var array<string> */ | ||
private array $supportedLanguages; | ||
|
||
private ActionServiceInterface $actionService; | ||
|
||
private ActionConfigurationServiceInterface $actionConfigurationService; | ||
|
||
public function __construct(ActionServiceInterface $actionService, ActionConfigurationServiceInterface $actionConfigurationService) | ||
{ | ||
$this->actionService = $actionService; | ||
$this->actionConfigurationService = $actionConfigurationService; | ||
} | ||
|
||
public function setConfiguration(array $configuration): void | ||
{ | ||
if (!array_key_exists('languages', $configuration)) { | ||
throw new ClientNotConfiguredException('List of supported languages is missing in the configuration under the "languages" key'); | ||
} | ||
$this->supportedLanguages = $configuration['languages']; | ||
} | ||
|
||
public function translate(string $payload, ?string $from, string $to): string | ||
{ | ||
$action = new TranslateAction(new Text([$payload])); | ||
$action->setRuntimeContext( | ||
new RuntimeContext( | ||
[ | ||
'from' => $from, | ||
'to' => $to, | ||
] | ||
) | ||
); | ||
$actionConfiguration = $this->actionConfigurationService->getActionConfiguration('translate'); | ||
$actionResponse = $this->actionService->execute($action, $actionConfiguration)->getOutput(); | ||
|
||
assert($actionResponse instanceof Text); | ||
|
||
return $actionResponse->getText(); | ||
} | ||
|
||
public function supportsLanguage(string $languageCode): bool | ||
{ | ||
return in_array($languageCode, $this->supportedLanguages, true); | ||
} | ||
|
||
public function getServiceAlias(): string | ||
{ | ||
return 'aiclient'; | ||
} | ||
|
||
public function getServiceFullName(): string | ||
{ | ||
return 'Custom AI Automated Translation'; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace App\AutomatedTranslation; | ||
|
||
use Ibexa\Contracts\AutomatedTranslation\Encoder\Field\FieldEncoderInterface; | ||
use Ibexa\Contracts\Core\Repository\Values\Content\Field; | ||
use Ibexa\Core\FieldType\Image\Value; | ||
|
||
final class ImageFieldEncoder implements FieldEncoderInterface | ||
{ | ||
public function canEncode(Field $field): bool | ||
{ | ||
return $field->fieldTypeIdentifier === 'ezimage'; | ||
} | ||
|
||
public function canDecode(string $type): bool | ||
{ | ||
return $type === 'ezimage'; | ||
} | ||
|
||
public function encode(Field $field): string | ||
{ | ||
/** @var \Ibexa\Core\FieldType\Image\Value $value */ | ||
$value = $field->getValue(); | ||
|
||
return $value->alternativeText ?? ''; | ||
} | ||
|
||
/** | ||
* @param string $value | ||
* @param \Ibexa\Core\FieldType\Image\Value $previousFieldValue | ||
*/ | ||
public function decode(string $value, $previousFieldValue): Value | ||
{ | ||
$previousFieldValue->alternativeText = $value; | ||
|
||
return $previousFieldValue; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace App\AutomatedTranslation; | ||
|
||
use Ibexa\Contracts\ConnectorAi\Action\TextToText\Action; | ||
|
||
final class TranslateAction extends Action | ||
{ | ||
public function getActionTypeIdentifier(): string | ||
{ | ||
return 'translate'; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
--- | ||
Check warning on line 1 in docs/multisite/languages/automated_translations.md
|
||
description: With the automated translation add-on, users can translate content items into multiple languages with Google Translate or DeepL. | ||
--- | ||
|
||
# Automated content translation | ||
|
||
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. | ||
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. | ||
However, you can also run a Console Command to translate a specific content item. | ||
Either way, as a result, a new version of the content item is created. | ||
|
||
The following field types are supported out of the box: | ||
|
||
- [TextLine](textlinefield.md) | ||
- [TextBlock](textblockfield.md) | ||
- [RichText](richtextfield.md) | ||
- [Page](pagefield.md): the content of `text` and `richtext` [block attributes](page_block_attributes.md#block-attribute-types) | ||
|
||
See [adding a custom field or block attribute encoder](##add-a-custom-field-encoder) for more information on how you can extend this list. | ||
adriendupuis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Configure automated content translation | ||
|
||
### Install package | ||
|
||
The automated content translation feature comes as an additional package that you must download and install separately: | ||
|
||
```bash | ||
composer require ibexa/automated-translation | ||
``` | ||
|
||
!!! caution "Modify the default configuration" | ||
|
||
Symfony Flex installs and activates the package. | ||
However, you must modify the `config/bundles.php` file to change the bundle loading order so that `IbexaAutomatedTranslationBundle` is loaded before `IbexaAdminUiBundle`: | ||
|
||
```php | ||
<?php | ||
|
||
return [ | ||
// ... | ||
Ibexa\Bundle\AutomatedTranslation\IbexaAutomatedTranslationBundle::class => ['all' => true], | ||
Ibexa\Bundle\AdminUi\IbexaAdminUiBundle::class => ['all' => true], | ||
// ... | ||
]; | ||
``` | ||
|
||
### Configure access to translation services | ||
|
||
Before you can start using the feature, you must configure access to your Google and/or DeepL account. | ||
|
||
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). | ||
|
||
2\. Set these values in the YAML configuration files, under the `ibexa_automated_translation.system.default.configurations` key: | ||
|
||
``` yaml | ||
ibexa_automated_translation: | ||
system: | ||
default: | ||
configurations: | ||
google: | ||
apiKey: "google-api-key" | ||
deepl: | ||
authKey: "deepl-pro-key" | ||
``` | ||
The configuration is SiteAccess-aware, therefore, you can configure different engines to be used for different sites. | ||
## Translate content items with CLI | ||
To create a machine translation of a specific content item, you can use the `ibexa:automated:translate` command. | ||
|
||
The following arguments and options are supported: | ||
|
||
- `--from` - the source language | ||
- `--to` - the target language | ||
- `contentId` - ID of the content to translate | ||
- `serviceName` - the service to use for translation | ||
|
||
For example, to translate the root content item from English to French with the help of Google Translate, run: | ||
|
||
``` bash | ||
php bin/console ibexa:automated:translate --from=eng-GB --to=fre-FR 52 google | ||
``` | ||
|
||
## Extend automated content translations | ||
|
||
### Add a custom machine translation service | ||
|
||
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. | ||
You would do it, for example, when a new service emerges on the market, or your company requires that a specific service is used. | ||
|
||
The following example adds a new translation service. | ||
It uses the [AI actions framework](ai_actions_md) and assumes a custom `TranslateAction` AI Action exists. | ||
adriendupuis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
To learn how to build custom AI actions see [Extending AI actions](extend_ai_actions.md#custom-action-type-use-case). | ||
|
||
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: | ||
Check failure on line 96 in docs/multisite/languages/automated_translations.md
|
||
|
||
``` php hl_lines="35-52" | ||
[[= include_file('code_samples/multisite/automated_translation/src/AutomatedTranslation/AiClient.php') =]] | ||
``` | ||
|
||
2\. Tag the service as `ibexa.automated_translation.client` in the Symfony container: | ||
|
||
``` yaml | ||
[[= include_file('code_samples/multisite/automated_translation/config/services.yaml', 15, 18) =]] | ||
``` | ||
|
||
3\. Specify the configuration under the `ibexa_automated_translation.system.default.configurations` key: | ||
|
||
``` yaml | ||
[[= include_file('code_samples/multisite/automated_translation/config/services.yaml', 23, 32) =]] | ||
``` | ||
|
||
### Create custom field or block attribute encoder | ||
|
||
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 =]]. | ||
|
||
The whole automated translation process consists of 3 phases: | ||
|
||
1. **Encoding** - data is extracted from the field types and block attributes and serialized into XML format | ||
1. **Translating** - the serialized XML is sent into specified translation service | ||
1. **Decoding** - the translated response is deserialized into the original data structures for storage in [[= product_name =]] | ||
|
||
The following example adds support for automatically translating alternative text in image fields. | ||
|
||
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: | ||
Check failure on line 126 in docs/multisite/languages/automated_translations.md
|
||
|
||
``` php hl_lines="11-14 16-19 21-27 33-38" | ||
[[= include_file('code_samples/multisite/automated_translation/src/AutomatedTranslation/ImageFieldEncoder.php') =]] | ||
``` | ||
In this example, the methods are responsible for: | ||
|
||
- `canEncode` - deciding whether the field to be encoded is an [Image](imagefield.md) field | ||
- `canDecode` - deciding whether the field to be decoded is an [Image](imagefield.md) field | ||
- `encode` - extracting the alternative text from the field type | ||
- `decode` - saving the translated alternative text in the field type's value object | ||
|
||
2\. Register the class as a service. | ||
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. | ||
|
||
``` yaml | ||
[[= include_file('code_samples/multisite/automated_translation/config/services.yaml', 19, 22) =]] | ||
``` | ||
|
||
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`. | ||
Check failure on line 145 in docs/multisite/languages/automated_translations.md
|
Uh oh!
There was an error while loading. Please reload this page.