Skip to content
Snippets Groups Projects
Commit fa85870d authored by Adam G-H's avatar Adam G-H
Browse files

Issue #3368808 by phenaproxima: Override Composer Stager's TranslatableFactory...

Issue #3368808 by phenaproxima: Override Composer Stager's TranslatableFactory to return Drupal's TranslatableMarkup
parent 4d2c1697
No related branches found
No related tags found
2 merge requests!989Issue #3356804 by phenaproxima: Flag a warning during status check if the...,!933Issue #3368808: Override Composer Stager's TranslatableFactory to return Drupal's TranslatableMarkup
......@@ -10,6 +10,7 @@ declare(strict_types = 1);
use Drupal\package_manager\ComposerInspector;
use Drupal\package_manager\Exception\StageFailureMarkerException;
use PhpTuf\ComposerStager\API\Core\BeginnerInterface;
use PhpTuf\ComposerStager\API\Exception\ExceptionInterface;
use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
/**
......@@ -62,10 +63,15 @@ function package_manager_requirements(string $phase) {
];
}
catch (\Throwable $e) {
// All Composer Stager exceptions are translatable.
$message = $e instanceof ExceptionInterface
? $e->getTranslatableMessage()
: $e->getMessage();
$requirements['package_manager_composer'] = [
'title' => $title,
'description' => t('Composer was not found. The error message was: @message', [
'@message' => $e->getMessage(),
'@message' => $message,
]),
'severity' => REQUIREMENT_ERROR,
];
......
......@@ -16,12 +16,16 @@ services:
public: false
Drupal\package_manager\FileSyncerFactory:
public: false
Drupal\package_manager\TranslatableStringFactory:
public: false
PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface:
alias: 'Drupal\package_manager\ExecutableFinder'
PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface:
alias: 'Drupal\package_manager\ProcessFactory'
PhpTuf\ComposerStager\API\FileSyncer\Service\FileSyncerInterface:
factory: ['@Drupal\package_manager\FileSyncerFactory', 'create']
PhpTuf\ComposerStager\API\Translation\Factory\TranslatableFactoryInterface:
alias: 'Drupal\package_manager\TranslatableStringFactory'
logger.channel.package_manager:
parent: logger.channel_base
arguments:
......
......@@ -111,6 +111,7 @@ final class PackageManagerServiceProvider extends ServiceProviderBase {
'theme_handler' => 'Drupal\Core\Extension\ThemeHandlerInterface',
'cron' => 'Drupal\Core\CronInterface',
'logger.factory' => 'Drupal\Core\Logger\LoggerChannelFactoryInterface',
'string_translation' => 'Drupal\Core\StringTranslation\TranslationInterface',
];
foreach ($aliases as $service_id => $alias) {
if (!$container->hasAlias($alias)) {
......
<?php
declare(strict_types = 1);
namespace Drupal\package_manager;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use PhpTuf\ComposerStager\API\Translation\Service\TranslatorInterface;
use PhpTuf\ComposerStager\API\Translation\Value\TranslatableInterface;
use PhpTuf\ComposerStager\API\Translation\Value\TranslationParametersInterface;
/**
* An adapter for interoperable string translation.
*
* This class is designed to adapt Drupal's style of string translation so it
* can be used with the Symfony-inspired architecture used by Composer Stager.
*
* If this object is cast to a string, it will be translated by Drupal's
* translation system. It will ONLY be translated by Composer Stager if the
* trans() method is explicitly called.
*
* @internal
* This is an internal part of Package Manager and may be changed or removed
* at any time without warning. External code should not interact with this
* class.
*/
final class TranslatableStringAdapter extends TranslatableMarkup implements TranslatableInterface, TranslationParametersInterface {
/**
* {@inheritdoc}
*/
public function getAll(): array {
return $this->getArguments();
}
/**
* {@inheritdoc}
*/
public function trans(TranslatorInterface $translator, ?string $locale = NULL): string {
// This method is NEVER used by Drupal to translate the underlying string;
// it exists solely for Composer Stager's translation system to
// transparently translate Drupal strings using its own architecture.
return $translator->trans(
$this->getUntranslatedString(),
$this,
// The 'context' option is the closest analogue to the Symfony-inspired
// concept of translation domains.
$this->getOption('context'),
$locale ?? $this->getOption('langcode'),
);
}
}
<?php
declare(strict_types = 1);
namespace Drupal\package_manager;
use Drupal\Core\StringTranslation\TranslationInterface;
use PhpTuf\ComposerStager\API\Translation\Factory\TranslatableFactoryInterface;
use PhpTuf\ComposerStager\API\Translation\Service\DomainOptionsInterface;
use PhpTuf\ComposerStager\API\Translation\Value\TranslatableInterface;
use PhpTuf\ComposerStager\API\Translation\Value\TranslationParametersInterface;
use PhpTuf\ComposerStager\Internal\Translation\Factory\TranslatableFactory as StagerTranslatableFactory;
/**
* Creates translatable strings that can interoperate with Composer Stager.
*
* @internal
* This is an internal part of Package Manager and may be changed or removed
* at any time without warning. External code should not interact with this
* class.
*/
final class TranslatableStringFactory implements TranslatableFactoryInterface {
/**
* Constructs a TranslatableStringFactory object.
*
* @param \PhpTuf\ComposerStager\Internal\Translation\Factory\TranslatableFactory $decorated
* The decorated translatable factory service.
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation
* The string translation service.
*/
public function __construct(
private readonly StagerTranslatableFactory $decorated,
private readonly TranslationInterface $translation,
) {}
/**
* {@inheritdoc}
*/
public function createDomainOptions(): DomainOptionsInterface {
return $this->decorated->createDomainOptions();
}
/**
* {@inheritdoc}
*/
public function createTranslatableMessage(string $message, ?TranslationParametersInterface $parameters = NULL, ?string $domain = NULL,): TranslatableInterface {
return new TranslatableStringAdapter(
$message,
$parameters?->getAll() ?? [],
// TranslatableMarkup's 'context' option is the closest analogue to the
// $domain parameter.
['context' => $domain ?? ''],
$this->translation,
);
}
/**
* {@inheritdoc}
*/
public function createTranslationParameters(array $parameters = []): TranslationParametersInterface {
return $this->decorated->createTranslationParameters($parameters);
}
}
......@@ -7,6 +7,7 @@ namespace Drupal\package_manager;
use Drupal\Component\Assertion\Inspector;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\system\SystemManager;
use PhpTuf\ComposerStager\API\Exception\ExceptionInterface;
/**
* A value object to contain the results of a validation.
......@@ -72,7 +73,10 @@ final class ValidationResult {
* @return static
*/
public static function createErrorFromThrowable(\Throwable $throwable, ?TranslatableMarkup $summary = NULL): static {
return new static(SystemManager::REQUIREMENT_ERROR, [$throwable->getMessage()], $summary, FALSE);
// All Composer Stager exceptions are translatable.
$is_translatable = $throwable instanceof ExceptionInterface;
$message = $is_translatable ? $throwable->getTranslatableMessage() : $throwable->getMessage();
return new static(SystemManager::REQUIREMENT_ERROR, [$message], $summary, $is_translatable);
}
/**
......
......@@ -318,7 +318,10 @@ class ComposerInspectorTest extends PackageManagerKernelTestBase {
}
catch (ComposerNotReadyException $e) {
$this->assertSame($project_root, $e->workingDir);
$this->assertStringContainsString('composer.json" does not match the expected JSON schema', $e->getMessage());
// The exception message is translated by Composer Stager and HTML-escaped
// by Drupal's markup system, which is why there's a &quot; in the
// final exception message.
$this->assertStringContainsString('composer.json&quot; does not match the expected JSON schema', $e->getMessage());
$this->assertStringContainsString('prefer-stable : String value found, but a boolean is required', $e->getPrevious()?->getMessage());
}
}
......
......@@ -7,9 +7,11 @@ namespace Drupal\Tests\package_manager\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\package_manager\ExecutableFinder;
use Drupal\package_manager\ProcessFactory;
use Drupal\package_manager\TranslatableStringFactory;
use Drupal\Tests\package_manager\Traits\AssertPreconditionsTrait;
use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
use PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface;
use PhpTuf\ComposerStager\API\Translation\Factory\TranslatableFactoryInterface;
/**
* Tests that Package Manager services are wired correctly.
......@@ -44,6 +46,7 @@ class ServicesTest extends KernelTestBase {
$overrides = [
ExecutableFinderInterface::class => ExecutableFinder::class,
ProcessFactoryInterface::class => ProcessFactory::class,
TranslatableFactoryInterface::class => TranslatableStringFactory::class,
];
foreach ($overrides as $interface => $expected_class) {
$this->assertInstanceOf($expected_class, $this->container->get($interface));
......
......@@ -47,8 +47,10 @@ class SymlinkValidatorTest extends PackageManagerKernelTestBase {
link($project_root . '/composer.json', $project_root . '/composer.link');
$result = ValidationResult::createError([
t('The active directory at @dir contains hard links, which is not supported. The first one is @dir/composer.json.', [
'@dir' => $project_root,
t('The %which directory at %dir contains hard links, which is not supported. The first one is %file.', [
'%which' => 'active',
'%dir' => $project_root,
'%file' => $project_root . '/composer.json',
]),
]);
$this->assertStatusCheckResults([$result]);
......@@ -63,8 +65,10 @@ class SymlinkValidatorTest extends PackageManagerKernelTestBase {
symlink($project_root . '/composer.json', $project_root . '/composer.link');
$result = ValidationResult::createError([
t('The active directory at @dir contains absolute links, which is not supported. The first one is @dir/composer.link.', [
'@dir' => $project_root,
t('The %which directory at %dir contains absolute links, which is not supported. The first one is %file.', [
'%which' => 'active',
'%dir' => $project_root,
'%file' => $project_root . '/composer.link',
]),
]);
$this->assertStatusCheckResults([$result]);
......@@ -84,8 +88,10 @@ class SymlinkValidatorTest extends PackageManagerKernelTestBase {
chdir($project_root);
symlink('../hello.txt', 'fail.txt');
$result = ValidationResult::createError([
t('The active directory at @dir contains links that point outside the codebase, which is not supported. The first one is @dir/fail.txt.', [
'@dir' => $project_root,
t('The %which directory at %dir contains links that point outside the codebase, which is not supported. The first one is %file.', [
'%which' => 'active',
'%dir' => $project_root,
'%file' => $project_root . '/fail.txt',
]),
]);
$this->assertStatusCheckResults([$result]);
......@@ -111,8 +117,10 @@ class SymlinkValidatorTest extends PackageManagerKernelTestBase {
symlink('../hello.txt', 'fail.txt');
$result = ValidationResult::createError([
t('The staging directory at @dir contains links that point outside the codebase, which is not supported. The first one is @dir/fail.txt.', [
'@dir' => $stage_dir,
t('The %which directory at %dir contains links that point outside the codebase, which is not supported. The first one is %file.', [
'%which' => 'staging',
'%dir' => $stage_dir,
'%file' => $stage_dir . '/fail.txt',
]),
]);
try {
......@@ -136,7 +144,9 @@ class SymlinkValidatorTest extends PackageManagerKernelTestBase {
'php',
[
ValidationResult::createError([
t('The active directory at <PROJECT_ROOT> contains symlinks that point to a directory, which is not supported. The first one is <PROJECT_ROOT>/modules/custom/example_module.'),
t('The %which directory at <em class="placeholder"><PROJECT_ROOT></em> contains symlinks that point to a directory, which is not supported. The first one is <em class="placeholder"><PROJECT_ROOT>/modules/custom/example_module</em>.', [
'%which' => 'active',
]),
]),
],
],
......@@ -197,8 +207,10 @@ class SymlinkValidatorTest extends PackageManagerKernelTestBase {
symlink('composer.json', 'composer.link');
$result = ValidationResult::createError([
t('The active directory at @dir contains links, which is not supported on Windows. The first one is @dir/composer.link.', [
'@dir' => $project_root,
t('The %which directory at %dir contains links, which is not supported on Windows. The first one is %file.', [
'%which' => 'active',
'%dir' => $project_root,
'%file' => $project_root . '/composer.link',
]),
]);
$this->assertStatusCheckResults([$result]);
......
<?php
namespace Drupal\Tests\package_manager\Kernel;
use Drupal\package_manager\TranslatableStringAdapter;
use Drupal\package_manager\TranslatableStringFactory;
use PhpTuf\ComposerStager\API\Translation\Factory\TranslatableFactoryInterface;
/**
* @covers \Drupal\package_manager\TranslatableStringFactory
* @covers \Drupal\package_manager\TranslatableStringAdapter
*
* @group package_manager
*/
class TranslatableStringTest extends PackageManagerKernelTestBase {
/**
* Tests various ways of creating a translatable string.
*/
public function testCreateTranslatableString(): void {
// Ensure that we have properly overridden Composer Stager's factory.
$factory = $this->container->get(TranslatableFactoryInterface::class);
$this->assertInstanceOf(TranslatableStringFactory::class, $factory);
/** @var \Drupal\package_manager\TranslatableStringAdapter $string */
$string = $factory->createTranslatableMessage('This string has no parameters.');
$this->assertInstanceOf(TranslatableStringAdapter::class, $string);
$this->assertEmpty($string->getArguments());
$this->assertEmpty($string->getOption('context'));
$this->assertSame('This string has no parameters.', (string) $string);
$parameters = $factory->createTranslationParameters([
'%name' => 'Slim Shady',
]);
$string = $factory->createTranslatableMessage('My name is %name.', $parameters, 'outer space');
$this->assertSame($parameters->getAll(), $string->getArguments());
$this->assertSame('outer space', $string->getOption('context'));
$this->assertSame('My name is <em class="placeholder">Slim Shady</em>.', (string) $string);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment