From 2efae476bba4c73a2cf37eb818e158e334194b8b Mon Sep 17 00:00:00 2001 From: cs Date: Tue, 31 Mar 2026 18:35:45 +0200 Subject: [PATCH 1/3] [FEATURE] Add XLIFF processing rules and tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ✨ Introduced `EnsureXliffHasSourceLanguageFractor` to ensure source-language attribute is present in XLIFF files. - ✨ Introduced `EnsureXliffHasTargetLanguageFractor` to add target-language attribute based on filename. - ✨ Added `ConvertXliff1To2Fractor` for converting XLIFF 1.2 files to XLIFF 2.0 format. - 🧪 Created tests for the new rules with various fixture files to validate functionality. - 📄 Added configuration files for the new rules to integrate with the Fractor framework. - 🛠️ Implemented necessary value objects and factories for handling XLIFF documents and configurations. --- composer.json | 11 + packages/fractor-xliff/.gitattributes | 5 + packages/fractor-xliff/.gitignore | 3 + packages/fractor-xliff/LICENSE | 25 ++ packages/fractor-xliff/README.md | 91 +++++++ packages/fractor-xliff/composer.json | 55 +++++ packages/fractor-xliff/config/application.php | 38 +++ .../fractor-xliff/docs/xliff-fractor-rules.md | 75 ++++++ packages/fractor-xliff/phpunit.xml | 13 + .../ConvertXliff1To2FractorTest.php | 29 +++ .../Fixtures/basic.xlf.fixture | 24 ++ .../Fixtures/with-groups.xlf.fixture | 35 +++ .../Fixtures/with-notes.xlf.fixture | 27 +++ .../with-target-and-approved.xlf.fixture | 36 +++ .../config/fractor.php | 15 ++ ...nsureXliffHasSourceLanguageFractorTest.php | 29 +++ .../missing-source-language-v1.xlf.fixture | 23 ++ .../Fixtures/missing-srclang-v2.xlf.fixture | 23 ++ .../config/fractor.php | 15 ++ ...nsureXliffHasTargetLanguageFractorTest.php | 29 +++ .../de.add-target-lang-v1.xlf.fixture | 25 ++ .../Fixtures/de.add-trglang-v2.xlf.fixture | 25 ++ .../config/fractor.php | 15 ++ .../rules/ConvertXliff1To2Fractor.php | 223 ++++++++++++++++++ .../EnsureXliffHasSourceLanguageFractor.php | 103 ++++++++ .../EnsureXliffHasTargetLanguageFractor.php | 121 ++++++++++ .../Configuration/XliffProcessorOption.php | 14 ++ .../src/Contract/XliffFractorRule.php | 19 ++ .../fractor-xliff/src/DomDocumentFactory.php | 17 ++ packages/fractor-xliff/src/IndentFactory.php | 29 +++ .../src/ValueObject/XliffDocument.php | 17 ++ .../ValueObject/XliffFormatConfiguration.php | 29 +++ .../src/ValueObject/XliffVersion.php | 51 ++++ .../fractor-xliff/src/XliffFileProcessor.php | 112 +++++++++ .../tests/Fixtures/DummyXliffFractorRule.php | 33 +++ .../Fixtures/xliff12.xlf.fixture | 23 ++ .../XliffFileProcessorTest.php | 29 +++ .../XliffFileProcessor/config/fractor.php | 15 ++ packages/typo3-fractor/composer.json | 1 + 39 files changed, 1502 insertions(+) create mode 100644 packages/fractor-xliff/.gitattributes create mode 100644 packages/fractor-xliff/.gitignore create mode 100644 packages/fractor-xliff/LICENSE create mode 100644 packages/fractor-xliff/README.md create mode 100644 packages/fractor-xliff/composer.json create mode 100644 packages/fractor-xliff/config/application.php create mode 100644 packages/fractor-xliff/docs/xliff-fractor-rules.md create mode 100644 packages/fractor-xliff/phpunit.xml create mode 100644 packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/ConvertXliff1To2FractorTest.php create mode 100644 packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/basic.xlf.fixture create mode 100644 packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/with-groups.xlf.fixture create mode 100644 packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/with-notes.xlf.fixture create mode 100644 packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/with-target-and-approved.xlf.fixture create mode 100644 packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/config/fractor.php create mode 100644 packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/EnsureXliffHasSourceLanguageFractorTest.php create mode 100644 packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/Fixtures/missing-source-language-v1.xlf.fixture create mode 100644 packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/Fixtures/missing-srclang-v2.xlf.fixture create mode 100644 packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/config/fractor.php create mode 100644 packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/EnsureXliffHasTargetLanguageFractorTest.php create mode 100644 packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/Fixtures/de.add-target-lang-v1.xlf.fixture create mode 100644 packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/Fixtures/de.add-trglang-v2.xlf.fixture create mode 100644 packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/config/fractor.php create mode 100644 packages/fractor-xliff/rules/ConvertXliff1To2Fractor.php create mode 100644 packages/fractor-xliff/rules/EnsureXliffHasSourceLanguageFractor.php create mode 100644 packages/fractor-xliff/rules/EnsureXliffHasTargetLanguageFractor.php create mode 100644 packages/fractor-xliff/src/Configuration/XliffProcessorOption.php create mode 100644 packages/fractor-xliff/src/Contract/XliffFractorRule.php create mode 100644 packages/fractor-xliff/src/DomDocumentFactory.php create mode 100644 packages/fractor-xliff/src/IndentFactory.php create mode 100644 packages/fractor-xliff/src/ValueObject/XliffDocument.php create mode 100644 packages/fractor-xliff/src/ValueObject/XliffFormatConfiguration.php create mode 100644 packages/fractor-xliff/src/ValueObject/XliffVersion.php create mode 100644 packages/fractor-xliff/src/XliffFileProcessor.php create mode 100644 packages/fractor-xliff/tests/Fixtures/DummyXliffFractorRule.php create mode 100644 packages/fractor-xliff/tests/XliffFileProcessor/Fixtures/xliff12.xlf.fixture create mode 100644 packages/fractor-xliff/tests/XliffFileProcessor/XliffFileProcessorTest.php create mode 100644 packages/fractor-xliff/tests/XliffFileProcessor/config/fractor.php diff --git a/composer.json b/composer.json index 7f89d0c2..e270ff31 100644 --- a/composer.json +++ b/composer.json @@ -59,6 +59,7 @@ "a9f/fractor-phpstan-rules": "self.version", "a9f/fractor-rule-generator": "self.version", "a9f/fractor-typoscript": "self.version", + "a9f/fractor-xliff": "self.version", "a9f/fractor-xml": "self.version", "a9f/fractor-yaml": "self.version", "a9f/typo3-fractor": "self.version" @@ -78,6 +79,10 @@ "a9f\\FractorPhpStanRules\\": "packages/fractor-phpstan-rules/src/", "a9f\\FractorRuleGenerator\\": "packages/fractor-rule-generator/src/", "a9f\\FractorTypoScript\\": "packages/fractor-typoscript/src/", + "a9f\\FractorXliff\\": [ + "packages/fractor-xliff/rules/", + "packages/fractor-xliff/src/" + ], "a9f\\FractorXml\\": "packages/fractor-xml/src/", "a9f\\FractorYaml\\": "packages/fractor-yaml/src/", "a9f\\Fractor\\": "packages/fractor/src/", @@ -99,6 +104,10 @@ "a9f\\FractorPhpStanRules\\Tests\\": "packages/fractor-phpstan-rules/tests/", "a9f\\FractorRuleGenerator\\Tests\\": "packages/fractor-rule-generator/tests/", "a9f\\FractorTypoScript\\Tests\\": "packages/fractor-typoscript/tests/", + "a9f\\FractorXliff\\Tests\\": [ + "packages/fractor-xliff/rules-tests/", + "packages/fractor-xliff/tests/" + ], "a9f\\FractorXml\\Tests\\": "packages/fractor-xml/tests/", "a9f\\FractorYaml\\Tests\\": "packages/fractor-yaml/tests/", "a9f\\Fractor\\Tests\\": "packages/fractor/tests/", @@ -160,6 +169,7 @@ "@composer normalize --dry-run packages/fractor-fluid/composer.json", "@composer normalize --dry-run packages/fractor-htaccess/composer.json", "@composer normalize --dry-run packages/fractor-typoscript/composer.json", + "@composer normalize --dry-run packages/fractor-xliff/composer.json", "@composer normalize --dry-run packages/fractor-xml/composer.json", "@composer normalize --dry-run packages/fractor-yaml/composer.json", "@composer normalize --dry-run packages/typo3-fractor/composer.json" @@ -174,6 +184,7 @@ "@composer normalize --no-check-lock packages/fractor-fluid/composer.json", "@composer normalize --no-check-lock packages/fractor-htaccess/composer.json", "@composer normalize --no-check-lock packages/fractor-typoscript/composer.json", + "@composer normalize --no-check-lock packages/fractor-xliff/composer.json", "@composer normalize --no-check-lock packages/fractor-xml/composer.json", "@composer normalize --no-check-lock packages/fractor-yaml/composer.json", "@composer normalize --no-check-lock packages/typo3-fractor/composer.json" diff --git a/packages/fractor-xliff/.gitattributes b/packages/fractor-xliff/.gitattributes new file mode 100644 index 00000000..27fe5f73 --- /dev/null +++ b/packages/fractor-xliff/.gitattributes @@ -0,0 +1,5 @@ +tests export-ignore +.gitattributes export-ignore +.gitignore export-ignore +phpunit.xml export-ignore +README.md export-ignore diff --git a/packages/fractor-xliff/.gitignore b/packages/fractor-xliff/.gitignore new file mode 100644 index 00000000..ff1cad65 --- /dev/null +++ b/packages/fractor-xliff/.gitignore @@ -0,0 +1,3 @@ +/vendor/ +/composer.lock +.phpunit.cache diff --git a/packages/fractor-xliff/LICENSE b/packages/fractor-xliff/LICENSE new file mode 100644 index 00000000..42f67d03 --- /dev/null +++ b/packages/fractor-xliff/LICENSE @@ -0,0 +1,25 @@ +The MIT License +--------------- + +Copyright (c) 2026-present Sebastian Schreiber and Christian Sonntag + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/fractor-xliff/README.md b/packages/fractor-xliff/README.md new file mode 100644 index 00000000..ef73efaf --- /dev/null +++ b/packages/fractor-xliff/README.md @@ -0,0 +1,91 @@ +# Fractor XLIFF + +XLIFF extension for the [Fractor](https://github.com/andreaswolf/fractor) file refactoring tool. + +Allows validating and transforming XLIFF (XML Localization Interchange File Format) translation files. +Supports XLIFF Versions 1.0, 1.1, 1.2 and 2.0. + +## Installation + +```bash +composer require a9f/fractor-xliff --dev +``` + +## Configuration + +```php +withPaths([__DIR__ . '/Resources/Private/Language/']) + ->withOptions([ + XliffProcessorOption::INDENT_CHARACTER => Indent::STYLE_SPACE, + XliffProcessorOption::INDENT_SIZE => 4, + XliffProcessorOption::ALLOWED_FILE_EXTENSIONS => ['xlf', 'xliff'], + ]); +``` + +Have a look at all available rules [Overview of all rules](docs/xliff-fractor-rules.md) + +## Processed File Extensions + +By default, the following file extensions are processed: `xlf`, `xliff`. + +## For Devlopers + +All rules must implement the a9f\FractorXliff\Contract\XliffFractorRule interface. +The rule will be tagged with 'fractor.xliff_rule' and be injected in the XliffFileProcessor. + +### Testing with DDEV + +#### phpstan + +```bash +ddev composer analyze:php +``` + +#### rector + +```bash +ddev composer rector +``` + +Fix with: + +```bash +ddev exec rector src/ +``` + +#### composer normalize + +```bash +ddev composer style:composer:normalize +``` + +Fix with: + +```bash +ddev composer normalize +``` + +#### php-cs-fixer + +```bash +ddev composer style:php:check +``` + +Fix with: + +```bash +ddev exec ecs check --fix --config ecs.php src/ +``` + +#### phpunit + +```bash +ddev composer test:php +``` diff --git a/packages/fractor-xliff/composer.json b/packages/fractor-xliff/composer.json new file mode 100644 index 00000000..ce5b0aed --- /dev/null +++ b/packages/fractor-xliff/composer.json @@ -0,0 +1,55 @@ +{ + "name": "a9f/fractor-xliff", + "description": "XLIFF extension for the File Read-Analyse-Change Tool. Allows modifying XLIFF translation files", + "license": "MIT", + "type": "fractor-extension", + "keywords": [ + "dev", + "fractor", + "upgrade", + "refactoring", + "automation", + "migration", + "xliff", + "translation" + ], + "require": { + "php": "^8.2", + "ext-dom": "*", + "ext-xml": "*", + "a9f/fractor": "^0.5.10", + "a9f/fractor-extension-installer": "^0.5.10", + "simonschaufi/pretty-xml": "^3.0.0", + "symplify/rule-doc-generator-contracts": "^11.2", + "webmozart/assert": "^1.11" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "autoload": { + "psr-4": { + "a9f\\FractorXliff\\": [ + "src/", + "rules/" + ] + } + }, + "autoload-dev": { + "psr-4": { + "a9f\\FractorXliff\\Tests\\": [ + "tests/", + "rules-tests/" + ] + } + }, + "config": { + "allow-plugins": { + "a9f/fractor-extension-installer": true + }, + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-main": "0.5-dev" + } + } +} diff --git a/packages/fractor-xliff/config/application.php b/packages/fractor-xliff/config/application.php new file mode 100644 index 00000000..07f37666 --- /dev/null +++ b/packages/fractor-xliff/config/application.php @@ -0,0 +1,38 @@ +services(); + $services->defaults() + ->autowire() + ->autoconfigure(); + + $services->load('a9f\\FractorXliff\\', __DIR__ . '/../src/'); + + $services->set('fractor.xliff_processor.indent', Indent::class) + ->factory([service(IndentFactory::class), 'create']); + + $services->set('fractor.xliff_processor.format_configuration', XliffFormatConfiguration::class) + ->factory([null, 'createFromParameterBag']); + + $services->set(XliffFileProcessor::class) + ->arg('$indent', service('fractor.xliff_processor.indent')) + ->arg('$rules', tagged_iterator('fractor.xliff_rule')) + ->arg('$xliffFormatConfiguration', service('fractor.xliff_processor.format_configuration')); + + $services->set(Formatter::class); + + $containerBuilder->registerForAutoconfiguration(XliffFractorRule::class)->addTag('fractor.xliff_rule'); +}; diff --git a/packages/fractor-xliff/docs/xliff-fractor-rules.md b/packages/fractor-xliff/docs/xliff-fractor-rules.md new file mode 100644 index 00000000..c0fcbc7c --- /dev/null +++ b/packages/fractor-xliff/docs/xliff-fractor-rules.md @@ -0,0 +1,75 @@ +# 3 Rules Overview + +## ConvertXliff1To2Fractor + +Convert XLIFF 1.2 files to XLIFF 2.0 format + +- class: [`a9f\FractorXliff\ConvertXliff1To2Fractor`](../rules/ConvertXliff1To2Fractor.php) + +```diff + +- +- +-
+- +- ++ ++ ++ ++ + Hello +- +- ++ ++ + + +``` + +
+ +## EnsureXliffHasSourceLanguageFractor + +Ensure XLIFF files have the required source-language (v1.x) or srcLang (v2.0) attribute + +- class: [`a9f\FractorXliff\EnsureXliffHasSourceLanguageFractor`](../rules/EnsureXliffHasSourceLanguageFractor.php) + +```diff + + +- ++ + + + Hello + + + + +``` + +
+ +## EnsureXliffHasTargetLanguageFractor + +Add target-language attribute to localized XLIFF files where the filename starts with a 2-letter ISO language code + +- class: [`a9f\FractorXliff\EnsureXliffHasTargetLanguageFractor`](../rules/EnsureXliffHasTargetLanguageFractor.php) + +```diff + + + +- ++ + + + Hello + Hallo + + + + +``` + +
diff --git a/packages/fractor-xliff/phpunit.xml b/packages/fractor-xliff/phpunit.xml new file mode 100644 index 00000000..bdfed53f --- /dev/null +++ b/packages/fractor-xliff/phpunit.xml @@ -0,0 +1,13 @@ + + + + + tests + + + + + ./src + + + diff --git a/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/ConvertXliff1To2FractorTest.php b/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/ConvertXliff1To2FractorTest.php new file mode 100644 index 00000000..113409b7 --- /dev/null +++ b/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/ConvertXliff1To2FractorTest.php @@ -0,0 +1,29 @@ +doTestFile($filePath); + $this->assertThatRuleIsApplied(ConvertXliff1To2Fractor::class); + } + + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixtures', '*.xlf.fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/fractor.php'; + } +} diff --git a/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/basic.xlf.fixture b/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/basic.xlf.fixture new file mode 100644 index 00000000..b9560f66 --- /dev/null +++ b/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/basic.xlf.fixture @@ -0,0 +1,24 @@ + + + +
+ + + Hello + + + + +----- + + + + + + Hello + + + + diff --git a/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/with-groups.xlf.fixture b/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/with-groups.xlf.fixture new file mode 100644 index 00000000..371272fd --- /dev/null +++ b/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/with-groups.xlf.fixture @@ -0,0 +1,35 @@ + + + + + + + Home + + + About + + + + + +----- + + + + + + + Home + + + + + About + + + + + diff --git a/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/with-notes.xlf.fixture b/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/with-notes.xlf.fixture new file mode 100644 index 00000000..4f343553 --- /dev/null +++ b/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/with-notes.xlf.fixture @@ -0,0 +1,27 @@ + + + + + + Hello + This is a greeting + + + + +----- + + + + + + This is a greeting + + + Hello + + + + diff --git a/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/with-target-and-approved.xlf.fixture b/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/with-target-and-approved.xlf.fixture new file mode 100644 index 00000000..9f59c443 --- /dev/null +++ b/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/Fixtures/with-target-and-approved.xlf.fixture @@ -0,0 +1,36 @@ + + + +
+ + + Hello + Hallo + + + World + Welt + + + + +----- + + + + + + Hello + Hallo + + + + + World + Welt + + + + diff --git a/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/config/fractor.php b/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/config/fractor.php new file mode 100644 index 00000000..75a642da --- /dev/null +++ b/packages/fractor-xliff/rules-tests/ConvertXliff1To2Fractor/config/fractor.php @@ -0,0 +1,15 @@ +withOptions([ + XliffProcessorOption::INDENT_CHARACTER => Indent::STYLE_TAB, + XliffProcessorOption::INDENT_SIZE => 1, + ]) + ->withRules([ConvertXliff1To2Fractor::class]); diff --git a/packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/EnsureXliffHasSourceLanguageFractorTest.php b/packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/EnsureXliffHasSourceLanguageFractorTest.php new file mode 100644 index 00000000..8d213d2c --- /dev/null +++ b/packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/EnsureXliffHasSourceLanguageFractorTest.php @@ -0,0 +1,29 @@ +doTestFile($filePath); + $this->assertThatRuleIsApplied(EnsureXliffHasSourceLanguageFractor::class); + } + + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixtures', '*.xlf.fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/fractor.php'; + } +} diff --git a/packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/Fixtures/missing-source-language-v1.xlf.fixture b/packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/Fixtures/missing-source-language-v1.xlf.fixture new file mode 100644 index 00000000..1a2c0731 --- /dev/null +++ b/packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/Fixtures/missing-source-language-v1.xlf.fixture @@ -0,0 +1,23 @@ + + + + + + Hello + + + + +----- + + + + + + Hello + + + + diff --git a/packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/Fixtures/missing-srclang-v2.xlf.fixture b/packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/Fixtures/missing-srclang-v2.xlf.fixture new file mode 100644 index 00000000..987536c9 --- /dev/null +++ b/packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/Fixtures/missing-srclang-v2.xlf.fixture @@ -0,0 +1,23 @@ + + + + + + Hello + + + + +----- + + + + + + Hello + + + + diff --git a/packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/config/fractor.php b/packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/config/fractor.php new file mode 100644 index 00000000..390e49bc --- /dev/null +++ b/packages/fractor-xliff/rules-tests/EnsureXliffHasSourceLanguageFractor/config/fractor.php @@ -0,0 +1,15 @@ +withOptions([ + XliffProcessorOption::INDENT_CHARACTER => Indent::STYLE_TAB, + XliffProcessorOption::INDENT_SIZE => 1, + ]) + ->withRules([EnsureXliffHasSourceLanguageFractor::class]); diff --git a/packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/EnsureXliffHasTargetLanguageFractorTest.php b/packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/EnsureXliffHasTargetLanguageFractorTest.php new file mode 100644 index 00000000..c29ed6bb --- /dev/null +++ b/packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/EnsureXliffHasTargetLanguageFractorTest.php @@ -0,0 +1,29 @@ +doTestFile($filePath); + $this->assertThatRuleIsApplied(EnsureXliffHasTargetLanguageFractor::class); + } + + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixtures', '*.xlf.fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/fractor.php'; + } +} diff --git a/packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/Fixtures/de.add-target-lang-v1.xlf.fixture b/packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/Fixtures/de.add-target-lang-v1.xlf.fixture new file mode 100644 index 00000000..71123bd1 --- /dev/null +++ b/packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/Fixtures/de.add-target-lang-v1.xlf.fixture @@ -0,0 +1,25 @@ + + + + + + Hello + Hallo + + + + +----- + + + + + + Hello + Hallo + + + + diff --git a/packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/Fixtures/de.add-trglang-v2.xlf.fixture b/packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/Fixtures/de.add-trglang-v2.xlf.fixture new file mode 100644 index 00000000..2c858437 --- /dev/null +++ b/packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/Fixtures/de.add-trglang-v2.xlf.fixture @@ -0,0 +1,25 @@ + + + + + + Hello + Hallo + + + + +----- + + + + + + Hello + Hallo + + + + diff --git a/packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/config/fractor.php b/packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/config/fractor.php new file mode 100644 index 00000000..c3d11404 --- /dev/null +++ b/packages/fractor-xliff/rules-tests/EnsureXliffHasTargetLanguageFractor/config/fractor.php @@ -0,0 +1,15 @@ +withOptions([ + XliffProcessorOption::INDENT_CHARACTER => Indent::STYLE_TAB, + XliffProcessorOption::INDENT_SIZE => 1, + ]) + ->withRules([EnsureXliffHasTargetLanguageFractor::class]); diff --git a/packages/fractor-xliff/rules/ConvertXliff1To2Fractor.php b/packages/fractor-xliff/rules/ConvertXliff1To2Fractor.php new file mode 100644 index 00000000..93323df4 --- /dev/null +++ b/packages/fractor-xliff/rules/ConvertXliff1To2Fractor.php @@ -0,0 +1,223 @@ +version === XliffVersion::V2_0) { + return null; + } + + $oldDoc = $xliffDocument->document; + $newDoc = new \DOMDocument('1.0', 'UTF-8'); + $newDoc->formatOutput = true; + + $oldRoot = $oldDoc->documentElement; + if (! $oldRoot instanceof \DOMElement) { + return null; + } + + $newRoot = $newDoc->createElementNS(self::XLIFF_2_NAMESPACE, 'xliff'); + $newRoot->setAttribute('version', '2.0'); + $newDoc->appendChild($newRoot); + + $this->convertFiles($oldRoot, $newDoc, $newRoot); + + return new XliffDocument($newDoc, XliffVersion::V2_0, $xliffDocument->file); + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition('Convert XLIFF 1.2 files to XLIFF 2.0 format', [new CodeSample( + <<<'CODE_SAMPLE' + + + +
+ + + Hello + + + + +CODE_SAMPLE + , + <<<'CODE_SAMPLE' + + + + + + Hello + + + + +CODE_SAMPLE + )]); + } + + private function convertFiles(\DOMElement $oldRoot, \DOMDocument $newDoc, \DOMElement $newRoot): void + { + foreach ($this->getChildElementsByTagName($oldRoot, 'file') as $oldFile) { + $sourceLang = $oldFile->getAttribute('source-language'); + $targetLang = $oldFile->getAttribute('target-language'); + + if ($sourceLang !== '') { + $newRoot->setAttribute('srcLang', $sourceLang); + } + + if ($targetLang !== '') { + $newRoot->setAttribute('trgLang', $targetLang); + } + + $fileId = $oldFile->getAttribute('original'); + if ($fileId === '') { + $fileId = 'f1'; + } + + $newFile = $newDoc->createElementNS(self::XLIFF_2_NAMESPACE, 'file'); + $newFile->setAttribute('id', $fileId); + $newRoot->appendChild($newFile); + + $body = $this->getFirstChildElementByTagName($oldFile, 'body'); + if ($body instanceof \DOMElement) { + $this->convertChildren($body, $newDoc, $newFile); + } + } + } + + private function convertChildren(\DOMElement $parent, \DOMDocument $newDoc, \DOMElement $newParent): void + { + foreach ($parent->childNodes as $child) { + if (! $child instanceof \DOMElement) { + continue; + } + + $localName = $child->localName ?? ''; + if ($localName === 'trans-unit') { + $this->convertTransUnit($child, $newDoc, $newParent); + } elseif ($localName === 'group') { + $this->convertGroup($child, $newDoc, $newParent); + } + } + } + + private function convertTransUnit(\DOMElement $transUnit, \DOMDocument $newDoc, \DOMElement $newParent): void + { + $unit = $newDoc->createElementNS(self::XLIFF_2_NAMESPACE, 'unit'); + $id = $transUnit->getAttribute('id'); + if ($id !== '') { + $unit->setAttribute('id', $id); + } + + $newParent->appendChild($unit); + + $this->convertNotes($transUnit, $newDoc, $unit); + + $segment = $newDoc->createElementNS(self::XLIFF_2_NAMESPACE, 'segment'); + + $approved = $transUnit->getAttribute('approved'); + if ($approved === 'yes') { + $segment->setAttribute('state', 'final'); + } elseif ($approved === 'no') { + $segment->setAttribute('state', 'translated'); + } + + $source = $this->getFirstChildElementByTagName($transUnit, 'source'); + if ($source instanceof \DOMElement) { + $newSource = $newDoc->createElementNS(self::XLIFF_2_NAMESPACE, 'source'); + $this->copyInnerContent($source, $newDoc, $newSource); + $segment->appendChild($newSource); + } + + $target = $this->getFirstChildElementByTagName($transUnit, 'target'); + if ($target instanceof \DOMElement) { + $newTarget = $newDoc->createElementNS(self::XLIFF_2_NAMESPACE, 'target'); + $this->copyInnerContent($target, $newDoc, $newTarget); + $segment->appendChild($newTarget); + } + + $unit->appendChild($segment); + } + + private function convertGroup(\DOMElement $oldGroup, \DOMDocument $newDoc, \DOMElement $newParent): void + { + $newGroup = $newDoc->createElementNS(self::XLIFF_2_NAMESPACE, 'group'); + $id = $oldGroup->getAttribute('id'); + if ($id !== '') { + $newGroup->setAttribute('id', $id); + } + + $newParent->appendChild($newGroup); + $this->convertChildren($oldGroup, $newDoc, $newGroup); + } + + private function convertNotes(\DOMElement $transUnit, \DOMDocument $newDoc, \DOMElement $unit): void + { + $noteElements = $this->getChildElementsByTagName($transUnit, 'note'); + if ($noteElements === []) { + return; + } + + $notesContainer = $newDoc->createElementNS(self::XLIFF_2_NAMESPACE, 'notes'); + foreach ($noteElements as $oldNote) { + $newNote = $newDoc->createElementNS(self::XLIFF_2_NAMESPACE, 'note'); + $this->copyInnerContent($oldNote, $newDoc, $newNote); + $notesContainer->appendChild($newNote); + } + + $unit->appendChild($notesContainer); + } + + private function copyInnerContent(\DOMElement $source, \DOMDocument $newDoc, \DOMElement $target): void + { + foreach ($source->childNodes as $child) { + $imported = $newDoc->importNode($child, true); + $target->appendChild($imported); + } + } + + /** + * @return list<\DOMElement> + */ + private function getChildElementsByTagName(\DOMElement $parent, string $tagName): array + { + $elements = []; + foreach ($parent->childNodes as $child) { + if ($child instanceof \DOMElement && ($child->localName ?? '') === $tagName) { + $elements[] = $child; + } + } + + return $elements; + } + + private function getFirstChildElementByTagName(\DOMElement $parent, string $tagName): ?\DOMElement + { + foreach ($parent->childNodes as $child) { + if ($child instanceof \DOMElement && ($child->localName ?? '') === $tagName) { + return $child; + } + } + + return null; + } +} diff --git a/packages/fractor-xliff/rules/EnsureXliffHasSourceLanguageFractor.php b/packages/fractor-xliff/rules/EnsureXliffHasSourceLanguageFractor.php new file mode 100644 index 00000000..5b35e32b --- /dev/null +++ b/packages/fractor-xliff/rules/EnsureXliffHasSourceLanguageFractor.php @@ -0,0 +1,103 @@ +version === XliffVersion::V2_0) { + return $this->ensureV2SourceLanguage($xliffDocument); + } + + return $this->ensureV1SourceLanguage($xliffDocument); + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Ensure XLIFF files have the required source-language (v1.x) or srcLang (v2.0) attribute', + [new CodeSample( + <<<'CODE_SAMPLE' + + + + + + Hello + + + + +CODE_SAMPLE + , + <<<'CODE_SAMPLE' + + + + + + Hello + + + + +CODE_SAMPLE + )] + ); + } + + private function ensureV1SourceLanguage(XliffDocument $xliffDocument): ?XliffDocument + { + $changed = false; + $rootElement = $xliffDocument->document->documentElement; + if (! $rootElement instanceof \DOMElement) { + return null; + } + + foreach ($rootElement->childNodes as $child) { + if (! $child instanceof \DOMElement) { + continue; + } + if (($child->localName ?? '') !== 'file') { + continue; + } + if ($child->getAttribute('source-language') === '') { + $child->setAttribute('source-language', self::DEFAULT_SOURCE_LANGUAGE); + $changed = true; + } + } + + return $changed ? $xliffDocument : null; + } + + private function ensureV2SourceLanguage(XliffDocument $xliffDocument): ?XliffDocument + { + $rootElement = $xliffDocument->document->documentElement; + if (! $rootElement instanceof \DOMElement) { + return null; + } + + if ($rootElement->getAttribute('srcLang') !== '') { + return null; + } + + $rootElement->setAttribute('srcLang', self::DEFAULT_SOURCE_LANGUAGE); + + return $xliffDocument; + } +} diff --git a/packages/fractor-xliff/rules/EnsureXliffHasTargetLanguageFractor.php b/packages/fractor-xliff/rules/EnsureXliffHasTargetLanguageFractor.php new file mode 100644 index 00000000..a5365739 --- /dev/null +++ b/packages/fractor-xliff/rules/EnsureXliffHasTargetLanguageFractor.php @@ -0,0 +1,121 @@ +extractLanguageFromFilename($xliffDocument->file->getFileName()); + if ($language === null) { + return null; + } + + if ($xliffDocument->version === XliffVersion::V2_0) { + return $this->addV2TargetLanguage($xliffDocument, $language); + } + + return $this->addV1TargetLanguage($xliffDocument, $language); + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Add target-language attribute to localized XLIFF files where the filename starts with a 2-letter ISO language code', + [new CodeSample( + <<<'CODE_SAMPLE' + + + + + + + Hello + Hallo + + + + +CODE_SAMPLE + , + <<<'CODE_SAMPLE' + + + + + + + Hello + Hallo + + + + +CODE_SAMPLE + )] + ); + } + + private function addV1TargetLanguage(XliffDocument $xliffDocument, string $language): ?XliffDocument + { + $changed = false; + $rootElement = $xliffDocument->document->documentElement; + if (! $rootElement instanceof \DOMElement) { + return null; + } + + foreach ($rootElement->childNodes as $child) { + if (! $child instanceof \DOMElement) { + continue; + } + if (($child->localName ?? '') !== 'file') { + continue; + } + if ($child->getAttribute('target-language') !== '') { + continue; + } + + $child->setAttribute('target-language', $language); + $changed = true; + } + + return $changed ? $xliffDocument : null; + } + + private function addV2TargetLanguage(XliffDocument $xliffDocument, string $language): ?XliffDocument + { + $rootElement = $xliffDocument->document->documentElement; + if (! $rootElement instanceof \DOMElement) { + return null; + } + + if ($rootElement->getAttribute('trgLang') !== '') { + return null; + } + + $rootElement->setAttribute('trgLang', $language); + + return $xliffDocument; + } + + private function extractLanguageFromFilename(string $filename): ?string + { + if (\preg_match('/^([a-z]{2})\./i', $filename, $matches) !== 1) { + return null; + } + + return \strtolower($matches[1]); + } +} diff --git a/packages/fractor-xliff/src/Configuration/XliffProcessorOption.php b/packages/fractor-xliff/src/Configuration/XliffProcessorOption.php new file mode 100644 index 00000000..6e7c3f28 --- /dev/null +++ b/packages/fractor-xliff/src/Configuration/XliffProcessorOption.php @@ -0,0 +1,14 @@ +preserveWhiteSpace = false; + $document->formatOutput = true; + + return $document; + } +} diff --git a/packages/fractor-xliff/src/IndentFactory.php b/packages/fractor-xliff/src/IndentFactory.php new file mode 100644 index 00000000..e6848175 --- /dev/null +++ b/packages/fractor-xliff/src/IndentFactory.php @@ -0,0 +1,29 @@ +parameterBag->has(XliffProcessorOption::INDENT_SIZE) ? $this->parameterBag->get( + XliffProcessorOption::INDENT_SIZE + ) : 4; + $style = $this->parameterBag->has(XliffProcessorOption::INDENT_CHARACTER) ? $this->parameterBag->get( + XliffProcessorOption::INDENT_CHARACTER + ) : Indent::STYLE_SPACE; + + return Indent::fromSizeAndStyle($size, $style); + } +} diff --git a/packages/fractor-xliff/src/ValueObject/XliffDocument.php b/packages/fractor-xliff/src/ValueObject/XliffDocument.php new file mode 100644 index 00000000..fee76883 --- /dev/null +++ b/packages/fractor-xliff/src/ValueObject/XliffDocument.php @@ -0,0 +1,17 @@ + $allowedFileExtensions + */ + public function __construct( + public array $allowedFileExtensions + ) { + } + + public static function createFromParameterBag(ParameterBagInterface $parameterBag): self + { + /** @var list $allowedFileExtensions */ + $allowedFileExtensions = $parameterBag->has(XliffProcessorOption::ALLOWED_FILE_EXTENSIONS) + ? $parameterBag->get(XliffProcessorOption::ALLOWED_FILE_EXTENSIONS) + : ['xlf', 'xliff']; + + return new self($allowedFileExtensions); + } +} diff --git a/packages/fractor-xliff/src/ValueObject/XliffVersion.php b/packages/fractor-xliff/src/ValueObject/XliffVersion.php new file mode 100644 index 00000000..daf72138 --- /dev/null +++ b/packages/fractor-xliff/src/ValueObject/XliffVersion.php @@ -0,0 +1,51 @@ + '1.1', + 'urn:oasis:names:tc:xliff:document:1.2' => '1.2', + 'urn:oasis:names:tc:xliff:document:2.0' => '2.0', + ]; + + public static function fromDomDocument(\DOMDocument $document): self + { + $rootElement = $document->documentElement; + Assert::notNull($rootElement, 'XLIFF document has no root element'); + + $localName = $rootElement->localName ?? ''; + Assert::same( + \strtolower($localName), + 'xliff', + \sprintf('Expected root element "xliff", got "%s"', $localName) + ); + + $version = $rootElement->getAttribute('version'); + if ($version !== '') { + $matched = self::tryFrom($version); + if ($matched !== null) { + return $matched; + } + } + + $namespaceUri = $rootElement->namespaceURI ?? ''; + if (isset(self::NAMESPACE_MAP[$namespaceUri])) { + return self::from(self::NAMESPACE_MAP[$namespaceUri]); + } + + throw new \InvalidArgumentException( + 'Could not determine XLIFF version: no version attribute or known namespace found' + ); + } +} diff --git a/packages/fractor-xliff/src/XliffFileProcessor.php b/packages/fractor-xliff/src/XliffFileProcessor.php new file mode 100644 index 00000000..d1144022 --- /dev/null +++ b/packages/fractor-xliff/src/XliffFileProcessor.php @@ -0,0 +1,112 @@ + + */ +final readonly class XliffFileProcessor implements FileProcessor +{ + /** + * @param iterable $rules + */ + public function __construct( + private DomDocumentFactory $domDocumentFactory, + private Formatter $formatter, + private iterable $rules, + private Indent $indent, + private ChangedFilesDetector $changedFilesDetector, + private XliffFormatConfiguration $xliffFormatConfiguration + ) { + } + + public function canHandle(File $file): bool + { + return in_array($file->getFileExtension(), $this->allowedFileExtensions(), true); + } + + /** + * @param iterable $appliedRules + */ + public function handle(File $file, iterable $appliedRules): void + { + $document = $this->domDocumentFactory->create(); + $originalXml = $file->getOriginalContent(); + $document->loadXML($originalXml); + + // Normalize baseline formatting for clean diffs + $oldXml = $this->saveXml($document); + $oldXml = $this->formatXml($oldXml); + $file->changeOriginalContent($oldXml); + + $version = XliffVersion::fromDomDocument($document); + $xliffDocument = new XliffDocument($document, $version, $file); + + foreach ($appliedRules as $rule) { + $result = $rule->refactor($xliffDocument); + if ($result !== null) { + $xliffDocument = $result; + $file->addAppliedRule(AppliedRule::fromRule($rule)); + } + } + + $newXml = $this->saveXml($xliffDocument->document); + $newXml = $this->formatXml($newXml); + + // Compare against raw original to detect formatting changes too + if ($newXml === $originalXml) { + return; + } + + $file->changeFileContent($newXml); + if (! $file->hasChanged()) { + $this->changedFilesDetector->addCachableFile($file->getFilePath()); + } + } + + /** + * @return list + */ + public function allowedFileExtensions(): array + { + return $this->xliffFormatConfiguration->allowedFileExtensions; + } + + public function getAllRules(): iterable + { + return $this->rules; + } + + private function saveXml(\DOMDocument $document): string + { + $xml = $document->saveXML(); + if ($xml === false) { + throw new ShouldNotHappenException('Could not save XLIFF document'); + } + + return $xml; + } + + private function formatXml(string $xml): string + { + $indentCharacter = $this->indent->isSpace() ? Indent::CHARACTERS[Indent::STYLE_SPACE] : Indent::CHARACTERS[Indent::STYLE_TAB]; + $this->formatter->setIndentCharacter($indentCharacter); + $this->formatter->setIndentSize($this->indent->length()); + + return rtrim($this->formatter->format($xml)) . "\n"; + } +} diff --git a/packages/fractor-xliff/tests/Fixtures/DummyXliffFractorRule.php b/packages/fractor-xliff/tests/Fixtures/DummyXliffFractorRule.php new file mode 100644 index 00000000..bd79f7e1 --- /dev/null +++ b/packages/fractor-xliff/tests/Fixtures/DummyXliffFractorRule.php @@ -0,0 +1,33 @@ +document->getElementsByTagName('source'); + + foreach ($sourceElements as $sourceElement) { + if ($sourceElement->textContent === 'Hello') { + $sourceElement->textContent = 'Hello World'; + $changed = true; + } + } + + return $changed ? $xliffDocument : null; + } + + public function getRuleDefinition(): RuleDefinition + { + throw new BadMethodCallException('Not implemented yet'); + } +} diff --git a/packages/fractor-xliff/tests/XliffFileProcessor/Fixtures/xliff12.xlf.fixture b/packages/fractor-xliff/tests/XliffFileProcessor/Fixtures/xliff12.xlf.fixture new file mode 100644 index 00000000..b683725a --- /dev/null +++ b/packages/fractor-xliff/tests/XliffFileProcessor/Fixtures/xliff12.xlf.fixture @@ -0,0 +1,23 @@ + + + + + + Hello + + + + +----- + + + + + + Hello World + + + + diff --git a/packages/fractor-xliff/tests/XliffFileProcessor/XliffFileProcessorTest.php b/packages/fractor-xliff/tests/XliffFileProcessor/XliffFileProcessorTest.php new file mode 100644 index 00000000..ba4f01d7 --- /dev/null +++ b/packages/fractor-xliff/tests/XliffFileProcessor/XliffFileProcessorTest.php @@ -0,0 +1,29 @@ +doTestFile($filePath); + $this->assertThatRuleIsApplied(DummyXliffFractorRule::class); + } + + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixtures', '*.xlf.fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/fractor.php'; + } +} diff --git a/packages/fractor-xliff/tests/XliffFileProcessor/config/fractor.php b/packages/fractor-xliff/tests/XliffFileProcessor/config/fractor.php new file mode 100644 index 00000000..439dd186 --- /dev/null +++ b/packages/fractor-xliff/tests/XliffFileProcessor/config/fractor.php @@ -0,0 +1,15 @@ +withOptions([ + XliffProcessorOption::INDENT_CHARACTER => Indent::STYLE_TAB, + XliffProcessorOption::INDENT_SIZE => 1, + ]) + ->withRules([DummyXliffFractorRule::class]); diff --git a/packages/typo3-fractor/composer.json b/packages/typo3-fractor/composer.json index 25fb6d01..7099251a 100644 --- a/packages/typo3-fractor/composer.json +++ b/packages/typo3-fractor/composer.json @@ -27,6 +27,7 @@ "a9f/fractor-fluid": "^0.5.10", "a9f/fractor-htaccess": "^0.5.10", "a9f/fractor-typoscript": "^0.5.10", + "a9f/fractor-xliff": "^0.5.10", "a9f/fractor-xml": "^0.5.10", "a9f/fractor-yaml": "^0.5.10", "symplify/rule-doc-generator-contracts": "^11.2" From 36556ecd3bc9130cc612c883d247adcbbf52533e Mon Sep 17 00:00:00 2001 From: cs Date: Tue, 31 Mar 2026 18:42:45 +0200 Subject: [PATCH 2/3] [DOCS] Extend the README by mentioning the new XliffFileProcessor and hot to skip it --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 77cbf3b4..d5b03780 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ If skipping rules, files, or folders is not sufficient, you can also skip entire use a9f\FractorFluid\FluidFileProcessor; use a9f\FractorHtaccess\HtaccessFileProcessor; use a9f\FractorTypoScript\TypoScriptFileProcessor; +use a9f\FractorYaml\XliffFileProcessor; use a9f\FractorXml\XmlFileProcessor; use a9f\FractorYaml\YamlFileProcessor; @@ -137,6 +138,7 @@ return FractorConfiguration::configure() FluidFileProcessor::class, HtaccessFileProcessor::class, TypoScriptFileProcessor::class, + XliffFileProcessor::class, XmlFileProcessor::class, YamlFileProcessor::class, ]); From 2e9e662f30de10d0d81be946094cb3d46896a296 Mon Sep 17 00:00:00 2001 From: cs Date: Wed, 1 Apr 2026 09:03:52 +0200 Subject: [PATCH 3/3] [TASK] Add test case for skipping the new XliffFileProcessor --- .../Application/ProcessorSkipper/ProcessorSkipperTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/fractor/tests/Application/ProcessorSkipper/ProcessorSkipperTest.php b/packages/fractor/tests/Application/ProcessorSkipper/ProcessorSkipperTest.php index 643680ee..e5b9cf2b 100644 --- a/packages/fractor/tests/Application/ProcessorSkipper/ProcessorSkipperTest.php +++ b/packages/fractor/tests/Application/ProcessorSkipper/ProcessorSkipperTest.php @@ -9,6 +9,7 @@ use a9f\FractorFluid\FluidFileProcessor; use a9f\FractorHtaccess\HtaccessFileProcessor; use a9f\FractorTypoScript\TypoScriptFileProcessor; +use a9f\FractorXliff\XliffFileProcessor; use a9f\FractorXml\XmlFileProcessor; use a9f\FractorYaml\YamlFileProcessor; use PHPUnit\Framework\Attributes\DataProvider; @@ -78,6 +79,7 @@ public function multipleProcessorsCanBeSkippedSimultaneously(): void self::assertTrue($subject->shouldSkip(FluidFileProcessor::class)); self::assertFalse($subject->shouldSkip(HtaccessFileProcessor::class)); self::assertFalse($subject->shouldSkip(TypoScriptFileProcessor::class)); + self::assertFalse($subject->shouldSkip(XliffFileProcessor::class)); self::assertTrue($subject->shouldSkip(XmlFileProcessor::class)); self::assertTrue($subject->shouldSkip(YamlFileProcessor::class)); } @@ -102,6 +104,7 @@ private static function allProcessorClasses(): array FluidFileProcessor::class, HtaccessFileProcessor::class, TypoScriptFileProcessor::class, + XliffFileProcessor::class, XmlFileProcessor::class, YamlFileProcessor::class, ];