diff --git a/code_samples/translations_management/config/services.yaml b/code_samples/translations_management/config/services.yaml new file mode 100644 index 00000000000..6925c8f441b --- /dev/null +++ b/code_samples/translations_management/config/services.yaml @@ -0,0 +1,36 @@ +services: + App\TranslationsManagement\MyCustomProvider: + tags: + - name: 'ibexa.translations_management.auto_translate.provider' + identifier: 'my_custom_provider' + validation_profile: 'my_custom_profile' + App\TranslationsManagement\MyProviderValidator: + tags: + - name: 'ibexa.translations_management.auto_translate.provider.validator' + profile: 'my_custom_profile' + App\TranslationsManagement\MyTranslationAddExtension: + tags: + - { name: form.type_extension } + App\TranslationsManagement\ImageAltTextTransformer: + tags: + - name: 'ibexa.translations_management.auto_translate.field_value_transformer' + field_type_identifier: 'ibexa_image' + App\TranslationsManagement\MyCustomExclusionRule: + tags: + - { name: 'ibexa.translations_management.side_by_side.exclusion_rule' } + app.translations_management.exclusion_rule.custom_field_types: + class: Ibexa\TranslationsManagement\SideBySide\Service\UnsupportedFieldTypeExclusionRule + arguments: + $excludedFieldTypeIdentifiers: ['custom_blog_post', 'custom_landing_page'] + tags: + - { name: 'ibexa.translations_management.side_by_side.exclusion_rule' } + App\TranslationsManagement\TwigComponent\MyTranslationModalFooter: + tags: + - name: ibexa.twig.component + group: 'admin-ui-content-translation-modal-footer' + priority: 10 + App\TranslationsManagement\MyCustomAiProvider: + tags: + - name: 'ibexa.translations_management.auto_translate.provider' + identifier: 'my_custom_ai_provider' + validation_profile: 'ai_generic' diff --git a/code_samples/translations_management/install/schema.mysql.sql b/code_samples/translations_management/install/schema.mysql.sql new file mode 100644 index 00000000000..e45471a1bbc --- /dev/null +++ b/code_samples/translations_management/install/schema.mysql.sql @@ -0,0 +1,35 @@ +CREATE TABLE IF NOT EXISTS ibexa_auto_translation ( + id INT AUTO_INCREMENT NOT NULL, + provider_identifier VARCHAR(190) NOT NULL, + content_id INT NOT NULL, + version_no INT NOT NULL, + source_language_id BIGINT NOT NULL, + target_language_id BIGINT NOT NULL, + review_status VARCHAR(64) NOT NULL, + created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', + updated_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', + INDEX ibexa_auto_translation_content_version_idx (content_id, version_no), + INDEX ibexa_auto_translation_target_language_idx (target_language_id), + INDEX ibexa_auto_translation_review_status_idx (review_status), + UNIQUE INDEX ibexa_auto_translation_context_uidx (content_id, version_no, source_language_id, target_language_id), + PRIMARY KEY(id) +) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; + +CREATE TABLE IF NOT EXISTS ibexa_auto_translation_review_log ( + id INT AUTO_INCREMENT NOT NULL, + auto_translation_id INT DEFAULT NULL, + user_id INT NOT NULL, + status VARCHAR(64) NOT NULL, + operation VARCHAR(64) NOT NULL, + created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', + INDEX IDX_325A3B737CE350E8 (auto_translation_id), + INDEX ibexa_auto_translation_review_log_auto_translation_created_idx (auto_translation_id, created_at, id), + INDEX ibexa_auto_translation_review_log_status_created_idx (status, created_at), + INDEX ibexa_auto_translation_review_log_user_idx (user_id), + PRIMARY KEY(id) +) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; + +ALTER TABLE ibexa_auto_translation_review_log ADD CONSTRAINT ibexa_auto_translation_review_log_auto_translation_fk + FOREIGN KEY (auto_translation_id) REFERENCES ibexa_auto_translation (id) ON UPDATE CASCADE ON DELETE SET NULL; +ALTER TABLE ibexa_auto_translation_review_log ADD CONSTRAINT ibexa_auto_translation_review_log_user_fk + FOREIGN KEY (user_id) REFERENCES ibexa_user (contentobject_id) ON UPDATE CASCADE ON DELETE RESTRICT; diff --git a/code_samples/translations_management/install/schema.postgresql.sql b/code_samples/translations_management/install/schema.postgresql.sql new file mode 100644 index 00000000000..c8af0e838cb --- /dev/null +++ b/code_samples/translations_management/install/schema.postgresql.sql @@ -0,0 +1,40 @@ +CREATE TABLE IF NOT EXISTS ibexa_auto_translation ( + id SERIAL NOT NULL, + provider_identifier VARCHAR(190) NOT NULL, + content_id INT NOT NULL, + version_no INT NOT NULL, + source_language_id BIGINT NOT NULL, + target_language_id BIGINT NOT NULL, + review_status VARCHAR(64) NOT NULL, + created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, + updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, + PRIMARY KEY(id) +); + +CREATE INDEX IF NOT EXISTS ibexa_auto_translation_content_version_idx ON ibexa_auto_translation (content_id, version_no); +CREATE INDEX IF NOT EXISTS ibexa_auto_translation_target_language_idx ON ibexa_auto_translation (target_language_id); +CREATE INDEX IF NOT EXISTS ibexa_auto_translation_review_status_idx ON ibexa_auto_translation (review_status); +CREATE UNIQUE INDEX IF NOT EXISTS ibexa_auto_translation_context_uidx ON ibexa_auto_translation (content_id, version_no, source_language_id, target_language_id); +COMMENT ON COLUMN ibexa_auto_translation.created_at IS '(DC2Type:datetime_immutable)'; +COMMENT ON COLUMN ibexa_auto_translation.updated_at IS '(DC2Type:datetime_immutable)'; + +CREATE TABLE IF NOT EXISTS ibexa_auto_translation_review_log ( + id SERIAL NOT NULL, + auto_translation_id INT DEFAULT NULL, + user_id INT NOT NULL, + status VARCHAR(64) NOT NULL, + operation VARCHAR(64) NOT NULL, + created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, + PRIMARY KEY(id) +); + +CREATE INDEX IF NOT EXISTS IDX_325A3B737CE350E8 ON ibexa_auto_translation_review_log (auto_translation_id); +CREATE INDEX IF NOT EXISTS ibexa_auto_translation_review_log_auto_translation_created_idx ON ibexa_auto_translation_review_log (auto_translation_id, created_at, id); +CREATE INDEX IF NOT EXISTS ibexa_auto_translation_review_log_status_created_idx ON ibexa_auto_translation_review_log (status, created_at); +CREATE INDEX IF NOT EXISTS ibexa_auto_translation_review_log_user_idx ON ibexa_auto_translation_review_log (user_id); +COMMENT ON COLUMN ibexa_auto_translation_review_log.created_at IS '(DC2Type:datetime_immutable)'; + +ALTER TABLE ibexa_auto_translation_review_log ADD CONSTRAINT ibexa_auto_translation_review_log_auto_translation_fk + FOREIGN KEY (auto_translation_id) REFERENCES ibexa_auto_translation (id) ON UPDATE CASCADE ON DELETE SET NULL; +ALTER TABLE ibexa_auto_translation_review_log ADD CONSTRAINT ibexa_auto_translation_review_log_user_fk + FOREIGN KEY (user_id) REFERENCES ibexa_user (contentobject_id) ON UPDATE CASCADE ON DELETE RESTRICT; diff --git a/code_samples/translations_management/src/TranslationsManagement/ContentProxyTranslateSubscriber.php b/code_samples/translations_management/src/TranslationsManagement/ContentProxyTranslateSubscriber.php new file mode 100644 index 00000000000..cb9e07c8705 --- /dev/null +++ b/code_samples/translations_management/src/TranslationsManagement/ContentProxyTranslateSubscriber.php @@ -0,0 +1,41 @@ + ['onProxyTranslate', 200], + ]; + } + + public function onProxyTranslate(ContentProxyTranslateEvent $event): void + { + // Read the translation context: + $event->getContentId(); + $event->getFromLanguageCode(); // ?string — null when no source language exists + $event->getToLanguageCode(); + $event->getLocationId(); // ?int — null when no location context is available + + $url = $this->urlGenerator->generate('your_custom_route', [ + 'contentId' => $event->getContentId(), + ]); + + $event->setResponse(new RedirectResponse($url)); + $event->stopPropagation(); + } +} diff --git a/code_samples/translations_management/src/TranslationsManagement/ImageAltTextTransformer.php b/code_samples/translations_management/src/TranslationsManagement/ImageAltTextTransformer.php new file mode 100644 index 00000000000..d11ec9b1305 --- /dev/null +++ b/code_samples/translations_management/src/TranslationsManagement/ImageAltTextTransformer.php @@ -0,0 +1,60 @@ +getValue(); + if (!$value instanceof ImageValue) { + throw new InvalidArgumentException( + '$field', + sprintf('Expected %s, got %s.', ImageValue::class, get_debug_type($value)) + ); + } + + return new EncodedFieldValue($value->alternativeText ?? ''); + } + + /** + * @param array $metadata + */ + public function decode(string $value, mixed $previousFieldValue, array $metadata): Value + { + if (!$previousFieldValue instanceof ImageValue) { + throw new InvalidArgumentException( + '$previousFieldValue', + sprintf('Expected %s, got %s.', ImageValue::class, get_debug_type($previousFieldValue)) + ); + } + + return new ImageValue([ + 'id' => $previousFieldValue->id, + 'fileName' => $previousFieldValue->fileName, + 'fileSize' => $previousFieldValue->fileSize, + 'uri' => $previousFieldValue->uri, + 'imageId' => $previousFieldValue->imageId, + 'inputUri' => $previousFieldValue->inputUri, + 'width' => $previousFieldValue->width, + 'height' => $previousFieldValue->height, + 'alternativeText' => $value, + 'additionalData' => $previousFieldValue->additionalData, + 'mime' => $previousFieldValue->mime, + ]); + } +} diff --git a/code_samples/translations_management/src/TranslationsManagement/MyApiClient.php b/code_samples/translations_management/src/TranslationsManagement/MyApiClient.php new file mode 100644 index 00000000000..4091bb52859 --- /dev/null +++ b/code_samples/translations_management/src/TranslationsManagement/MyApiClient.php @@ -0,0 +1,15 @@ +apiClient->translate( + $translationData->getText(), + $translationData->getSourceLanguage(), + $translationData->getTargetLanguage() + ); + } + + /** @return array */ + public function getSupportedLanguageCodes(): array + { + return ['eng-GB', 'ger-DE', 'fre-FR']; + } + + /** @return array */ + public function getConfiguration(): array + { + return [ + 'actionConfigurationIdentifier' => $this->actionConfigurationIdentifier, + ]; + } + + public function isConfigured(): bool + { + return $this->actionConfigurationIdentifier !== ''; + } +} diff --git a/code_samples/translations_management/src/TranslationsManagement/MyCustomExclusionRule.php b/code_samples/translations_management/src/TranslationsManagement/MyCustomExclusionRule.php new file mode 100644 index 00000000000..48a55e68e04 --- /dev/null +++ b/code_samples/translations_management/src/TranslationsManagement/MyCustomExclusionRule.php @@ -0,0 +1,16 @@ +getContentType()->identifier === 'my_excluded_type'; + } +} diff --git a/code_samples/translations_management/src/TranslationsManagement/MyCustomProvider.php b/code_samples/translations_management/src/TranslationsManagement/MyCustomProvider.php new file mode 100644 index 00000000000..e726d3435fd --- /dev/null +++ b/code_samples/translations_management/src/TranslationsManagement/MyCustomProvider.php @@ -0,0 +1,50 @@ +apiClient->translate( + $translationData->getText(), + $translationData->getSourceLanguage(), + $translationData->getTargetLanguage() + ); + } + + /** @return array */ + public function getSupportedLanguageCodes(): array + { + return ['eng-GB', 'ger-DE', 'fre-FR']; + } +} diff --git a/composer.json b/composer.json index d229c7579a5..a05f70e2214 100644 --- a/composer.json +++ b/composer.json @@ -86,7 +86,8 @@ "ibexa/cdp": "~5.0.x-dev", "ibexa/connector-raptor": "~5.0.x-dev", "ibexa/image-editor": "~5.0.x-dev", - "ibexa/integrated-help": "~5.0.x-dev" + "ibexa/integrated-help": "~5.0.x-dev", + "ibexa/translations-management": "~5.0.x-dev" }, "scripts": { "fix-cs": "php-cs-fixer fix --config=.php-cs-fixer.php -v --show-progress=dots", diff --git a/docs/api/event_reference/event_reference.md b/docs/api/event_reference/event_reference.md index 6cc5792d698..49396a4264f 100644 --- a/docs/api/event_reference/event_reference.md +++ b/docs/api/event_reference/event_reference.md @@ -37,6 +37,7 @@ For example, copying a content item is connected with two events: `BeforeCopyCon "api/event_reference/segmentation_events", "api/event_reference/site_events", "api/event_reference/taxonomy_events", + "api/event_reference/translations_management_events", "api/event_reference/trash_events", "api/event_reference/twig_component_events", "api/event_reference/url_events", diff --git a/docs/api/event_reference/translations_management_events.md b/docs/api/event_reference/translations_management_events.md new file mode 100644 index 00000000000..79f08c33ed9 --- /dev/null +++ b/docs/api/event_reference/translations_management_events.md @@ -0,0 +1,29 @@ +--- +description: Events that are triggered when working with translations management. +edition: lts-update +page_type: reference +--- + +# Translations management events + +The [Translations management](configure_translations_management.md) package dispatches events at two levels. + +## Translation events + +Translation events are thrown once per field value per translation operation. +They are used for logging, analytics, and observability. +Both events are read-only, you can't use them to override the translation result. + +| Event | Dispatched by | Dispatched when | Properties | +|---|---|---|----| +| [`BeforeTranslateEvent`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-AutoTranslate-Event-BeforeTranslateEvent.html) | `EventDispatchingProviderTranslator` | Before a translation request is sent to the provider | `TranslationProviderInterface $provider`
`string $text`
`string $sourceLanguage`
`string $targetLanguage` | +| [`TranslateEvent`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-AutoTranslate-Event-TranslateEvent.html) | `EventDispatchingProviderTranslator` | After a translation response is received | `string $result`
`TranslationProviderInterface $provider`
`string $text`
`string $sourceLanguage`
`string $targetLanguage` | + +## Side-by-side creation events + +Side-by-side creation events are dispatched when a new translation draft is being prepared. + +| Event | Dispatched by | Dispatched when | Properties | +|---|---|---|---| +| [`OnContentSideBySideTranslationCreateEvent`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-SideBySide-Event-OnContentSideBySideTranslationCreateEvent.html) | `ContentTranslationCreateController` | When a draft side-by-side translation of a content item is being created | `Request $request`
`Content $sourceContent`
`string $sourceLanguageCode`
`string $targetLanguageCode`
`?Content $targetDraft` | +| [`OnProductSideBySideTranslationCreateEvent`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-SideBySide-Event-OnProductSideBySideTranslationCreateEvent.html) | `ProductTranslationViewController` | When a draft side-by-side translation of a product is being created | `Request $request`
`ContentAwareProductInterface $sourceProduct`
`ContentAwareProductInterface $targetProduct`
`string $sourceLanguageCode`
`string $targetLanguageCode`
`?ProductUpdateData $productUpdateData` | diff --git a/docs/ibexa_products/editions.md b/docs/ibexa_products/editions.md index fc5296196e7..5bc7c73bf9b 100644 --- a/docs/ibexa_products/editions.md +++ b/docs/ibexa_products/editions.md @@ -71,3 +71,4 @@ The features brought by LTS Updates become standard parts of the next LTS releas | [Integrated help](integrated_help.md) | ✔ | ✔ | ✔ | | [MCP servers](mcp_guide.md) | ✔ | ✔ | ✔ | | [Shopping list](shopping_list_guide.md) | | | ✔ | +| [Translations management](translations_management_guide.md) | ✔ | ✔ | ✔ | diff --git a/docs/multisite/img/diagram_source/translations_management_flow.drawio b/docs/multisite/img/diagram_source/translations_management_flow.drawio new file mode 100644 index 00000000000..e289d5a54ee --- /dev/null +++ b/docs/multisite/img/diagram_source/translations_management_flow.drawio @@ -0,0 +1 @@ +5Zptc6M2EIB/DTPtB2d4MRh/tB3Hcc5pPfW0l/uUkUEG9WTkCuGX+/WVQNiAyJn0iJ1cZzxjWEmw++xqWQk0a7TeTyjYhI/Eh1gzdX+vWbeaaRqGaWrip/sHKbFtJ5MEFPlSdhIs0DcohbqUJsiHcakjIwQztCkLPRJF0GMlGaCU7MrdVgSX77oBAVQECw9gVfoZ+SzMDdP1U8M9REEob+3asmEN8s5SEIfAJ7uCyBpr1ogSwrKj9X4EsaCXc8nG3b3QelSMwog1GTDIBmwBTqRtA3+NIqEZZDH/SzYCLgVRjAFDRLRsKNly/jSWJrBDzoWSJPKhuLShWcNdiBhcbIAnWnc8FLgsZGssm1cI4xHBhKZjLR9Ad+Vxecwo+QoLLY7nwuWKt6jGSXu3kDK4L4iksRNI1pDRA+8iW007GyEjr2PkIbUr+NGVsrDgQkfKgAyd4HjpE11+IAHXwx4qsMc+YtxMU6fwnwTGKfEI7qrI28RsQ9fv1mF2zaXlOC1h7l+T80jh/AeMCd7CuBC9mulgfs/hUhwF4ugXDKIg4VO3swFIOEXckM+AWHTm8wHzVMLd8Wur/litVqZXG/a+s3TslvxhdPWKP1R3HHNS0R29Ftxxp4b9noe3l0Z7HudgyWnyVIwg9ttNLJchXEks/QuG+0Thu4CRn8IlLybvj4i4fz3G9zUpxYMoSyk5YU5PIKUoCj5iCBvlHGFahpoj9DfiO1X4jijkRFO8gAaQdfLsLApHClbsAxI23Ssifvhe9bFFcBenj7zqUxFEIqrXxEc8N6fuCOGHr0+MbjmVWOYFHfFJzddgC7OLvkVsXwhpObS7NUQNu4ao3QLRmUo05LbE6e353fMIb/mxdyGwPfs82d4bkX1UyM6TJUZxCFte/V0EpeU0CNK6MrgNlLBmcS5FMUmol6+/M1H20NOK6xnol3YlVCspFAl5W96k+CGVzUYqD9+TylYjlUeqyndtq5wOHVAKDoUOG4IiFheuPBeC4jqiPNtdvWJ9dsETi6NmzfB0G+G5U/FMruZRW8lB2sjUBr1GlrSudr3bjlt/x8qukjEylHLUyfjXhofllsPD6Ff2987oVen/4/HkNPLCvRpP06vFU7PAmaoqP7yLDGFVFhF57dqaS91GfB5UPp+u5tL+f1V5djWV83r59To/XktnFPT8ER3iu0n09LsxGuyecNLJi7BCdq4WhnEINuLQSyg+DCnwvgpTzlWI5XJyhdHmXh5r9WVfDcsXK0G7slzpHMu+0gpQUyrBrtlCKVgL0vopQFp63Upa5Wj0+m/EsatwnM/+nEx/U2i+ClsbqKwyKkeNuNrFRxt7DrWkHIXUYno77gy/dMQ/b/lrOv787rDxRdyNfWVy7vm5KmxGHsAzsIR4TmKUbpVZt0vCGFlzNHmHAUaBaGCkQjKf7et9IN6j3yxBjLyb7nNaIDzH/JHwzLEN0/fq+o3bDu3jZD2+m3cU1k4N6jZeFNWi7v9/UNckhLqofrMnUJ7KC2hFkbGQp4SykAQkAnh8khbmu87PTn1mREBO6f4NGTvIrzhAwkiZPdwj9pRyteXZl0LL7V5eOT05FE7mkCJuN6RSptVVRWfrq+9N72LR9XK/qxVeDSqvn2RmHLfi258Z/PT0tU22Wjp9tGSN/wU= \ No newline at end of file diff --git a/docs/multisite/img/managing_translations_sxs_view.png b/docs/multisite/img/managing_translations_sxs_view.png new file mode 100644 index 00000000000..466a8465bf7 Binary files /dev/null and b/docs/multisite/img/managing_translations_sxs_view.png differ diff --git a/docs/multisite/img/translations_management_flow.png b/docs/multisite/img/translations_management_flow.png new file mode 100644 index 00000000000..61a7d044712 Binary files /dev/null and b/docs/multisite/img/translations_management_flow.png differ diff --git a/docs/multisite/translations_management/configure_translations_management.md b/docs/multisite/translations_management/configure_translations_management.md new file mode 100644 index 00000000000..3a3ffd8113c --- /dev/null +++ b/docs/multisite/translations_management/configure_translations_management.md @@ -0,0 +1,318 @@ +--- +description: Install translations management configure translation providers, language pairs, and more. +edition: lts-update +month_change: true +--- + +# Configure translations management + +`ibexa/translations-management` extends [[= product_name =]]'s built-in language management tools that editors use for content item and product translation. +It introduces a plugin that handles automatic translations through the translation provider system by connecting to REST APIs and AI services, a [side-by-side editing interface](#side-by-side-translation-view) where editors can compare source and target , provide content item and product translations in a single view, and reject or approve translations, and multiple extension points that you can use to [customize different areas of the translation workflow](extend_translations_management.md). + +!!! note "Automatic translation limitations" + + Content types that contain the `ibexa_form` or `ibexa_landing_page` fields do not support the side-by-side translation view and open in the single-language editor instead. + When a content type that uses `ibexa_landing_page` is automatically translated, only the page's title and description are translated. + When a content type that uses `ibexa_form` is automatically translated, only the forms's title is translated. + + Also, [product attributes](products.md#product-attributes) are not translatable. + +## Install package + +To install the Translations management [LTS Update](editions.md#lts-updates), run the following command: + +```bash +composer require ibexa/translations-management +``` + +If you're installing Translations management LTS Update as part of the installation process of a fresh [[= product_name =]] instance, this step copies the migration files into the project's migrations directory, creates the database tables required for the review workflow, and adds the default action configurations in the database. +Otherwise follow the steps below. + +### Existing installations + +To add the Translations management LTS Update to an existing [[= product_name =]] instance, after installation, you must create database tabes and action configurations yourself. + +#### Modify database schema + +Add the tables needed by the bundle: + +=== "MySQL" + + ```sql + [[= include_file('code_samples/translations_management/install/schema.mysql.sql', 0, None, ' ') =]] + ``` + +=== "PostgreSQL" + + ```sql + [[= include_file('code_samples/translations_management/install/schema.postgresql.sql', 0, None, ' ') =]] + ``` + +The script creates the required data structures, but doesn't add any data to the database. + +#### Add action configurations + +Import and run the AI Action Configuration migrations to complete the setup: + +```bash +php bin/console ibexa:migrations:import vendor/ibexa/translations-management/src/bundle/Resources/migrations/2026_05_06_15_00_auto_translate_openai_action_configuration.yaml +php bin/console ibexa:migrations:import vendor/ibexa/translations-management/src/bundle/Resources/migrations/2026_05_11_10_00_auto_translate_gemini_action_configuration.yaml +php bin/console ibexa:migrations:import vendor/ibexa/translations-management/src/bundle/Resources/migrations/2026_05_12_08_30_auto_translate_anthropic_action_configuration.yaml +php bin/console ibexa:migrations:migrate +``` + +## Configure translation providers + +Translation providers are the services that perform the actual text translation. + If you fail to configure them, the automatic translation feature is disabled in the editor's UI, and a message is displayed that prompts the user to contact the administrator + +The Translations management package comes with two types of translation services: + +- REST API-based providers call a translation service such as Google Translate or DeepL directly by using an API key. +- AI-based providers send translation requests through the [AI Actions](configure_ai_actions.md) framework, relying on the same model selection and policy controls as other AI features in [[= product_name =]]. + +!!! note "Prerequisites for the default translation providers" + + Before you can configure translation providers, you must fulfill the following prerequisites: + + - For the REST API-based translation providers, add API keys that you obtain from the machine translation services to the `.env` file in the root directory of your project. + + - For the AI-based translation providers, [install and configure](configure_ai_actions.md) the `ibexa/connector-ai` package and their corresponding connectors. + +Out of the box, Translations management can support the following translation providers: + +| Provider | Type | +|---|---|---| +| Google Translate | REST API | +| DeepL | REST API | +| OpenAI | AI Actions | +| Anthropic (Claude) | AI Actions | +| Google Gemini | AI Actions | + +**Built-in AI providers** + +If you fulfill the above prerequisites, and you install the Translations management package, the installation process automatically creates AI [Action Configurations](extend_ai_actions.md#action-configurations) for OpenAI (`auto_translate_openai`), Google Gemini (`auto_translate_gemini`), and Anthropic Claude (`auto_translate_anthropic`). + +You can use them directly in provider configuration: + +| Action Configuration identifier | Handler | Default model | +|---|---|---| +| `auto_translate_openai` | `openai-text-to-text` | `gpt-5` | +| `auto_translate_gemini` | `gemini-text-to-text` | `gemini-pro-latest` | +| `auto_translate_anthropic` | `anthropic-text-to-text` | `claude-sonnet-4-20250514` | + +You can then [customize these configurations in the UI]([[= user_doc =]]/ai_actions/work_with_ai_actions/#edit-existing-ai-actions). + +### Add YAML configuration + +In `config/packages`, create a `translations_management.yaml` file. +You configure the providers in the SiteAccess-aware `translations_management` namespace. + +``` yaml +ibexa: + system: + default: + translations_management: + auto_translate: + providers: + google: + apiKey: '%env(GOOGLE_TRANSLATE_API_KEY)%' + deepl: + apiKey: '%env(DEEPL_API_KEY)%' + openai: + actionConfigurationIdentifier: 'auto_translate_openai' + anthropic: + actionConfigurationIdentifier: 'auto_translate_anthropic' + gemini: + actionConfigurationIdentifier: 'auto_translate_gemini' +``` + +The `apiKey` values must reference API key values that you added to the `.env` file. +The `actionConfigurationIdentifier` values must reference existing Action Configurations. +If a value is missing or empty, the provider doesn't appear in the UI as a selectable option. + +#### Advanced translation provider options + +In addition to their required authentication keys, all providers support two optional ones: + +- `supportedLanguageCodes` - overrides the default list of language codes that this provider accepts +- `languageCodesMap` - maps language codes used by [[= product_name =]], for example, `eng-GB`, to the provider-specific codes the API expects + +``` yaml +ibexa: + system: + default: + translations_management: + auto_translate: + providers: + # ... + openai: + actionConfigurationIdentifier: 'auto_translate_openai' + supportedLanguageCodes: + - 'eng-GB' + - 'ger-DE' + - 'fre-FR' + languageCodesMap: + eng-GB: 'en' + ger-DE: 'de' + fre-FR: 'fr' +``` + +The `supportedLanguageCodes` setting controls which languages are available when creating [language pairs](#define-language-pairs) for this provider. + +!!! note "Identifier normalization" + + Provider identifiers are normalized from hyphens to underscores during configuration processing. + Use one format consistently. + If you mix `my-provider` and `my_provider` for the same provider, it results in an exception. + +## Define language pairs + +Language pair definitions decide which provider handles each source-to-target language combination by default. +For example, you can decide that English to French translations should use DeepL. +When an editor [opens the translation modal]([[= user_doc =]]/content_management/translate_content/#add-new-translation) and selects a matching language combination, the provider that you chose is pre-selected in the dropdown. +The editor can override the pre-selection. + +The list of languages available when creating a language pair is determined by what each provider supports. +You can only select the languages that are present in a provider's [supported list](#advanced-translation-provider-options) for that provider's pairs. + +You [manage language pairs in the back office]([[= user_doc =]]/content_management/translate_content/#manage-translation-services-and-language-pairs). + +## User settings + +The Translations management package adds preferences that editors can configure under their [user settings]([[= user_doc =]]/getting_started/get_started/#user-settings). +Each editor can configure them independently, and they do not affect other users. + +For example, editors can choose whether the target language column appears on the left or right in the side-by-side view. +By default, the target is on the right, and each editor can override this default. + +You can change the system-wide default in configuration: + +``` yaml +ibexa: + system: + default: + translations_management: + default_side_by_side_column_order: 'source_left_target_right' +``` + +The accepted values are `source_left_target_right` (default) and `source_right_target_left`. + +## Side-by-side translation view + +The [side-by-side translation view]([[= user_doc =]]/content_management/translate_content/#side-by-side-translation-view) is a two-column content editing interface where the source column is read-only and the target column is an editable form. + +Content types that contain the `ibexa_landing_page` or `ibexa_form` fields can't be opened in the side-by-side translation view. +Editors can open them in the standard single-language editor. + +You can exclude the support for additional content types if needed. +To do it, [define custom exclusion rules](extend_translations_management.md#define-custom-exclusion-rules). + +### Architecture + +The side-by-side view consists of three forms placed in a single Twig template: + +- `view.sourcePreviewForm` — the source language content, rendered as read-only fields +- `view.form` — the target language content, rendered as editable fields +- `view.copyAllForm` — the **Copy all from source** action + +To assemble the view, `SideBySideEditContextBuilder` performs the following actions: + +1. Resolves source and target languages +2. Loads the correct content version +3. Groups fields by their content type field groups + +!!! note "Meta fields" + + The builder excludes the fields that are marked marked as `meta: true` or belong to a field group that is listed in `admin_ui_forms.content_edit.meta_field_groups_list`, and does not render them. + +To resolve the column order, `SideBySideTargetLanguagePositionResolver` reads the user setting and falls back to `source_left_target_right` when the setting is not made. +The Twig template applies `order-xl-*` classes for responsive column placement. + +### Side-by-side view behavior + +Editors have multiple ways to arrive at the side-by-side translation view, for example: + +- From the **Create a new translation** modal, by clicking the **Open side-by-side** action. + This submits the modal to the `ibexa.translations_management.side_by_side_create` route, which creates a new draft and redirects to `side_by_side_view` with the resolved `versionNo`. + +- From the **Versions** tab, by clicking the **Edit side-by-side** action next to a draft whose source and target languages differ. + This doesn't create a new draft, and the existing version number is used. + +!!! tip "Routes" + + The Translations management package registers internal back office routes. + To list them with their current paths, run: + + ``` bash + php bin/console debug:router | grep translations_management + ``` + +### Side-by-side view functions + +The side-by-side translation view has several functions, including: + +- Copy all from source + +When an editor clicks the **Copy all from source** action, all translatable field values are copied from the source to target column. +It's a single server-side operation handled by `SideBySideFieldCopyService::copyAllFields()` after which the view is reloaded. + +- Draft conflict warning + +When an user opens the translation modal and selects a target language which already has a draft translation, a warning appears in the modal. +The warning is shown or hidden dynamically by `add.translation.modal.warning.js` when the user changes the target language selection. + +For a description of the side-by-side view and its functions from the user's perspective, see [Translate content](([[= user_doc =]]/content_management/translate_content/#side-by-side-translation-view). + +## Translate content items with CLI + +For the purposes of batch processing, automation and other scripted actions, the Translations management package exposes a command that translates content items by using any of the configured providers: + +``` bash +php bin/console ibexa:translations:auto-translate-content \ + --content-id=42 \ + --provider=deepl \ + --from=eng-GB \ + --to=fre-FR +``` + +!!! tip "Command alias" + + You can use `ibexa:translations:translate-content` as an alias. + +The command uses the same provider configuration and field value transformers as the UI, so the results are the same if an editor triggered the translation manually. + +### CLI command options + +| Option | Required | Description | +|---|---|---| +| `--content-id` | Yes | ID of the content item to translate | +| `--provider` | Yes | Identifier of the translation provider to use | +| `--from` | Yes | Source language code | +| `--to` | Yes | Target language code | +| `--user-id` | No | Repository user ID to run the translation (default: `14`, which is the Administrator user) | +| `--draft-only` | No | Create a translated draft without publishing it | + +## Translation review + +When a draft translation of a content item or product is created by going through the automatic translation process, the system creates a review status record and marks the draft `for_review`. +This way editors and reviewers can check whether automatically translated drafts have been checked before publishing. + +Automatically translated drafts can have one of the following two states: +- `for_review` - The draft was machine-translated and is awaiting review. +- `translated` - The translation has been accepted by a reviewer. + +The `ibexa_auto_translation_review` workflow has two transitions: + +| Transition | From | To | +|---|---|---| +| `approved` | `for_review` | `translated` | +| `rejected` | `for_review` | `for_review` | + +When the editor rejects the translation, the status doesn't change, but the system records that the draft translation requires corrections. +A draft translation in `translated` state can't be rejected. + +!!! note + + This workflow is separate from the [editorial workflow](workflow.md). + Accepting or rejecting draft translations does not trigger editorial workflow transitions or notifications. diff --git a/docs/multisite/translations_management/extend_translations_management.md b/docs/multisite/translations_management/extend_translations_management.md new file mode 100644 index 00000000000..b0f9e2be145 --- /dev/null +++ b/docs/multisite/translations_management/extend_translations_management.md @@ -0,0 +1,218 @@ +--- +description: Extend translations management - add custom classes, exclude custom content types and intercept the flow. +edition: lts-update +month_change: true +--- + +# Extend translations management + +By extending [Translations management](translations_management_guide.md), you can adapt the package's behavior to your specific requirements. +The package is designed to be extended in multiple ways. +You can create custom [translation providers](configure_translations_management.md#configure-translation-providers), field type transformers, exclusion rules, and UI components. +In all cases, you follow the same pattern: implement an interface first, then register the service with a service tag. +The package discovers and registers tagged services automatically. + +## Add custom translation provider + +Before you build a custom translation provider, if your provider uses the AI Actions framework, make sure that the `ibexa/connector-ai` package is installed in your system. + +### REST API-based provider + +To connect a translation service that calls a REST API directly, implement [`TranslationProviderInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-AutoTranslate-Provider-TranslationProviderInterface.html). +The `translate()` method receives a `TranslationDataInterface` object that carries the text to translate along with the source and target language codes: + +``` php hl_lines="36-49" +[[= include_code('code_samples/translations_management/src/TranslationsManagement/MyCustomProvider.php') =]] +``` + +Register the provider with the `ibexa.translations_management.auto_translate.provider` tag. +Both `identifier` and [`validation_profile`](#validation-profiles) are required attributes. + +``` yaml +[[= include_code('code_samples/translations_management/config/services.yaml', 1, 6) =]] +``` + +### AI-based provider + +To connect a translation service that uses the [AI Actions](ai_actions.md) framework, implement [`AiTranslationProviderInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-AutoTranslate-Provider-AiTranslationProviderInterface.html). +The interface adds `getConfiguration()` and `isConfigured()` to the base provider contract. +These methods allow the package to determine whether the provider is available before it displays selectable options in the **Create a new translation** modal: + +``` php hl_lines="37-50" +[[= include_code('code_samples/translations_management/src/TranslationsManagement/MyCustomAiProvider.php') =]] +``` + +Register the provider with the `ibexa.translations_management.auto_translate.provider` tag, with `ai_generic` as the validation profile. +The `ai_generic` validation profile is used by default for AI providers, but you can [implement your own](#validation-profiles). + +``` yaml +[[= include_file('code_samples/translations_management/config/services.yaml', 0, 1) =]] [[= include_code('code_samples/translations_management/config/services.yaml', 32, 36) =]] +``` + +!!! note "Minimal `getConfiguration()` and `isConfigured()` implementations" + + The sample implements `getConfiguration()` and `isConfigured()` as stubs. + The built-in AI providers delegate these methods to internal services that are not part of the public API and are not available to custom code outside the bundle. + If your custom provider integrates with the AI Actions framework, `isConfigured()` should check whether the `actionConfigurationIdentifier` resolves to an existing and enabled Action Configuration. + +The `validation_profile`, `supportedLanguageCodes`, and `languageCodesMap` options work the same way as for REST API-based providers. + +### Validation profiles + +The `validation_profile` attribute links the provider to a validator that checks language codes and payload size before each before each translation request. +By default, three profiles are available: + +| Profile | Used by | +|---|---| +| `google` | Google Translate provider | +| `deepl` | DeepL provider | +| `ai_generic` | All built-in AI providers. Suitable for custom AI providers. | + +To define a custom validation profile, implement [`ProviderValidatorInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-AutoTranslate-Validator-ProviderValidatorInterface.html) and register it: + +``` yaml +[[= include_code('code_samples/translations_management/config/services.yaml', 1, 1) =]] +[[= include_code('code_samples/translations_management/config/services.yaml', 7, 10) =]] +``` + +You can extend [`DefaultProviderValidator`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-AutoTranslate-Validator-DefaultProviderValidator.html) as base class. +It exposes configurable maximum payload size and language code regex patterns. + +The package also provides several specialized interfaces for providers with specific requirements: + +| Interface | Purpose | +|---|---| +| [`ConfigurableProviderInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-AutoTranslate-Provider-ConfigurableProviderInterface.html) | Extends `TranslationProviderInterface`. Adds `getConfiguration()` and `isConfigured()` for providers that store API keys and other settings | +| [`AiTranslationProviderInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-AutoTranslate-Provider-AiTranslationProviderInterface.html) | Extends `ConfigurableProviderInterface`. Used as a type marker for AI-based providers, it inherits the configuration methods | +| [`TranslationHttpClientInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-AutoTranslate-Http-TranslationHttpClientInterface.html) | For HTTP-based providers that use a REST API pattern | + +## Add support for custom field types + +The translation engine works by extracting translatable text from fields, sending it to the provider, and writing the translated text back. +Field value transformers handle this encode/decode cycle, one per field type. +The package includes transformers for standard text and RichText fields. + +To add support for a custom or non-standard field type, implement [`FieldValueTransformerInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-AutoTranslate-Transformer-Field-FieldValueTransformerInterface.html): + +- `getFieldTypeIdentifier()` - returns the field type identifier that this transformer handles +- `encode(Field $field): EncodedFieldValue` - extracts the translatable string from the field and wraps it in an [`EncodedFieldValue`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-AutoTranslate-Transformer-Field-EncodedFieldValue.html). +The constructor takes the extracted string as its first argument and an optional metadata array as the second. +- `decode(string $value, mixed $previousFieldValue, array $metadata): Value` - receives the translated string, the previous field value, and any metadata. Returns the updated field value. + +``` php hl_lines="21 31 37 46-58" +[[= include_code('code_samples/translations_management/src/TranslationsManagement/ImageAltTextTransformer.php') =]] +``` + +Register the new transformer with the `ibexa.translations_management.auto_translate.field_value_transformer` tag. +The `field_type_identifier` attribute is required. +It must match the value that `getFieldTypeIdentifier()` returns: + +``` yaml +[[= include_code('code_samples/translations_management/config/services.yaml', 1, 1) =]] +[[= include_code('code_samples/translations_management/config/services.yaml', 14, 17) =]] +``` + +If a field type requires metadata, for example, RichText fields with embedded objects that you must preserve after translation, implement [`MetadataAwareFieldValueTransformerInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-AutoTranslate-Transformer-Field-MetadataAwareFieldValueTransformerInterface.html) instead. + +## Define custom exclusion rules + +Use exclusion rules to identify content types that cannot use the side-by-side view. +The Translations management package ships with one rule that excludes content types that contain `ibexa_landing_page` or `ibexa_form` fields. + +### Exclude with custom class + +To exclude additional content types, for example, content types whose fields render incorrectly in the side-by-side layout, implement [`SideBySideExclusionRuleInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-TranslationsManagement-SideBySide-Service-SideBySideExclusionRuleInterface.html). +The `isExcluded()` method receives a [`ContentInfo`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-Content-ContentInfo.html) object and returns `true` if the content item should be excluded. +Register the rule with the `ibexa.translations_management.side_by_side.exclusion_rule` tag. +This interface is not registered for Symfony autoconfiguration, so the tag is required. + +``` php +[[= include_code('code_samples/translations_management/src/TranslationsManagement/MyCustomExclusionRule.php') =]] +``` + + +``` yaml +[[= include_code('code_samples/translations_management/config/services.yaml', 1, 1) =]] +[[= include_code('code_samples/translations_management/config/services.yaml', 18, 20) =]] +``` + +### Exclude with existing class + +`MyCustomExclusionRule` targets one specific content type by name. +To exclude any content type that contain specific field types without the need to write a custom class, register an additional instance of the built-in [`UnsupportedFieldTypeExclusionRule`](https://github.com/ibexa/translations-management/blob/main/src/lib/SideBySide/Service/UnsupportedFieldTypeExclusionRule.php). +Because this registers a second instance of the service with different arguments, you can't use the class name as the service ID. +Use an arbitrary string ID instead to avoid a service definition conflict: + +``` yaml +[[= include_code('code_samples/translations_management/config/services.yaml', 1, 1) =]] +[[= include_code('code_samples/translations_management/config/services.yaml', 21, 26) =]] +``` + +## Use Twig component extension points + +Two Twig component groups allow you to inject custom UI elements into the translation interface without the need to override their templates. +Such custom elements could be: + +- buttons that allow the editor to create a new translation either in the side-by-side view or the standard single-panel editor +- a disclaimer or policy notice that the editor must acknowledge before a translation is created + +| Component group | Location | Variables available | +|---|---|---| +| `admin-ui-content-translation-modal-footer` | Footer of the **Add translation** modal | `form`, `content_id`, `location`, `allow_placeholder` | +| `admin-ui-content-edit-translation-select-footer` | Footer of the **Select translation** panel on the content edit screen | `form`, `content_id`, `main_language_code` | + +The two groups behave differently: + +- `admin-ui-content-translation-modal-footer` — if any of the components renders output that is not empty, it entirely replaces the default footer buttons. +Your component template must therefore include its own action buttons. +- `admin-ui-content-edit-translation-select-footer` — component output is inserted between the existing **Edit** and **Discard** buttons. + +Register a component with the `ibexa.twig.component` tag: + +``` yaml +[[= include_code('code_samples/translations_management/config/services.yaml', 1, 1) =]] +[[= include_code('code_samples/translations_management/config/services.yaml', 27, 31) =]] +``` + +!!! note + + The `admin-ui-content-translation-modal-footer` group receives a `location` variable that may be `null` when the modal is rendered outside a location context. + Always check for `null` before you access location properties in your component template. + +## Intercept translation flow + +The `BeforeTranslateEvent` and `TranslateEvent` [events](translations_management_events.md#translation-events) operate at the field-value level and cannot redirect the HTTP flow. +To intercept the "Add translation" action at the HTTP level, for example, to trigger auto-translation and redirect to a custom view, or to bypass the default flow entirely, subscribe to `admin-ui`'s `ContentProxyTranslateEvent`. + +The `translations-management` package listens to this event at priority `100`. +Subscribe at a higher priority to act before the package does: + +``` php hl_lines="22 38 39" +[[= include_code('code_samples/translations_management/src/TranslationsManagement/ContentProxyTranslateSubscriber.php') =]] +``` + +Both highlighted calls are required: + +- `setResponse()` alone does not prevent the Translations management listener at priority 100 from running and overwriting the response. +- `stopPropagation()` stops all lower-priority listeners from executing. + +When a response is set on the event, `admin-ui` uses it and doesn't proceed with the standard translation editor. + +When the package's Subscriber fails to create the auto-translated draft, for example, when the provider is unreachable, it catches the exception, shows an error notification in the back office, and redirects the editor to the content view, but it does not surface a full error page. +If your subscriber takes over the flow by calling both `setResponse()` and `stopPropagation()`, you must implement error handling. + +!!! caution "Internal `ContentProxyTranslateEvent`" + + `ContentProxyTranslateEvent` is marked `@internal` in `ibexa/admin-ui`. + While it functions as an extension point in practice, its name and signature may change. + It may even be removed entirely without a deprecation notice. + +## Service tags reference + +The following service tags expose additional extension points that you can use to customize and extend translations management behavior. + +| Tag | Purpose | Required attributes | +|---|---|---| +| `ibexa.translations_management.auto_translate.provider.language_normalizer` | Register a language code normalizer for a provider | none | +| `ibexa.translations_management.auto_translate.provider.ai.translation_strategy` | Register a custom AI translation strategy (prompt structure) | `priority` | +| `ibexa.translations_management.auto_translate.metadata_validation.retry_policy` | Register a metadata validation retry policy | `priority` | diff --git a/docs/multisite/translations_management/translations_management.md b/docs/multisite/translations_management/translations_management.md new file mode 100644 index 00000000000..468165d8559 --- /dev/null +++ b/docs/multisite/translations_management/translations_management.md @@ -0,0 +1,16 @@ +--- +description: Translations management brings multiple features that help managers, developers and localization teams automated multilingual content delivery. +edition: lts-update +page_type: landing_page +--- + +# Translations management + +Translations management helps [[= product_name =]] developers and users deliver automated content item, product and product catalog translations. + +[[= cards([ + "multisite/translations_management/translations_management_guide", + "multisite/translations_management/configure_translations_management", + "multisite/translations_management/extend_translations_management", + "api/event_reference/translations_management_events", +], columns=3) =]] diff --git a/docs/multisite/translations_management/translations_management_guide.md b/docs/multisite/translations_management/translations_management_guide.md new file mode 100644 index 00000000000..36932a4af85 --- /dev/null +++ b/docs/multisite/translations_management/translations_management_guide.md @@ -0,0 +1,101 @@ +--- +description: Translations management helps managers, developers and localization teams with multilingual content delivery. +edition: lts-update +month_change: true +--- + +# Translations management product guide + +## What is Translations management + +Content managers, translators, and proofreaders who work with multilingual content in [[= product_name =]] often face a common set of challenges: + +- context is lost when the source text isn't visible alongside the translation +- translating long and complex content items is time-consuming +- quality assurance is slow and error-prone without a direct comparison view +- switching between tools or tabs to cross-reference languages disrupts focus and slows down publishing + +The Translations management package addresses these pain points through a side-by-side view, machine translation and the ability to invite reviewers to collaborate on the translation of a content items or products. + +The package integrates with the [AI Actions framework](ai_actions.md) to support machine translation providers such as Google Translate and DeepL, and AI powered translation services like OpenAI, Anthropic, and Google Gemini. + +Administrators can manage providers and configure default provider-to-language-pair mappings directly in [[= product_name =]]'s user interface, while editors can trigger machine translation from the content editing interface. + +!!! note + + Translations management is a standalone set of features. + Although some views are similar to those delivered by the [Automated translations](automated_translations.md) opt-in package, Translations management does not require the `ibexa/automated-translation` package to run. + These two packages use different namespaces, service tags, and provider interfaces. + +## Availability + +Translations management is an [LTS Update](editions.md#lts-updates) available in all [[= product_name =]] editions. + +## How it works + +Before the translation flow can happen, an administrator sets up the translation providers and assigns language pairs to them. +Then, when an editor opens a content item and requests a new machine translation, the plugin resolves which provider to use. +If no language-pair rule matches, it falls back to the user's manual selection. +The plugin then extracts the translatable fields from the source language version of a content item and sends them to the configured provider's API. +The system writes the translated strings into a target-language draft of the content item, and opens it in a side-by-side view for the editor to review and refine. +The editor can save the result as a draft, share it with a reviewer or publish it. + +![Translations management flow](translations_management_flow.png "Translations management flow") + +## Capabilities + +### Translation provider management + +Administrators can manage translation providers and configure translation provider/language combination assignments ([language pairs](configure_translations_management.md#define-language-pairs)). +This allows administrators to define which provider handles which language combination. +Editors see the configured provider pre-selected when creating a new translation, but can override it if needed. + +![Creating a language pair](translations_management_language_pairs.png "Creating a language pair") + +The package provides integrations with several translation providers, including REST API-based services such as Google Translate and DeepL, and AI-powered services through the [AI Actions](ai_actions.md). + +### Side-by-side translation view + +Translations management introduces a [side-by-side translation view]([[= user_doc =]]/content_management/translate_content/#side-by-side-translation-view) that displays the read-only source language content next to an editable target language form. +In this view, editors can provide and review translations in context, without having to leave the content editing interface. + +![Side-by-side translation view](managing_translations_sxs_view.png "Side-by-side translation view") + +Editors can: + +- access the side-by-side view when creating a new translation, reviewing an existing one, or editing a draft +- compare source and target content field by field while editing +- copy all content from the source column to the target column with a single action +- provide localized versions of media assets and their alternative text +- use the distraction-free mode for focused editing of individual fields, with AI actions available inline +- choose whether the source column appears on the left or right in user settings + +!!! note "Excluded content types" + + Content types that are editable in Page builder or Form builder are excluded from side-by-side editing. + + Products are editable in the side-by-side view, but product attributes are not translatable. + +### Command-line translation + +The Translations management package exposes a [console command](configure_translations_management.md#translate-content-items-with-cli) for translating content items from the command line. +You can use it for batch processing or automated workflows. + +### Translation review + +When a draft is created by going through the automatic translation process, it is marked as "For review". +Editors can [accept or reject the translation]([[= user_doc =]]/content_management/translate_content/#review-automatic-translation) directly in the side-by-side view. +Accepted drafts are marked as "Translated". + +!!! note "No review for manual translations" + + Draft translations that were created manually don't have a review status. + +### Extensibility + +Developers can [extend the translations management](extend_translations_management.md) package: + +- create custom translation providers +- add support for custom fields +- add custom content type exclusion rules +- tap into the translation lifecycle with [events](translations_management_events.md) diff --git a/docs/release_notes/ibexa_dxp_v5.0.md b/docs/release_notes/ibexa_dxp_v5.0.md index 02469e686b4..557a248af6c 100644 --- a/docs/release_notes/ibexa_dxp_v5.0.md +++ b/docs/release_notes/ibexa_dxp_v5.0.md @@ -10,6 +10,63 @@ month_change: true
+[[% set version = 'v5.0.10' %]] +[[% set date = '2026-07-30' %]] + +[[= release_note_entry_begin( + 'Translations management ' + version, + date, + ['Headless', 'Experience', 'Commerce', 'LTS Update', 'New feature', 'First release'] +) =]] + +Translations management is a new LTS Update that extends [[= product_name =]]'s built-in language management tools with machine translation, a side-by-side editing view, and a command-line translation utility. + +### Machine translation providers + +Translation providers are the services that perform the actual text translation. +Translations management uses two provider types to connect to the translation services: + +- REST API-based providers: Google Translate and DeepL, configured with API keys +- AI-based providers: OpenAI, Anthropic Claude, and Google Gemini, routed through AI Actions + +For more information, see [Configure translation providers](configure_translations_management.md#configure-translation-providers). + +### Side-by-side translation view + +A [side-by-side translation view]([[= user_doc =]]/content_management/translate_content/#side-by-side-translation-view) displays the source and target text of the content item or product on one screen. +Editors can translate or compare source and target content, copy all content from the source column to the target column in a single action, and use the distraction-free mode for focused editing of individual fields. + +For more information, see [User Documentation]([[= user_doc =]]/content_management/translate_content/#side-by-side-translation-view). + +### CLI translation command + +A new console command translates content items from the command line, enabling batch processing and automated workflows. + +For more information, see [Translate content items with CLI](configure_translations_management.md#translate-content-items-with-cli). + +### Translation review + +When a draft is created through automatic translation, it receives the "For review" status. +Editors can accept or reject the translation in the side-by-side view, which displays a review bar. +Accepted translations are given the "Translated" status. + +The **Versions** tab shows a **Translation status** column with review status badges for draft translations created with automatic translation. + +For more information, see [Translation review](configure_translations_management.md#translation-review). + +### Developer experience + +The package exposes multiple extension points for custom translation workflows, including: + +- Custom translation providers through `TranslationProviderInterface` +- Custom field type support through `FieldValueTransformerInterface` +- Custom content type exclusion rules through `SideBySideExclusionRuleInterface` +- extension points for adding UI elements and fields to the views used by the feature + +For more information, see [Extend translations management](https://doc.ibexa.co/en/5.0/translations/extend_translations_management/). + +[[= release_note_entry_end() =]] + [[% set version = 'v5.0.8' %]] [[% set date = '2026-05-21' %]] diff --git a/mkdocs.yml b/mkdocs.yml index 6ecfc86c8cf..dc16cdb66a8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -105,6 +105,7 @@ nav: - Discounts events: api/event_reference/discounts_events.md - Collaboration events: api/event_reference/collaboration_events.md - Integrated help events: api/event_reference/integrated_help_events.md + - Translations management events: api/event_reference/translations_management_events.md - Other events: api/event_reference/other_events.md - Notification channels: api/notification_channels.md - Administration: @@ -479,6 +480,11 @@ nav: - Language API: multisite/languages/language_api.md - Back office translations: multisite/languages/back_office_translations.md - Automated content translation: multisite/languages/automated_translations.md + - Translations management: + - Translations management: multisite/translations_management/translations_management.md + - Translations management guide: multisite/translations_management/translations_management_guide.md + - Configure translations management: multisite/translations_management/configure_translations_management.md + - Extend translations management: multisite/translations_management/extend_translations_management.md - Permissions: - Permissions: permissions/permissions.md - Permission overview: permissions/permission_overview.md