diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..c62e0b22 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +/.gitattributes export-ignore +/.github export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/CHANGELOG.md export-ignore +/CONTRIBUTING.md export-ignore +/README.md export-ignore +/Tests export-ignore +/phpunit.xml.dist export-ignore diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..1ab42fed --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,61 @@ +# yamllint disable rule:line-length +# yamllint disable rule:braces + +name: CI + +on: + pull_request: + push: + branches: + - "master" + +jobs: + phpunit: + name: "PHPUnit" + runs-on: "ubuntu-20.04" + + strategy: + fail-fast: false + matrix: + symfony-version: + - '^4.4' + - '^5.4' + - '^6.4' + php-version: + - "7.4" + - "8.0" + - "8.1" + dependencies: + - "lowest" + - "highest" + exclude: + - symfony-version: '^6.4' + php-version: '7.4' + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + with: + fetch-depth: 2 + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "${{ matrix.php-version }}" + coverage: "pcov" + ini-values: "zend.assertions=1" + + - name: Configure symfony version + uses: php-actions/composer@v6 + with: + command: config + args: extra.symfony.require ${{ matrix.symfony-version }} + + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@v1" + with: + dependency-versions: "${{ matrix.dependencies }}" + composer-options: "${{ matrix.composer-options }}" + + - name: Run tests + run: | + SYMFONY_DEPRECATIONS_HELPER=weak vendor/bin/simple-phpunit ${PHPUNIT_FLAGS} diff --git a/.github/workflows/coding-standards.yaml b/.github/workflows/coding-standards.yaml new file mode 100644 index 00000000..40c98e65 --- /dev/null +++ b/.github/workflows/coding-standards.yaml @@ -0,0 +1,35 @@ +name: "Coding Standards" + +on: + pull_request: + push: + branches: + - "master" + +jobs: + coding-standards: + name: "Coding Standards" + runs-on: "ubuntu-20.04" + + strategy: + matrix: + php-version: + - "7.4" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "${{ matrix.php-version }}" + tools: "cs2pr" + extensions: pdo_sqlite + + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@v1" + + - name: "Run PHP_CodeSniffer" + run: "vendor/bin/phpcs" diff --git a/.gitignore b/.gitignore index 1bc9c776..3ca416f2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,41 @@ phpunit.xml composer.lock vendor/ .phpunit.result.cache +/.idea .phpcs-cache phpcs.xml + + +/bin/ +/config/ +/public/ +/src/ +/templates +/tests +/translations +/var/ +/.env* +/.phpunit.result.cache + +.idea/ +/symfony.lock + +###> symfony/framework-bundle ### +/.env.local +/.env.local.php +/.env.*.local +/config/secrets/prod/prod.decrypt.private.php +/public/bundles/ +/var/ +/vendor/ +###< symfony/framework-bundle ### + +###> squizlabs/php_codesniffer ### +/.phpcs-cache +/phpcs.xml +###< squizlabs/php_codesniffer ### + +###> symfony/phpunit-bridge ### +.phpunit.result.cache +/phpunit.xml +###< symfony/phpunit-bridge ### diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8d2aa294..00000000 --- a/.travis.yml +++ /dev/null @@ -1,57 +0,0 @@ -language: php - -sudo: false - -cache: - directories: - - $HOME/.composer/cache - -php: - - 7.2 - - 7.3 - - 7.4 - - 8.0 - -env: - global: - - TARGET=test - -matrix: - fast_finish: true - - include: - - php: 7.2 - env: COMPOSER_FLAGS="--prefer-lowest" - - php: 7.2 - env: TARGET=cs - - php: 7.2 - env: SYMFONY_VERSION=^3.4 - - php: 7.3 - env: SYMFONY_VERSION=^3.4 - - php: 7.4 - env: SYMFONY_VERSION=^3.4 - - php: 7.2 - env: SYMFONY_VERSION=^4.4 - - php: 7.3 - env: SYMFONY_VERSION=^4.4 - - php: 7.4 - env: SYMFONY_VERSION=^4.4 - - php: 8.0 - env: SYMFONY_VERSION=^4.4 - - php: 7.2 - env: SYMFONY_VERSION=^5.2 - - php: 7.3 - env: SYMFONY_VERSION=^5.2 - - php: 7.4 - env: SYMFONY_VERSION=^5.2 - - php: 8.0 - env: SYMFONY_VERSION=^5.2 - -install: - - if [ -x .travis/install_${TARGET}.sh ]; then .travis/install_${TARGET}.sh; fi; - -script: - - if [ -x .travis/script_${TARGET}.sh ]; then .travis/script_${TARGET}.sh; fi; - -after_success: - - if [ -x .travis/success_${TARGET}.sh ]; then .travis/success_${TARGET}.sh; fi; diff --git a/.travis/install_cs.sh b/.travis/install_cs.sh deleted file mode 100755 index 4dd1c034..00000000 --- a/.travis/install_cs.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -composer self-update --1 - -phpenv config-rm xdebug.ini - -composer update -n diff --git a/.travis/install_test.sh b/.travis/install_test.sh deleted file mode 100755 index c9df7224..00000000 --- a/.travis/install_test.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -composer self-update --1 - -if [[ $SYMFONY_VERSION ]]; then composer require symfony/symfony:${SYMFONY_VERSION} --no-update; fi - -COMPOSER_MEMORY_LIMIT=-1 composer update ${COMPOSER_FLAGS} --no-interaction diff --git a/.travis/script_cs.sh b/.travis/script_cs.sh deleted file mode 100755 index f1d9c4fa..00000000 --- a/.travis/script_cs.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -vendor/bin/phpcs diff --git a/.travis/script_test.sh b/.travis/script_test.sh deleted file mode 100755 index 1d759f3e..00000000 --- a/.travis/script_test.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -vendor/bin/phpunit $PHPUNIT_FLAGS -phpenv config-rm xdebug.ini || true diff --git a/.travis/success_test.sh b/.travis/success_test.sh deleted file mode 100755 index 043daada..00000000 --- a/.travis/success_test.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -if [[ "$COVERAGE" = true ]]; then wget https://scrutinizer-ci.com/ocular.phar; fi -if [[ "$COVERAGE" = true ]]; then php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml; fi diff --git a/Command/ExtractTranslationCommand.php b/Command/ExtractTranslationCommand.php index c67fad75..ae58d570 100644 --- a/Command/ExtractTranslationCommand.php +++ b/Command/ExtractTranslationCommand.php @@ -61,13 +61,10 @@ public function __construct(ConfigFactory $configFactory, Updater $updater, arra parent::__construct(); } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this - ->setName('translation:extract') + ->setName('jms:translation:extract') ->setDescription('Extracts translation messages from your code.') ->addArgument('locales', InputArgument::IS_ARRAY, 'The locales for which to extract messages.') ->addOption('enable-extractor', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'The alias of an extractor which should be enabled.') @@ -180,7 +177,7 @@ private function updateWithInput(InputInterface $input, ConfigBuilder $builder) $builder->setOutputFormat($outputFormat); } - if ($input->hasParameterOption('intl-icu')) { + if ($input->hasParameterOption('--intl-icu')) { $builder->setUseIcuMessageFormat(true); } diff --git a/Command/ResourcesListCommand.php b/Command/ResourcesListCommand.php index 0feae44f..b18482d2 100644 --- a/Command/ResourcesListCommand.php +++ b/Command/ResourcesListCommand.php @@ -56,13 +56,10 @@ public function __construct(string $projectDir, array $bundles, ?string $rootDir parent::__construct(); } - /** - * {@inheritdoc} - */ - protected function configure() + protected function configure(): void { $this - ->setName('translation:list-resources') + ->setName('jms:translation:list-resources') ->setDescription('List translation resources available.') ->addOption('files', null, InputOption::VALUE_OPTIONAL, 'Display only files'); } diff --git a/Controller/ApiController.php b/Controller/ApiController.php index 2765c768..09af04e8 100644 --- a/Controller/ApiController.php +++ b/Controller/ApiController.php @@ -33,6 +33,7 @@ * * @Route("/api") */ +#[Route('/api')] class ApiController { /** @@ -65,6 +66,7 @@ public function __construct(ConfigFactory $configFactory, Updater $updater) * defaults = {"id" = null}, * options = {"i18n" = false}) */ + #[Route('/configs/{config}/domains/{domain}/locales/{locale}/messages', name: 'jms_translation_update_message', methods: [Request::METHOD_PUT], defaults: ['id' => null], options: ['i18n' => false])] public function updateMessageAction(Request $request, $config, $domain, $locale) { $id = $request->query->get('id'); diff --git a/Controller/TranslateController.php b/Controller/TranslateController.php index 8ce17d85..a28001d2 100644 --- a/Controller/TranslateController.php +++ b/Controller/TranslateController.php @@ -26,7 +26,9 @@ use JMS\TranslationBundle\Util\FileUtils; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; +use Twig\Environment; /** * Translate Controller. @@ -45,15 +47,21 @@ class TranslateController */ private $loader; + /** + * @var Environment|null + */ + private $twig; + /** * @var string */ private $sourceLanguage; - public function __construct(ConfigFactory $configFactory, LoaderManager $loader) + public function __construct(ConfigFactory $configFactory, LoaderManager $loader, ?Environment $twig = null) { $this->configFactory = $configFactory; $this->loader = $loader; + $this->twig = $twig; } /** @@ -67,11 +75,12 @@ public function setSourceLanguage($lang) /** * @param Request $request * - * @return array + * @return Response|array * * @Route("/", name="jms_translation_index", options = {"i18n" = false}) * @Template("@JMSTranslation/Translate/index.html.twig") */ + #[Route('/', name: 'jms_translation_index', options: ['i18n' => false])] public function indexAction(Request $request) { $configs = $this->configFactory->getNames(); @@ -136,7 +145,7 @@ public function indexAction(Request $request) $existingMessages[$id] = $message; } - return [ + $variables = [ 'selectedConfig' => $config, 'configs' => $configs, 'selectedDomain' => $domain, @@ -151,5 +160,11 @@ public function indexAction(Request $request) 'file' => (string) $files[$domain][$locale][1], 'sourceLanguage' => $this->sourceLanguage, ]; + + if (null !== $this->twig) { + return new Response($this->twig->render('@JMSTranslation/Translate/index.html.twig', $variables)); + } + + return $variables; } } diff --git a/DependencyInjection/Compiler/IntegrationPass.php b/DependencyInjection/Compiler/IntegrationPass.php index b25c9b32..99e5466a 100644 --- a/DependencyInjection/Compiler/IntegrationPass.php +++ b/DependencyInjection/Compiler/IntegrationPass.php @@ -25,7 +25,7 @@ class IntegrationPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('translation.loader.xliff')) { return; diff --git a/DependencyInjection/Compiler/MountDumpersPass.php b/DependencyInjection/Compiler/MountDumpersPass.php index a53061b2..2ba046ee 100644 --- a/DependencyInjection/Compiler/MountDumpersPass.php +++ b/DependencyInjection/Compiler/MountDumpersPass.php @@ -28,7 +28,7 @@ class MountDumpersPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('jms_translation.file_writer')) { return; diff --git a/DependencyInjection/Compiler/MountExtractorsPass.php b/DependencyInjection/Compiler/MountExtractorsPass.php index 7b40c7e5..12deff22 100644 --- a/DependencyInjection/Compiler/MountExtractorsPass.php +++ b/DependencyInjection/Compiler/MountExtractorsPass.php @@ -27,7 +27,7 @@ class MountExtractorsPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('jms_translation.extractor_manager')) { return; diff --git a/DependencyInjection/Compiler/MountFileVisitorsPass.php b/DependencyInjection/Compiler/MountFileVisitorsPass.php index d303e7ca..805420b5 100644 --- a/DependencyInjection/Compiler/MountFileVisitorsPass.php +++ b/DependencyInjection/Compiler/MountFileVisitorsPass.php @@ -26,7 +26,7 @@ class MountFileVisitorsPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('jms_translation.extractor.file_extractor')) { return; diff --git a/DependencyInjection/Compiler/MountLoadersPass.php b/DependencyInjection/Compiler/MountLoadersPass.php index f69ad68e..bd8365fe 100644 --- a/DependencyInjection/Compiler/MountLoadersPass.php +++ b/DependencyInjection/Compiler/MountLoadersPass.php @@ -28,7 +28,7 @@ class MountLoadersPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('jms_translation.loader_manager')) { return; diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 14f5a442..63e7c6b4 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -33,7 +33,7 @@ public function __construct(ContainerBuilder $container) $this->container = $container; } - public function getConfigTreeBuilder() + public function getConfigTreeBuilder(): TreeBuilder { $c = $this->container; diff --git a/DependencyInjection/JMSTranslationExtension.php b/DependencyInjection/JMSTranslationExtension.php index f28b399c..faf4b713 100644 --- a/DependencyInjection/JMSTranslationExtension.php +++ b/DependencyInjection/JMSTranslationExtension.php @@ -29,7 +29,7 @@ class JMSTranslationExtension extends Extension { - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { $config = $this->processConfiguration(new Configuration($container), $configs); @@ -41,7 +41,7 @@ public function load(array $configs, ContainerBuilder $container) $container->setParameter('jms_translation.locales', $config['locales']); foreach ($config['dumper'] as $option => $value) { - $container->setParameter("jms_translation.dumper.${option}", $value); + $container->setParameter('jms_translation.dumper.' . $option, $value); } $requests = []; diff --git a/JMSTranslationBundle.php b/JMSTranslationBundle.php index 4349eec6..57094e95 100644 --- a/JMSTranslationBundle.php +++ b/JMSTranslationBundle.php @@ -30,7 +30,7 @@ class JMSTranslationBundle extends Bundle { const VERSION = '1.1.0-DEV'; - public function build(ContainerBuilder $container) + public function build(ContainerBuilder $container): void { $container->addCompilerPass(new IntegrationPass()); $container->addCompilerPass(new MountFileVisitorsPass()); diff --git a/Resources/.gitattributes b/Resources/.gitattributes new file mode 100644 index 00000000..70038ee5 --- /dev/null +++ b/Resources/.gitattributes @@ -0,0 +1,2 @@ +/.gitattributes export-ignore +/docs export-ignore diff --git a/Resources/config/console.xml b/Resources/config/console.xml index 9083bbac..bcbcab01 100644 --- a/Resources/config/console.xml +++ b/Resources/config/console.xml @@ -6,14 +6,14 @@ - + %jms_translation.locales% - + %kernel.project_dir% %kernel.bundles% container.hasParameter('kernel.root_dir') ? parameter('kernel.root_dir') : null diff --git a/Resources/config/services.xml b/Resources/config/services.xml index c7de8031..e57de45b 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -41,6 +41,7 @@ + %jms_translation.source_language% diff --git a/Tests/Functional/AppKernel.php b/Tests/Functional/AppKernel.php index a201e0ed..23d51052 100644 --- a/Tests/Functional/AppKernel.php +++ b/Tests/Functional/AppKernel.php @@ -34,23 +34,31 @@ class AppKernel extends Kernel { private $config; - public function __construct($config) + private $fwConfig; + + public function __construct(string $fwConfig, ?string $config) { parent::__construct('test', true); $fs = new Filesystem(); - if (! $fs->isAbsolutePath($config)) { - $config = __DIR__ . '/config/' . $config; + if ($config) { + if (!$fs->isAbsolutePath($config)) { + $config = __DIR__ . '/config/' . $config; + } + + if (!file_exists($config)) { + throw new RuntimeException(sprintf('The config file "%s" does not exist.', $config)); + } } + $this->config = $config; - if (! file_exists($config)) { - throw new RuntimeException(sprintf('The config file "%s" does not exist.', $config)); + if (!$fs->isAbsolutePath($fwConfig)) { + $fwConfig = __DIR__ . '/config/' . $fwConfig; } - - $this->config = $config; + $this->fwConfig = $fwConfig; } - public function registerBundles() + public function registerBundles(): iterable { return [ new TestBundle(), @@ -63,7 +71,10 @@ public function registerBundles() public function registerContainerConfiguration(LoaderInterface $loader) { - $loader->load($this->config); + $loader->load($this->fwConfig); + if ($this->config) { + $loader->load($this->config); + } } public function getCacheDir(): string @@ -76,7 +87,7 @@ public function getLogDir(): string return $this->getBaseDir() . '/logs'; } - public function getProjectDir() + public function getProjectDir(): string { return __DIR__; } diff --git a/Tests/Functional/BaseTestCase.php b/Tests/Functional/BaseTestCase.php index 43ad51a1..84b13800 100644 --- a/Tests/Functional/BaseTestCase.php +++ b/Tests/Functional/BaseTestCase.php @@ -22,17 +22,24 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\KernelInterface; class BaseTestCase extends WebTestCase { - protected static function createKernel(array $options = []) + protected static function createKernel(array $options = []): KernelInterface { $isSf5 = version_compare(Kernel::VERSION, '5.0.0') >= 0; $default = $isSf5 ? 'default_sf5.yml' : 'default.yml'; - return new AppKernel( - $options['config'] ?? $default - ); + if (version_compare(Kernel::VERSION, '6.0.0') >= 0) { + $conf = 'framework_sf6.yml'; + } elseif (version_compare(Kernel::VERSION, '5.0.0') >= 0) { + $conf = 'framework.yml'; + } else { + $conf = 'framework.yml'; + } + + return new AppKernel($conf, $options['config'] ?? $default); } } diff --git a/Tests/Functional/Command/ExtractCommandTest.php b/Tests/Functional/Command/ExtractCommandTest.php index ed9f667d..46f54687 100644 --- a/Tests/Functional/Command/ExtractCommandTest.php +++ b/Tests/Functional/Command/ExtractCommandTest.php @@ -29,7 +29,7 @@ public function testExtract() { $input = new ArgvInput([ 'app/console', - 'translation:extract', + 'jms:translation:extract', 'en', '--dir=' . $inputDir = __DIR__ . '/../../Translation/Extractor/Fixture/SimpleTest', '--output-dir=' . ($outputDir = sys_get_temp_dir() . '/' . uniqid('extract')), @@ -62,7 +62,7 @@ public function testExtractDryRun() { $input = new ArgvInput([ 'app/console', - 'translation:extract', + 'jms:translation:extract', 'en', '--dir=' . $inputDir = __DIR__ . '/../../Translation/Extractor/Fixture/SimpleTest', '--output-dir=' . ($outputDir = sys_get_temp_dir() . '/' . uniqid('extract')), diff --git a/Tests/Functional/Command/ResourcesListCommandTest.php b/Tests/Functional/Command/ResourcesListCommandTest.php index 7d697016..64104e15 100644 --- a/Tests/Functional/Command/ResourcesListCommandTest.php +++ b/Tests/Functional/Command/ResourcesListCommandTest.php @@ -28,7 +28,7 @@ public function testList(): void { $input = new ArgvInput([ 'app/console', - 'translation:list-resources', + 'jms:translation:list-resources', ]); $expectedOutput = @@ -44,7 +44,7 @@ public function testListFiles(): void { $input = new ArgvInput([ 'app/console', - 'translation:list-resources', + 'jms:translation:list-resources', '--files', ]); diff --git a/Tests/Functional/Controller/ApiControllerTest.php b/Tests/Functional/Controller/ApiControllerTest.php index 14a13c08..d63c0a37 100644 --- a/Tests/Functional/Controller/ApiControllerTest.php +++ b/Tests/Functional/Controller/ApiControllerTest.php @@ -27,10 +27,13 @@ public function testUpdateAction() $this->assertTrue($written !== false && $written > 0); $client->request('POST', '/_trans/api/configs/app/domains/navigation/locales/en/messages?id=main.home', ['_method' => 'PUT', 'message' => 'Away']); + + $fileContent = is_file($file) ? file_get_contents($file) : ''; + unlink($file); $this->assertEquals(200, $client->getResponse()->getStatusCode()); // Verify that the file has new content - $array = Yaml::parse(file_get_contents($file)); + $array = Yaml::parse($fileContent); if ($isSf4) { $this->assertTrue(isset($array['main.home']), print_r($array, true)); @@ -40,8 +43,5 @@ public function testUpdateAction() $this->assertTrue(isset($array['main']['home'])); $this->assertEquals('Away', $array['main']['home']); } - - // Remove the file - unlink($file); } } diff --git a/Tests/Functional/config/default_sf5.yml b/Tests/Functional/config/default_sf5.yml index f6f04d58..b0fbfefc 100644 --- a/Tests/Functional/config/default_sf5.yml +++ b/Tests/Functional/config/default_sf5.yml @@ -1,5 +1,4 @@ imports: - - { resource: framework.yml } - { resource: twig.yml } - { resource: bundle.yml } diff --git a/Tests/Functional/config/framework.yml b/Tests/Functional/config/framework.yml index 53f86e6e..4d136549 100644 --- a/Tests/Functional/config/framework.yml +++ b/Tests/Functional/config/framework.yml @@ -6,9 +6,10 @@ framework: storage_id: session.storage.mock_file form: true csrf_protection: true + annotations: true + property_access: true validation: enabled: true - enable_annotations: true translator: enabled: true router: diff --git a/Tests/Functional/config/framework_sf6.yml b/Tests/Functional/config/framework_sf6.yml new file mode 100644 index 00000000..59349537 --- /dev/null +++ b/Tests/Functional/config/framework_sf6.yml @@ -0,0 +1,16 @@ +framework: + secret: test + test: ~ + assets: ~ + session: + storage_factory_id: session.storage.factory.mock_file + form: true + csrf_protection: true + annotations: true + property_access: true + validation: + enabled: true + translator: + enabled: true + router: + resource: "%kernel.project_dir%/config/routing.yml" diff --git a/Tests/Functional/config/routing.yml b/Tests/Functional/config/routing.yml index 5c249b41..7e829169 100644 --- a/Tests/Functional/config/routing.yml +++ b/Tests/Functional/config/routing.yml @@ -5,4 +5,4 @@ JMSTranslationBundle_ui: TestBundle: type: annotation - resource: "@TestBundle/Controller/" \ No newline at end of file + resource: "@TestBundle/Controller/" diff --git a/Tests/Functional/config/test_updating_translations.yml b/Tests/Functional/config/test_updating_translations.yml index 1ee24b29..6fb7d25b 100644 --- a/Tests/Functional/config/test_updating_translations.yml +++ b/Tests/Functional/config/test_updating_translations.yml @@ -1,6 +1,3 @@ -imports: - - { resource: default_sf5.yml } - parameters: translation_output_dir: "%kernel.cache_dir%/translations" diff --git a/Tests/Translation/Extractor/File/BasePhpFileExtractorTest.php b/Tests/Translation/Extractor/File/BasePhpFileExtractorTest.php index 56fe06a2..7792a460 100644 --- a/Tests/Translation/Extractor/File/BasePhpFileExtractorTest.php +++ b/Tests/Translation/Extractor/File/BasePhpFileExtractorTest.php @@ -38,7 +38,7 @@ final protected function extract($file, ?FileVisitorInterface $extractor = null) { $fileRealPath = __DIR__ . '/Fixture/' . $file; if (! is_file($fileRealPath)) { - throw new RuntimeException(sprintf('The file "%s" does not exist.', $fileRealPath)); + throw new \RuntimeException(sprintf('The file "%s" does not exist.', $fileRealPath)); } if ($extractor === null) { @@ -48,7 +48,7 @@ final protected function extract($file, ?FileVisitorInterface $extractor = null) $lexer = new Lexer(); if (class_exists(ParserFactory::class)) { $factory = new ParserFactory(); - $parser = $factory->create(ParserFactory::PREFER_PHP7, $lexer); + $parser = \method_exists($factory, 'create') ? $factory->create(ParserFactory::PREFER_PHP7, $lexer) : $factory->createForNewestSupportedVersion(); } else { $parser = new Parser($lexer); } diff --git a/Tests/Translation/Extractor/File/Fixture/MyAuthException.php b/Tests/Translation/Extractor/File/Fixture/MyAuthException.php index 5caaff3c..273d45b4 100644 --- a/Tests/Translation/Extractor/File/Fixture/MyAuthException.php +++ b/Tests/Translation/Extractor/File/Fixture/MyAuthException.php @@ -26,7 +26,7 @@ class MyAuthException extends AuthenticationException { private $foo; - public function getMessageKey() + public function getMessageKey(): string { if (! empty($this->foo)) { /** @Desc("%foo% is invalid.") */ diff --git a/Tests/Translation/Extractor/File/Fixture/MyEntity.php b/Tests/Translation/Extractor/File/Fixture/MyEntity.php index 103a0950..2585adba 100644 --- a/Tests/Translation/Extractor/File/Fixture/MyEntity.php +++ b/Tests/Translation/Extractor/File/Fixture/MyEntity.php @@ -1,12 +1,11 @@ 'form.label.choice.bar', ]; - /** @Assert\NotBlank(message = "form.error.name_required") */ + /** + * @Assert\NotBlank(message = "form.error.name_required") + */ public $name; public static function getTranslationMessages() diff --git a/Tests/Translation/Extractor/File/Fixture/MyFormType.php b/Tests/Translation/Extractor/File/Fixture/MyFormType.php index f4e521b0..6eadd6c4 100644 --- a/Tests/Translation/Extractor/File/Fixture/MyFormType.php +++ b/Tests/Translation/Extractor/File/Fixture/MyFormType.php @@ -26,6 +26,8 @@ class MyFormType extends AbstractType { + public const CHOICES = ['choices' => [null]]; + public function buildForm(FormBuilderInterface $builder, array $options) { $builder @@ -46,7 +48,8 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'translation_domain' => 'address', 'constraints' => [ new NotBlank(['message' => /** @Desc("You should fill in the street") */ 'form.street.empty_value']), - new Length(['max' => 100]), // https://github.com/schmittjoh/JMSTranslationBundle/issues/553 + new Length(['max' => 100]), + // https://github.com/schmittjoh/JMSTranslationBundle/issues/553 ], ]) ->add('zip', 'text', [ @@ -62,69 +65,46 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'label' => false, 'attr' => ['placeholder' => /** @Desc("Field with a placeholder but no label") */ 'form.placeholder.text.but.no.label'], ]) - ->add('field_with_choice_as_values', 'choice', array( - 'choices' => array( + ->add('field_with_choice_as_values', 'choice', [ + 'choices' => [ 'form.choice.choice_as_values.label.foo' => 'form.choice.choice_as_values.value.foo', - 'form.choice.choice_as_values.label.bar' => 'form.choice.choice_as_values.value.bar' - ), + 'form.choice.choice_as_values.label.bar' => 'form.choice.choice_as_values.value.bar', + ], 'choices_as_values' => true, - )) - ; + ]); + $child = $builder->create('created', 'text', ['label' => 'form.label.created']); $builder->add('dueDate', 'date', [ - 'empty_value' => ['year' => 'form.dueDate.empty.year', 'month' => 'form.dueDate.empty.month', 'day' => 'form.dueDate.empty.day'], + 'empty_value' => [ + 'year' => 'form.dueDate.empty.year', + 'month' => 'form.dueDate.empty.month', + 'day' => 'form.dueDate.empty.day', + ], ]); $builder - ->add('choices_with_translation_domain', 'choice', array( - 'choices' => array('form.choices_with_translation_domain.label' => 'form.choices_with_translation_domain.value'), - 'choice_translation_domain' => 'choice-domain' - )) - ->add('choices_without_translation', 'choice', array( - 'choices' => array('form.choices_without_translation.label' => 'form.choices_without_translation.value'), - 'choice_translation_domain' => false, - )) - ->add('untranslatable_label', 'text', array( - 'label' => 'form.untranslatable_label.label', - 'translation_domain' => false, - )) - ; - - $builder->add('untranslateable_field_with_placeholder', 'text', array( - 'label' => 'field.with.placeholder.no.translation.domain', - 'attr' => array('placeholder' => /** @Desc("Field with a placeholder value") */ 'form.placeholder.text.skip'), - 'translation_domain' => false, - )); - - $builder->add('custom_domain_field_with_placeholder', 'text', array( - 'attr' => array('placeholder' => 'form.custom_domain_field_with_placeholder.attr.placeholder'), - 'translation_domain' => 'custom_domain_field_with_placeholder', - )); - - $builder - ->add('choices_with_translation_domain', 'choice', array( - 'choices' => array('form.choices_with_translation_domain.label' => 'form.choices_with_translation_domain.value'), - 'choice_translation_domain' => 'choice-domain' - )) - ->add('choices_without_translation', 'choice', array( - 'choices' => array('form.choices_without_translation.label' => 'form.choices_without_translation.value'), + ->add('choices_with_translation_domain', 'choice', [ + 'choices' => ['form.choices_with_translation_domain.label' => 'form.choices_with_translation_domain.value'], + 'choice_translation_domain' => 'choice-domain', + ]) + ->add('choices_without_translation', 'choice', [ + 'choices' => ['form.choices_without_translation.label' => 'form.choices_without_translation.value'], 'choice_translation_domain' => false, - )) - ->add('untranslatable_label', 'text', array( + ]) + ->add('untranslatable_label', 'text', [ 'label' => 'form.untranslatable_label.label', 'translation_domain' => false, - )) - ; + ]); - $builder->add('untranslateable_field_with_placeholder', 'text', array( + $builder->add('untranslateable_field_with_placeholder', 'text', [ 'label' => 'field.with.placeholder.no.translation.domain', - 'attr' => array('placeholder' => /** @Desc("Field with a placeholder value") */ 'form.placeholder.text.skip'), + 'attr' => ['placeholder' => /** @Desc("Field with a placeholder value") */ 'form.placeholder.text.skip'], 'translation_domain' => false, - )); + ]); - $builder->add('custom_domain_field_with_placeholder', 'text', array( - 'attr' => array('placeholder' => 'form.custom_domain_field_with_placeholder.attr.placeholder'), + $builder->add('custom_domain_field_with_placeholder', 'text', [ + 'attr' => ['placeholder' => 'form.custom_domain_field_with_placeholder.attr.placeholder'], 'translation_domain' => 'custom_domain_field_with_placeholder', - )); + ]); } } diff --git a/Tests/Translation/Extractor/File/FormExtractorTest.php b/Tests/Translation/Extractor/File/FormExtractorTest.php index bc22c2d5..262a18e3 100644 --- a/Tests/Translation/Extractor/File/FormExtractorTest.php +++ b/Tests/Translation/Extractor/File/FormExtractorTest.php @@ -72,96 +72,117 @@ public function testExtract() $fixtureSplInfo = new \SplFileInfo(__DIR__ . '/Fixture/MyFormType.php'); $message = new Message('foo'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 35)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 37)); $expected->add($message); $message = new Message('form.states.empty_value'); $message->setDesc('Please select a state'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 36)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 38)); $expected->add($message); $message = new Message('form.label.lastname'); $message->setDesc('Lastname'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 33)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 35)); $expected->add($message); $message = new Message('form.label.firstname'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 32)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 34)); $expected->add($message); $message = new Message('form.label.password'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 40)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 42)); $expected->add($message); $message = new Message('form.label.password_repeated'); $message->setDesc('Repeat password'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 41)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 43)); $expected->add($message); $message = new Message('form.label.street', 'address'); $message->setDesc('Street'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 45)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 47)); $expected->add($message); $message = new Message('form.street.empty_value', 'validators'); $message->setDesc('You should fill in the street'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 48)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 50)); $expected->add($message); $message = new Message('form.label.zip', 'address'); $message->setDesc('ZIP'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 54)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 57)); $expected->add($message); $message = new Message('form.error.password_mismatch', 'validators'); $message->setDesc('The entered passwords do not match'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 42)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 44)); $expected->add($message); $message = new Message('form.label.created'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 65)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 76)); $expected->add($message); $message = new Message('field.with.placeholder'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 58)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 61)); $expected->add($message); $message = new Message('form.placeholder.text'); $message->setDesc('Field with a placeholder value'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 59)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 62)); $expected->add($message); $message = new Message('form.placeholder.text.but.no.label'); $message->setDesc('Field with a placeholder but no label'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 63)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 66)); $expected->add($message); $message = new Message('form.dueDate.empty.year'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 67)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 79)); $expected->add($message); $message = new Message('form.dueDate.empty.month'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 67)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 80)); $expected->add($message); $message = new Message('form.dueDate.empty.day'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 67)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 81)); + $expected->add($message); + + $message = new Message(0); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, -1)); $expected->add($message); $message = new Message('form.choices_with_translation_domain.label', 'choice-domain'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 84)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 87)); $expected->add($message); - $message = new Message('form.custom_domain_field_with_placeholder.attr.placeholder', 'custom_domain_field_with_placeholder'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 104)); + $message = new Message('form.untranslatable_label.label', 'messages'); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 95)); $expected->add($message); - $message = new Message('form.choices_with_translation_domain.label', 'choice-domain'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 84)); + $message = new Message('field.with.placeholder.no.translation.domain', 'messages'); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 100)); + $expected->add($message); + + $message = new Message('form.choices_without_translation.label', 'messages'); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 91)); + $expected->add($message); + + $message = new Message('form.placeholder.text.skip', 'messages'); + $message->setDesc('Field with a placeholder value'); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 101)); $expected->add($message); $message = new Message('form.custom_domain_field_with_placeholder.attr.placeholder', 'custom_domain_field_with_placeholder'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 104)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 106)); + $expected->add($message); + + $message = new Message('form.choice.choice_as_values.label.foo'); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 70)); + $expected->add($message); + + $message = new Message('form.choice.choice_as_values.label.bar'); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 71)); $expected->add($message); $this->assertEquals($expected, $this->extract('MyFormType.php')); diff --git a/Tests/Translation/Extractor/File/TranslationContainerExtractorTest.php b/Tests/Translation/Extractor/File/TranslationContainerExtractorTest.php index b2dbc221..7d65e560 100644 --- a/Tests/Translation/Extractor/File/TranslationContainerExtractorTest.php +++ b/Tests/Translation/Extractor/File/TranslationContainerExtractorTest.php @@ -62,7 +62,7 @@ private function extract($file, ?TranslationContainerExtractor $extractor = null $lexer = new Lexer(); if (class_exists(ParserFactory::class)) { $factory = new ParserFactory(); - $parser = $factory->create(ParserFactory::PREFER_PHP7, $lexer); + $parser = \method_exists($factory, 'create') ? $factory->create(ParserFactory::PREFER_PHP7, $lexer) : $factory->createForNewestSupportedVersion(); } else { $parser = new Parser($lexer); } diff --git a/Tests/Translation/Extractor/File/ValidationContextExtractorTest.php b/Tests/Translation/Extractor/File/ValidationContextExtractorTest.php index ee258efa..7651ce47 100644 --- a/Tests/Translation/Extractor/File/ValidationContextExtractorTest.php +++ b/Tests/Translation/Extractor/File/ValidationContextExtractorTest.php @@ -1,5 +1,7 @@ getFileSourceFactory(); - $fixtureSplInfo = new \SplFileInfo(__DIR__.'/Fixture/MyEntity.php'); - + $fixtureSplInfo = new \SplFileInfo(__DIR__ . '/Fixture/MyEntity.php'); $expected = new MessageCatalogue(); $message = new Message('entity.default'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 15)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 14)); $expected->add($message); $message = new Message('entity.custom-domain', 'custom-domain'); - $message->addSource($fileSourceFactory->create($fixtureSplInfo, 22)); + $message->addSource($fileSourceFactory->create($fixtureSplInfo, 21)); $expected->add($message); $this->assertEquals($expected, $this->extract('MyEntity.php')); diff --git a/Tests/Translation/Extractor/File/ValidationExtractorTest.php b/Tests/Translation/Extractor/File/ValidationExtractorTest.php index 18136c3f..234788c7 100644 --- a/Tests/Translation/Extractor/File/ValidationExtractorTest.php +++ b/Tests/Translation/Extractor/File/ValidationExtractorTest.php @@ -62,7 +62,7 @@ private function extract($file, ?ValidationExtractor $extractor = null) $lexer = new Lexer(); if (class_exists(ParserFactory::class)) { $factory = new ParserFactory(); - $parser = $factory->create(ParserFactory::PREFER_PHP7, $lexer); + $parser = \method_exists($factory, 'create') ? $factory->create(ParserFactory::PREFER_PHP7, $lexer) : $factory->createForNewestSupportedVersion(); } else { $parser = new Parser($lexer); } diff --git a/Tests/Twig/RemovingNodeVisitorTest.php b/Tests/Twig/RemovingNodeVisitorTest.php index e2da3374..f44b3eb3 100644 --- a/Tests/Twig/RemovingNodeVisitorTest.php +++ b/Tests/Twig/RemovingNodeVisitorTest.php @@ -30,8 +30,8 @@ public function testRemovalWithSimpleTemplate(): void $templateSuffix = $isSF5 ? '_sf5' : ''; - $expected = $this->parse("simple_template_compiled${templateSuffix}.html.twig"); - $actual = $this->parse("simple_template${templateSuffix}.html.twig"); + $expected = $this->parse('simple_template_compiled' . $templateSuffix . '.html.twig'); + $actual = $this->parse('simple_template' . $templateSuffix . '.html.twig'); $this->assertEquals($expected, $actual); } diff --git a/Translation/Extractor/File/AuthenticationMessagesExtractor.php b/Translation/Extractor/File/AuthenticationMessagesExtractor.php index 4621fd03..1eaff0e5 100644 --- a/Translation/Extractor/File/AuthenticationMessagesExtractor.php +++ b/Translation/Extractor/File/AuthenticationMessagesExtractor.php @@ -116,7 +116,7 @@ public function enterNode(Node $node) { if ($node instanceof Node\Stmt\Namespace_) { if (isset($node->name)) { - $this->namespace = implode('\\', $node->name->parts); + $this->namespace = property_exists($node->name, 'parts') ? implode('\\', $node->name->parts) : $node->name->name; } return; diff --git a/Translation/Extractor/File/DefaultPhpFileExtractor.php b/Translation/Extractor/File/DefaultPhpFileExtractor.php index 7fbc7b53..4d9287c3 100644 --- a/Translation/Extractor/File/DefaultPhpFileExtractor.php +++ b/Translation/Extractor/File/DefaultPhpFileExtractor.php @@ -159,15 +159,36 @@ public function enterNode(Node $node) } $id = $node->args[0]->value->value; - $index = $this->methodsToExtractFrom[strtolower($methodCallNodeName)]; - if (isset($node->args[$index])) { - if (!$node->args[$index]->value instanceof String_) { + $domainArg = null; + + if (isset($node->args[$index]) && $node->args[$index] instanceof Node\Arg && null === $node->args[$index]->name) { + $domainArg = $node->args[$index]; + } else { + foreach ($node->args as $arg) { + if (!$arg instanceof Node\Arg) { + continue; + } + + if (null !== $arg->name && 'domain' === $arg->name->name) { + $domainArg = $arg; + + break; + } + } + } + + if (null !== $domainArg) { + if ($domainArg->value instanceof Node\Expr\ConstFetch && 'null' === (string) $domainArg->value->name) { + $domain = 'messages'; + } elseif ($domainArg->value instanceof String_) { + $domain = $domainArg->value->value; + } else { if ($ignore) { return; } - $message = sprintf('Can only extract the translation domain from a scalar string, but got "%s". Please refactor your code to make it extractable, or add the doc comment /** @Ignore */ to this code element (in %s on line %d).', get_class($node->args[$index]->value), $this->file, $node->args[$index]->value->getLine()); + $message = sprintf('Can only extract the translation domain from a scalar string, but got "%s". Please refactor your code to make it extractable, or add the doc comment /** @Ignore */ to this code element (in %s on line %d).', get_class($domainArg->value), $this->file, $domainArg->value->getLine()); if ($this->logger) { $this->logger->error($message); @@ -177,8 +198,6 @@ public function enterNode(Node $node) throw new RuntimeException($message); } - - $domain = $node->args[$index]->value->value; } else { $domain = 'messages'; } diff --git a/Translation/Extractor/File/FormExtractor.php b/Translation/Extractor/File/FormExtractor.php index 44e4b944..e7f8aed3 100644 --- a/Translation/Extractor/File/FormExtractor.php +++ b/Translation/Extractor/File/FormExtractor.php @@ -28,6 +28,7 @@ use JMS\TranslationBundle\Logger\LoggerAwareInterface; use JMS\TranslationBundle\Model\Message; use JMS\TranslationBundle\Model\MessageCatalogue; +use JMS\TranslationBundle\Model\SourceInterface; use JMS\TranslationBundle\Translation\Extractor\FileVisitorInterface; use JMS\TranslationBundle\Translation\FileSourceFactory; use PhpParser\Comment\Doc; @@ -76,7 +77,7 @@ class FormExtractor implements FileVisitorInterface, LoggerAwareInterface, NodeV private $defaultDomain; /** - * @var string + * @var array */ private $defaultDomainMessages; @@ -133,7 +134,7 @@ public function enterNode(Node $node) // look for options containing a message foreach ($node->items as $item) { - if (!$item->key instanceof Node\Scalar\String_) { + if (!is_object($item) || !$item->key instanceof Node\Scalar\String_) { continue; } @@ -187,10 +188,6 @@ public function getDomain(Node $node) return $this->getDomainValueForKey('translation_domain', $node); } - /** - * @param Node $node - * @return null|string - */ public function getChoiceDomain(Node $node) { return $this->getDomainValueForKey('choice_translation_domain', $node); @@ -201,12 +198,12 @@ private function getDomainValueForKey($key, Node $node) $domain = null; foreach ($node->items as $item) { - if (!$item->key instanceof Node\Scalar\String_) { + if (!is_object($item) || !$item->key instanceof Node\Scalar\String_) { continue; } if ($key === $item->key->value) { - if ($item->value instanceof Node\Expr\ConstFetch && $item->value->name->parts[0] === 'false') { + if ($item->value instanceof Node\Expr\ConstFetch && $item->value->name === 'false') { $domain = false; break; } @@ -237,7 +234,7 @@ private function getDomainValueForKey($key, Node $node) protected function parseEmptyValueNode(Node $item, $domain) { // Skip empty_value when false - if ($item->value instanceof Node\Expr\ConstFetch && $item->value->name instanceof Node\Name && 'false' === $item->value->name->parts[0]) { + if ($item->value instanceof Node\Expr\ConstFetch && $item->value->name instanceof Node\Name && 'false' === (property_exists($item->value->name, 'parts') ? $item->value->name->parts[0] : $item->value->name->getFirst())) { return true; } @@ -274,10 +271,10 @@ protected function parseChoiceNode(Node $item, Node $node, $domain) return true; } - foreach ($item->value->items as $subItem) { + foreach ($item->value->items as $index => $subItem) { $newItem = clone $subItem; $newItem->key = $subItem->value; - $newItem->value = $subItem->key; + $newItem->value = $subItem->key ?? new Node\Scalar\LNumber($index); $subItem = $newItem; $this->parseItem($subItem, $domain); } @@ -409,7 +406,7 @@ private function parseDefaultsCall(Node $node) // check if a translation_domain is set as a default option $domain = null; foreach ($node->args[0]->value->items as $item) { - if (!$item->key instanceof Node\Scalar\String_) { + if (!is_object($item) || !$item->key instanceof Node\Scalar\String_) { continue; } @@ -441,6 +438,10 @@ private function parseItem($item, $domain = null) $docComment = $item->value->getDocComment(); } + if (!$docComment) { + $docComment = $item->getDocComment(); + } + $docComment = is_object($docComment) ? $docComment->getText() : null; if ($docComment) { @@ -461,7 +462,7 @@ private function parseItem($item, $domain = null) // check if the value is explicitly set to false => e.g. for FormField that should be rendered without label $ignore = $ignore || !$item->value instanceof Node\Scalar\String_ || $item->value->value === false; - if (!$item->value instanceof Node\Scalar\String_ && !$item->value instanceof Node\Scalar\LNumber) { + if (!$item->value instanceof Node\Scalar\String_ && !$item->value instanceof Node\Scalar\LNumber && !$item->value instanceof Node\Scalar\Int_) { if ($ignore) { return; } @@ -498,7 +499,7 @@ private function parseItem($item, $domain = null) /** * @param string $id - * @param string $source + * @param SourceInterface $source * @param string|null $domain * @param string|null $desc * @param string|null $meaning diff --git a/Translation/Extractor/File/TranslationContainerExtractor.php b/Translation/Extractor/File/TranslationContainerExtractor.php index b2044883..5e565656 100644 --- a/Translation/Extractor/File/TranslationContainerExtractor.php +++ b/Translation/Extractor/File/TranslationContainerExtractor.php @@ -74,7 +74,7 @@ public function enterNode(Node $node) { if ($node instanceof Node\Stmt\Namespace_) { if (isset($node->name)) { - $this->namespace = implode('\\', $node->name->parts); + $this->namespace = property_exists($node->name, 'parts') ? implode('\\', $node->name->parts) : $node->name->name; } $this->useStatements = []; @@ -83,7 +83,7 @@ public function enterNode(Node $node) if ($node instanceof Node\Stmt\UseUse) { $nodeAliasName = is_string($node->alias) ? $node->alias : $node->getAlias()->name; - $this->useStatements[$nodeAliasName] = implode('\\', $node->name->parts); + $this->useStatements[$nodeAliasName] = property_exists($node->name, 'parts') ? implode('\\', $node->name->parts) : $node->name->name; return; } @@ -94,7 +94,7 @@ public function enterNode(Node $node) $isContainer = false; foreach ($node->implements as $interface) { - $name = implode('\\', $interface->parts); + $name = property_exists($interface, 'parts') ? implode('\\', $interface->parts) : $interface->name; if (isset($this->useStatements[$name])) { $name = $this->useStatements[$name]; } diff --git a/Translation/Extractor/File/ValidationContextExtractor.php b/Translation/Extractor/File/ValidationContextExtractor.php index 7c244cf0..826283b0 100644 --- a/Translation/Extractor/File/ValidationContextExtractor.php +++ b/Translation/Extractor/File/ValidationContextExtractor.php @@ -1,5 +1,7 @@ * @@ -28,12 +30,8 @@ use PhpParser\NodeTraverser; use PhpParser\NodeVisitor; use SplFileInfo; +use Twig\Node\Node as TwigNode; -/** - * Class ValidationContextExtractor - * - * Extracts - */ class ValidationContextExtractor implements FileVisitorInterface, NodeVisitor { /** @@ -74,11 +72,6 @@ class ValidationContextExtractor implements FileVisitorInterface, NodeVisitor private $source; private $fileSourceFactory; - /** - * ValidationContextExtractor constructor. - * - * @param FileSourceFactory $fileSourceFactory - */ public function __construct(FileSourceFactory $fileSourceFactory) { $this->fileSourceFactory = $fileSourceFactory; @@ -111,7 +104,7 @@ public function visitPhpFile(SplFileInfo $file, MessageCatalogue $catalogue, arr /** * {@inheritdoc} */ - public function visitTwigFile(SplFileInfo $file, MessageCatalogue $catalogue, \Twig_Node $ast) + public function visitTwigFile(\SplFileInfo $file, MessageCatalogue $catalogue, TwigNode $ast) { } @@ -135,7 +128,8 @@ public function enterNode(Node $node) if ($node instanceof Node\Stmt\Use_) { foreach ($node->uses as $use) { - $this->aliases[$use->alias] = (string) $use->name; + $parts = explode('\\', (string) $use->name); + $this->aliases[end($parts)] = (string) $use->name; } return; @@ -149,7 +143,7 @@ public function enterNode(Node $node) $param1 = $params[0]; $paramClass = $this->resolveAlias((string) $param1->type); if (is_subclass_of($paramClass, '\Symfony\Component\Validator\Context\ExecutionContextInterface')) { - $this->contextVariable = $param1->name; + $this->contextVariable = $param1->getType(); } return; @@ -160,9 +154,6 @@ public function enterNode(Node $node) } } - /** - * @param Node\Expr\MethodCall $node - */ private function parseMethodCall(Node\Expr\MethodCall $node) { if (!$this->contextVariable) { @@ -173,7 +164,7 @@ private function parseMethodCall(Node\Expr\MethodCall $node) $this->parseMethodCall($node->var); } - if ($node->name === 'buildViolation') { + if ((string) $node->name === 'buildViolation') { $this->id = null; $this->domain = null; @@ -184,15 +175,15 @@ private function parseMethodCall(Node\Expr\MethodCall $node) $this->source = $this->fileSourceFactory->create($this->file, $arg1->value->getLine()); } } - } elseif ($node->name === 'setTranslationDomain') { + } elseif ((string) $node->name === 'setTranslationDomain') { if ($node->args) { $arg1 = $node->args[0]; if ($arg1->value instanceof Node\Scalar\String_) { $this->domain = $arg1->value->value; } } - } elseif ($node->name === 'addViolation') { - if ($this->id and $this->source) { + } elseif ((string) $node->name === 'addViolation') { + if ($this->id && $this->source) { $this->messages[] = [ 'id' => $this->id, 'source' => $this->source, @@ -244,13 +235,8 @@ private function addToCatalogue($id, SourceInterface $source, $domain = null) $this->catalogue->add($message); } - /** - * @param $class - * - * @return string - */ - private function resolveAlias($class) + private function resolveAlias(string $class): string { - return isset($this->aliases[$class]) ? $this->aliases[$class] : $class; + return $this->aliases[$class] ?: $class; } } diff --git a/Translation/Extractor/File/ValidationExtractor.php b/Translation/Extractor/File/ValidationExtractor.php index 7316c7e4..4fc01f99 100644 --- a/Translation/Extractor/File/ValidationExtractor.php +++ b/Translation/Extractor/File/ValidationExtractor.php @@ -84,7 +84,7 @@ public function enterNode(Node $node) { if ($node instanceof Node\Stmt\Namespace_) { if (isset($node->name)) { - $this->namespace = implode('\\', $node->name->parts); + $this->namespace = property_exists($node->name, 'parts') ? implode('\\', $node->name->parts) : $node->name->name; } return; @@ -168,7 +168,7 @@ private function extractFromConstraints(array $constraints) foreach ($constraints as $constraint) { $ref = new \ReflectionClass($constraint); $defaultValues = $ref->getDefaultProperties(); - + $defaultParameters = null !== $ref->getConstructor() ? $ref->getConstructor()->getParameters() : []; $properties = $ref->getProperties(); foreach ($properties as $property) { @@ -177,9 +177,18 @@ private function extractFromConstraints(array $constraints) // If the property ends with 'Message' if (strtolower(substr($propName, -1 * strlen('Message'))) === 'message') { // If it is different from the default value - if ($defaultValues[$propName] !== $constraint->{$propName}) { + if (array_key_exists($propName, $defaultValues) && $defaultValues[$propName] !== $constraint->{$propName}) { $message = new Message($constraint->{$propName}, 'validators'); $this->catalogue->add($message); + } elseif (method_exists($property, 'isPromoted') && $property->isPromoted()) { + foreach ($defaultParameters as $defaultParameter) { + if ($defaultParameter->getName() === $propName && $defaultParameter->isDefaultValueAvailable() && $defaultParameter->getDefaultValue() !== $constraint->{$propName}) { + $message = new Message($constraint->{$propName}, 'validators'); + $this->catalogue->add($message); + + break; + } + } } } } diff --git a/Translation/Extractor/FileExtractor.php b/Translation/Extractor/FileExtractor.php index 34c5e2a5..b7a6c576 100644 --- a/Translation/Extractor/FileExtractor.php +++ b/Translation/Extractor/FileExtractor.php @@ -107,7 +107,7 @@ public function __construct(Environment $twig, LoggerInterface $logger, array $v $lexer = new Lexer(); if (class_exists(ParserFactory::class)) { $factory = new ParserFactory(); - $this->phpParser = $factory->create(ParserFactory::PREFER_PHP7, $lexer); + $this->phpParser = \method_exists($factory, 'create') ? $factory->create(ParserFactory::PREFER_PHP7, $lexer) : $factory->createForNewestSupportedVersion(); } else { $this->phpParser = new Parser($lexer); } diff --git a/Translation/Loader/Symfony/XliffLoader.php b/Translation/Loader/Symfony/XliffLoader.php index 7cd12205..3440a301 100644 --- a/Translation/Loader/Symfony/XliffLoader.php +++ b/Translation/Loader/Symfony/XliffLoader.php @@ -24,6 +24,7 @@ use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Translation\Loader\LoaderInterface; use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\TranslatorBagInterface; /** * XLIFF loader. @@ -34,12 +35,10 @@ * * @author Johannes M. Schmitt */ -class XliffLoader implements LoaderInterface +// phpcs:ignore +class XliffLoaderInternal { - /** - * {@inheritdoc} - */ - public function load($resource, $locale, $domain = 'messages') + protected function loadInternal($resource, $locale, $domain = 'messages') { $previous = libxml_use_internal_errors(true); if (false === $xml = simplexml_load_file((string) $resource)) { @@ -66,3 +65,25 @@ public function load($resource, $locale, $domain = 'messages') return $catalogue; } } + +$isSf6 = method_exists(TranslatorBagInterface::class, 'getCatalogues'); + +if ($isSf6) { + // phpcs:ignore + class XliffLoader extends XliffLoaderInternal implements LoaderInterface + { + public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue + { + return $this->loadInternal($resource, $locale, $domain); + } + } +} else { + // phpcs:ignore + class XliffLoader extends XliffLoaderInternal implements LoaderInterface + { + public function load($resource, $locale, $domain = 'messages') + { + return $this->loadInternal($resource, $locale, $domain); + } + } +} diff --git a/Translation/Loader/XliffLoader.php b/Translation/Loader/XliffLoader.php index 05726cd9..129c328a 100644 --- a/Translation/Loader/XliffLoader.php +++ b/Translation/Loader/XliffLoader.php @@ -129,6 +129,7 @@ private function libxmlDisableEntityLoader(bool $disable): bool return true; } + // phpcs:ignore return libxml_disable_entity_loader($disable); } } diff --git a/Translation/Updater.php b/Translation/Updater.php index dc6dae71..a95cbf8e 100644 --- a/Translation/Updater.php +++ b/Translation/Updater.php @@ -181,9 +181,9 @@ public function process(Config $config) // Remove file if all translations removed $endOfFile = sprintf('.%s.%s', $this->config->getLocale(), $format); $translationFilesRegex = sprintf('/%s$/', $endOfFile); - foreach(Finder::create()->name($translationFilesRegex)->in($this->config->getTranslationsDir())->files() as $file){ + foreach (Finder::create()->name($translationFilesRegex)->in($this->config->getTranslationsDir())->files() as $file) { $domainName = str_replace($endOfFile, '', $file->getFilename()); - if($this->scannedCatalogue->hasDomain($domainName)){ + if ($this->scannedCatalogue->hasDomain($domainName)) { continue; } diff --git a/Twig/DefaultApplyingNodeVisitor.php b/Twig/DefaultApplyingNodeVisitor.php index 4a574e0c..b389d72f 100644 --- a/Twig/DefaultApplyingNodeVisitor.php +++ b/Twig/DefaultApplyingNodeVisitor.php @@ -74,7 +74,7 @@ public function doEnterNode(Node $node, Environment $env) } if (!$transNode instanceof FilterExpression) { - throw new RuntimeException(sprintf('The "desc" filter must be applied after a "trans", or "transchoice" filter.')); + throw new RuntimeException(sprintf('The "desc" filter in "%s" line %d must be applied after a "trans", or "transchoice" filter.', $node->getTemplateName(), $node->getTemplateLine())); } $wrappingNode = $node->getNode('node'); diff --git a/composer.json b/composer.json index 064ff862..f65d4aee 100644 --- a/composer.json +++ b/composer.json @@ -21,39 +21,50 @@ } ], "require": { - "php": "^7.2 || ^8.0", - "nikic/php-parser": "^1.4 || ^2.0 || ^3.0 || ^4.0", - "symfony/console": "^3.4 || ^4.3 || ^5.0", - "symfony/framework-bundle": "^3.4.31 || ^4.3 || ^5.0", - "symfony/translation": "^3.4 || ^4.3 || ^5.0", - "symfony/translation-contracts": "^1.1 || ^2.0", - "symfony/validator": "^3.4 || ^4.3 || ^5.0", - "twig/twig": "^1.42.4 || ^2.12.5 || ^3.0" + "php": "^7.4 || ^8.0", + "nikic/php-parser": "^4.9 || ^5", + "symfony/console": "^4.3 || ^5.4 || ^6.0", + "symfony/expression-language": "^4.3 || ^5.4 || ^6.0", + "symfony/framework-bundle": "^4.3 || ^5.4 || ^6.0", + "symfony/config": "^4.3 || ^5.4 || ^6.2", + "symfony/translation": "^4.3 || ^5.4 || ^6.0", + "symfony/translation-contracts": "^1.1 || ^2.0 || ^3.0", + "symfony/validator": "^4.3 || ^5.4 || ^6.0", + "twig/twig": "^1.42.4 || ^2.12.5 || ^3.0", + "psr/log": "^1.0 || ^2.0" }, "require-dev": { - "doctrine/annotations": "^1.8", - "doctrine/coding-standard": "^8.0", + "doctrine/annotations": "^1.11", + "doctrine/coding-standard": "^8.2.1", "matthiasnoback/symfony-dependency-injection-test": "^4.1", "nyholm/nsa": "^1.0.1", - "phpunit/phpunit": "^8.3", - "psr/log": "^1.0", - "sensio/framework-extra-bundle": "^5.4", - "symfony/asset": "^3.4 || ^4.3 || ^5.0", - "symfony/browser-kit": "^3.4 || ^4.3 || ^5.0", - "symfony/css-selector": "^3.4 || ^4.3 || ^5.0", - "symfony/expression-language": "^3.4 || ^4.3 || ^5.0", - "symfony/filesystem": "^3.4 || ^4.3 || ^5.0", - "symfony/form": "^3.4 || ^4.3 || ^5.0", - "symfony/security-csrf": "^3.4 || ^4.3 || ^5.0", - "symfony/templating": "^3.4 || ^4.3 || ^5.0", - "symfony/twig-bundle": "^3.4.37 || ^4.3.11 || ^5.0" + "symfony/phpunit-bridge": "^4.4 || ^5.4 || ^6.4", + "sensio/framework-extra-bundle": "^6.2.4", + "symfony/asset": "^4.4 || ^5.4 || ^6.4", + "symfony/browser-kit": "^4.4 || ^5.4 || ^6.4", + "symfony/css-selector": "^4.4 || ^5.4 || ^6.4", + "symfony/filesystem": "^4.4 || ^5.4 || ^6.4", + "symfony/form": "^4.4 || ^5.4 || ^6.4", + "symfony/security-csrf": "^4.4 || ^5.4 || ^6.4", + "symfony/templating": "^4.4 || ^5.4 || ^6.4", + "symfony/property-access": "^4.4 || ^5.4 || ^6.4", + "symfony/routing": "^4.4.15 || ^5.4 || ^6.4", + "symfony/twig-bundle": "^4.4 || ^5.4 || ^6.4", + "symfony/flex": "^1.19 || ^2.0" }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "symfony/flex": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } }, "extra": { "branch-alias": { - "dev-master": "1.6-dev" + "dev-master": "2.x-dev" + }, + "symfony": { + "allow-contrib": true } }, "autoload": { @@ -61,5 +72,10 @@ "JMS\\TranslationBundle\\": "" } }, - "minimum-stability": "stable" + "scripts": { + "auto-scripts": { + "cache:clear": "symfony-cmd", + "assets:install %PUBLIC_DIR%": "symfony-cmd" + } + } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 4f50e4d9..c1ca707b 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -4,6 +4,7 @@ +