Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions code_samples/multisite/automated_translation/config/services.yaml
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';
}
}
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
}
],
"require-dev": {
"ibexa/automated-translation": "5.0.x-dev",
"ibexa/code-style": "^1.0",
"friendsofphp/php-cs-fixer": "^3.30",
"phpstan/phpstan": "^2.0",
Expand Down Expand Up @@ -58,7 +59,6 @@
"ibexa/activity-log": "5.0.x-dev",
"ibexa/workflow": "5.0.x-dev",
"ibexa/checkout": "5.0.x-dev",
"ibexa/oauth2-server": "5.0.x-dev",
"ibexa/elasticsearch": "5.0.x-dev",
"ibexa/oauth2-client": "5.0.x-dev",
"ibexa/form-builder": "5.0.x-dev",
Expand Down
145 changes: 145 additions & 0 deletions docs/multisite/languages/automated_translations.md
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

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/multisite/languages/automated_translations.md#L1

[Ibexa.ReadingLevel] The grade level is 10.25. Aim for 8th grade or lower by using shorter sentences and words.
Raw output
{"message": "[Ibexa.ReadingLevel] The grade level is 10.25. Aim for 8th grade or lower by using shorter sentences and words.", "location": {"path": "docs/multisite/languages/automated_translations.md", "range": {"start": {"line": 1, "column": 1}}}, "severity": "WARNING"}
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.

## 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.
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

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/multisite/languages/automated_translations.md#L96

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/multisite/languages/automated_translations.md", "range": {"start": {"line": 96, "column": 44}}}, "severity": "ERROR"}

``` 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

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/multisite/languages/automated_translations.md#L126

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/multisite/languages/automated_translations.md", "range": {"start": {"line": 126, "column": 106}}}, "severity": "ERROR"}

``` 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

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/multisite/languages/automated_translations.md#L145

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/multisite/languages/automated_translations.md", "range": {"start": {"line": 145, "column": 138}}}, "severity": "ERROR"}
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ nav:
- Languages: multisite/languages/languages.md
- Language API: multisite/languages/language_api.md
- Back office translations: multisite/languages/back_office_translations.md
- Automated content translation: multisite/languages/automated_translations.md
- Permissions:
- Permissions: permissions/permissions.md
- Permission overview: permissions/permission_overview.md
Expand Down
Loading