Skip to content
Open
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
36 changes: 36 additions & 0 deletions code_samples/translations_management/config/services.yaml
Original file line number Diff line number Diff line change
@@ -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']
Comment thread
dabrt marked this conversation as resolved.
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'
35 changes: 35 additions & 0 deletions code_samples/translations_management/install/schema.mysql.sql
Original file line number Diff line number Diff line change
@@ -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;
40 changes: 40 additions & 0 deletions code_samples/translations_management/install/schema.postgresql.sql
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace App\TranslationsManagement\EventSubscriber;

use Ibexa\Contracts\AdminUi\Event\ContentProxyTranslateEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

final readonly class ContentProxyTranslateSubscriber implements EventSubscriberInterface
{
public function __construct(
private UrlGeneratorInterface $urlGenerator,
) {
}

public static function getSubscribedEvents(): array
{
return [
ContentProxyTranslateEvent::class => ['onProxyTranslate', 200],
];
}

public function onProxyTranslate(ContentProxyTranslateEvent $event): void
{
// Read the translation context:
$event->getContentId();
$event->getFromLanguageCode(); // ?string — null when no source language exists
Comment thread
dabrt marked this conversation as resolved.
$event->getToLanguageCode();
$event->getLocationId(); // ?int — null when no location context is available
Comment thread
dabrt marked this conversation as resolved.

$url = $this->urlGenerator->generate('your_custom_route', [
'contentId' => $event->getContentId(),
]);

$event->setResponse(new RedirectResponse($url));
$event->stopPropagation();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace App\TranslationsManagement;

use Ibexa\Contracts\Core\Repository\Values\Content\Field;
use Ibexa\Contracts\TranslationsManagement\AutoTranslate\Transformer\Field\EncodedFieldValue;
use Ibexa\Contracts\TranslationsManagement\AutoTranslate\Transformer\Field\FieldValueTransformerInterface;
use Ibexa\Core\Base\Exceptions\InvalidArgumentException;
use Ibexa\Core\FieldType\Image\Value as ImageValue;
use Ibexa\Core\FieldType\Value;

final class ImageAltTextTransformer implements FieldValueTransformerInterface
{
public function getFieldTypeIdentifier(): string
{
return 'ibexa_image';
}

public function encode(Field $field): EncodedFieldValue
{
$value = $field->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<string, mixed> $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,
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace App\TranslationsManagement;

/** Placeholder for your own HTTP client or third-party SDK wrapper. */
final class MyApiClient
{
public function translate(string $text, string $sourceLanguage, string $targetLanguage): string
{
// Your implementation here.
return '';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace App\TranslationsManagement;

use Ibexa\Contracts\TranslationsManagement\AutoTranslate\Provider\AiTranslationProviderInterface;
use Ibexa\Contracts\TranslationsManagement\AutoTranslate\TranslationDataInterface;

final readonly class MyCustomAiProvider implements AiTranslationProviderInterface
{
/**
* Replace MyAiClient with your HTTP client, SDK wrapper, or any service
* that communicates with the external AI translation API.
*/
public function __construct(
private MyAiClient $apiClient,
private string $actionConfigurationIdentifier,
) {
}

public function getIdentifier(): string
{
return 'my_custom_ai_provider';
}

public function getName(): string
{
return 'My AI Translation Service';
}

public function getVendorName(): string
{
return 'My Company Ltd';
}

public function translate(TranslationDataInterface $translationData): string
{
return $this->apiClient->translate(
$translationData->getText(),
$translationData->getSourceLanguage(),
$translationData->getTargetLanguage()
);
}

/** @return array<string> */
public function getSupportedLanguageCodes(): array
{
return ['eng-GB', 'ger-DE', 'fre-FR'];
}

/** @return array<string, mixed> */
public function getConfiguration(): array
{
return [
'actionConfigurationIdentifier' => $this->actionConfigurationIdentifier,
];
}

public function isConfigured(): bool
{
return $this->actionConfigurationIdentifier !== '';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace App\TranslationsManagement;

use Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo;
use Ibexa\Contracts\TranslationsManagement\SideBySide\Service\SideBySideExclusionRuleInterface;

final class MyCustomExclusionRule implements SideBySideExclusionRuleInterface
{
public function isExcluded(ContentInfo $contentInfo): bool
{
return $contentInfo->getContentType()->identifier === 'my_excluded_type';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace App\TranslationsManagement;

use Ibexa\Contracts\TranslationsManagement\AutoTranslate\Provider\TranslationProviderInterface;
use Ibexa\Contracts\TranslationsManagement\AutoTranslate\TranslationDataInterface;

final readonly class MyCustomProvider implements TranslationProviderInterface
{
/**
* Replace MyApiClient with your HTTP client, SDK wrapper, or any service
* that communicates with the external translation API.
*/
public function __construct(
private MyApiClient $apiClient,
) {
}

public function getIdentifier(): string
{
return 'my_custom_provider';
}

public function getName(): string
{
return 'My Translation Service';
}

public function getVendorName(): string
{
return 'My Company Ltd';
}

public function translate(TranslationDataInterface $translationData): string
{
return $this->apiClient->translate(
$translationData->getText(),
$translationData->getSourceLanguage(),
$translationData->getTargetLanguage()
);
}

/** @return array<string> */
public function getSupportedLanguageCodes(): array
{
return ['eng-GB', 'ger-DE', 'fre-FR'];
}
}
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions docs/api/event_reference/event_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading