From bfadf2d660d0c1be8a5bca36de716c059cbcaf95 Mon Sep 17 00:00:00 2001 From: Ulrich Mathes Date: Mon, 5 Jan 2026 15:32:00 +0100 Subject: [PATCH] [TASK] implement Symfony Translation Loader concept solves: #15 --- .github/workflows/release.yml | 22 +++-- .github/workflows/tests.yml | 2 +- .../Localization/CsvLocalizationParser.php | 57 ------------ Classes/Translation/Loader/CsvLoader.php | 92 +++++++++++++++++++ composer.json | 2 +- ext_emconf.php | 2 +- ext_localconf.php | 28 +++--- 7 files changed, 123 insertions(+), 82 deletions(-) delete mode 100644 Classes/Localization/CsvLocalizationParser.php create mode 100644 Classes/Translation/Loader/CsvLoader.php diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7f1483f..621d201 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,15 +1,17 @@ name: release on: - push: - tags: - - "**" + push: + tags: + - "**" jobs: - terUpload: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: tomasnorre/typo3-upload-ter@v2.0.7 - with: - api-token: ${{ secrets.TYPO3_API_TOKEN }} + terUpload: + runs-on: ubuntu-latest + steps: + - + uses: actions/checkout@v4 + - + uses: tomasnorre/typo3-upload-ter@v2.0.7 + with: + api-token: ${{ secrets.TYPO3_API_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 77973ea..cfcdcb1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.3 extensions: intl, mbstring, pdo_sqlite - diff --git a/Classes/Localization/CsvLocalizationParser.php b/Classes/Localization/CsvLocalizationParser.php deleted file mode 100644 index 3f66782..0000000 --- a/Classes/Localization/CsvLocalizationParser.php +++ /dev/null @@ -1,57 +0,0 @@ - [ - 'target' => $line[$i] - ] - ]; - if (isset($LOCAL_LANG['default'][$identifier][0]['target'])) { - $LOCAL_LANG[$header[$i]][$identifier][0]['source'] - = $LOCAL_LANG['default'][$identifier][0]['target']; - } - } - } - - fclose($handle); - unset($LOCAL_LANG['identifier']); - unset($LOCAL_LANG['description']); - return $LOCAL_LANG; - } -} diff --git a/Classes/Translation/Loader/CsvLoader.php b/Classes/Translation/Loader/CsvLoader.php new file mode 100644 index 0000000..21bd86f --- /dev/null +++ b/Classes/Translation/Loader/CsvLoader.php @@ -0,0 +1,92 @@ + 'translation.loader', 'format' => 'csv']])] +class CsvLoader implements LoaderInterface +{ + private ?Locales $locales = null; + + public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue + { + if (!file_exists($resource)) { + throw new NotFoundResourceException(\sprintf('File "%s" not found.', $resource), 1612282091); + } + + try { + $file = new \SplFileObject($resource, 'r'); + $file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY); + } catch (\RuntimeException $e) { + throw new InvalidResourceException(\sprintf('File "%s" could not be opened.', $resource), 1767019766, $e); + } + + if (!$file->valid()) { + throw new InvalidResourceException(\sprintf('File "%s" has invalid CSV content.', $resource), 1767623886); + } + + $csvHeader = $file->current(); + if ($csvHeader === false) { + return new MessageCatalogue($locale); + } + + $this->locales = GeneralUtility::makeInstance(Locales::class); + $localeColumnIndex = $this->searchLocaleColumnIndex($csvHeader, $locale); + if ($localeColumnIndex === false) { + return new MessageCatalogue($locale); + } + + foreach ($file as $lineNumber => $translation) { + if ($lineNumber === 0 || empty($translation[0])) { + continue; + } + $identifier = $translation[0]; + if (isset($translation[$localeColumnIndex]) && $translation[$localeColumnIndex] !== '') { + $messages[$identifier] = $translation[$localeColumnIndex]; + } + } + + return new MessageCatalogue($locale, [$domain => $messages]); + } + + private function searchLocaleColumnIndex(array $csvHeader, string $locale): int|false + { + $localeChain = $this->buildLocaleFallbackChain($locale, $this->locales); + + foreach ($localeChain as $currentLocale) { + $index = array_search($currentLocale, $csvHeader, true); + if ($index !== false) { + return $index; + } + } + + return false; + } + + private function buildLocaleFallbackChain(string $locale, Locales $locales): array + { + $chain = [$locale]; + + // e.g. de-DE -> de + if (str_contains($locale, '-')) { + [$baseLanguage] = explode('-', $locale, 2); + $chain[] = $baseLanguage; + } elseif (str_contains($locale, '_')) { + [$baseLanguage] = explode('_', $locale, 2); + $chain[] = $baseLanguage; + } + + $chain = array_merge($chain, $this->locales->getLocaleDependencies($locale)); + + $chain[] = 'default'; + + return array_unique($chain); + } +} diff --git a/composer.json b/composer.json index 51ee5f8..6c386af 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "issues": "https://github.com/sitegeist/csv-labels/issues" }, "require": { - "typo3/cms-core": "^13.1 || ^12.2 || ^11.5 || ^10.4 || ^9.5" + "typo3/cms-core": "^14" }, "require-dev": { "squizlabs/php_codesniffer": "^3.0", diff --git a/ext_emconf.php b/ext_emconf.php index c51418a..9f57172 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -12,7 +12,7 @@ 'version' => '', 'constraints' => [ 'depends' => [ - 'typo3' => '9.5.0-13.9.99', + 'typo3' => '14.0.0-14.9.99', 'php' => '7.4.0-8.9.99' ], 'conflicts' => [ diff --git a/ext_localconf.php b/ext_localconf.php index d57b4a6..c404c70 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -1,14 +1,18 @@