Last active
June 14, 2024 20:21
-
-
Save Kocal/dd3f99475521dd6525f08b2050c660fe to your computer and use it in GitHub Desktop.
One-shot command that helped me to migrate non-ICU translations to ICU translations, and merge them with original ICU translations, inside a Symfony project. You must copy your `translations` folder to `translations.original` before running the command.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Command\Tools; | |
use App\Core\Entity\Website\Locale; | |
use Psr\Log\LoggerInterface; | |
use Symfony\Component\Console\Attribute\AsCommand; | |
use Symfony\Component\Console\Command\Command; | |
use Symfony\Component\Console\Input\InputInterface; | |
use Symfony\Component\Console\Output\OutputInterface; | |
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; | |
use Symfony\Component\Translation\MessageCatalogue; | |
use Symfony\Component\Translation\MessageCatalogueInterface; | |
use Symfony\Component\Translation\Reader\TranslationReaderInterface; | |
use Symfony\Component\Translation\Writer\TranslationWriterInterface; | |
#[AsCommand( | |
name: 'app:tools:merge-non-icu-and-icu-translations', | |
description: 'Merge non-ICU and ICU translations', | |
)] | |
class MergeNonICUAndICUTranslations extends Command | |
{ | |
private const ICU_COMPATIBILITY_YES = 'yes'; | |
private const ICU_COMPATIBILITY_NO = 'no'; | |
private const ICU_COMPATIBILITY_TO_MIGRATE = 'to_migrate'; | |
/** | |
* @var list<Locale::*> | |
*/ | |
private array $locales = []; | |
public function __construct( | |
private TranslationReaderInterface $translationReader, | |
private TranslationWriterInterface $translationWriter, | |
ParameterBagInterface $parameterBag, | |
private LoggerInterface $logger | |
) { | |
$this->locales = $parameterBag->get('locales'); | |
parent::__construct(); | |
} | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
foreach ($this->locales as $locale) { | |
$catalogue = new MessageCatalogue($locale); | |
$this->translationReader->read(__DIR__.'/../../../translations.original', $catalogue); | |
$catalogue = $this->reorganizeMessages($catalogue, $locale); | |
$this->translationWriter->write($catalogue, 'xlf', [ | |
'path' => __DIR__.'/../../../translations', | |
]); | |
} | |
return self::SUCCESS; | |
} | |
private function reorganizeMessages(MessageCatalogue $oldCatalogue, string $locale): MessageCatalogue | |
{ | |
$catalogue = new MessageCatalogue($locale); | |
foreach ($oldCatalogue->getDomains() as $domain) { | |
$messagesIcu = $oldCatalogue->all($domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX); | |
$messages = []; | |
foreach ($oldCatalogue->all($domain) as $id => $message) { | |
if (isset($messagesIcu[$id])) { | |
continue; | |
} | |
$messages[$id] = $message; | |
} | |
foreach ($messages as $key => $message) { | |
$icuCompatibility = $this->getIcuCompatibility($message); | |
if (self::ICU_COMPATIBILITY_NO === $icuCompatibility) { | |
$this->logger->info(sprintf('Message "%s" in domain "%s" is not compatible with ICU.', $key, $domain)); | |
continue; | |
} elseif (self::ICU_COMPATIBILITY_TO_MIGRATE === $icuCompatibility) { | |
$message = $this->migrateToICU($message); | |
$icuCompatibility = $this->getICUCompatibility($message); | |
if (self::ICU_COMPATIBILITY_YES !== $icuCompatibility) { | |
$this->logger->error(sprintf('Unable to migrate message "%s" in domain "%s" to ICU format.', $key, $domain)); | |
} | |
} | |
$messagesIcu[$key] = $message; | |
unset($messages[$key]); | |
} | |
$catalogue->add($messages, $domain); | |
$catalogue->add($messagesIcu, $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX); | |
} | |
return $catalogue; | |
} | |
/** | |
* @return self::ICU_COMPATIBILITY_* | |
*/ | |
private function getICUCompatibility(string $message): string | |
{ | |
if (false === $placeholderMatches = preg_match('/%\w+%/', $message)) { | |
throw new \Exception(sprintf('Unable to parse message "%s", reason "%s".', $message, preg_last_error_msg())); | |
} | |
if (false === $choiceMatches = preg_match('/\{\d+\}/', $message)) { | |
throw new \Exception(sprintf('Unable to parse message "%s", reason "%s".', $message, preg_last_error_msg())); | |
} | |
return match (true) { | |
0 === $placeholderMatches + $choiceMatches => self::ICU_COMPATIBILITY_YES, | |
$placeholderMatches + $choiceMatches > 0 => self::ICU_COMPATIBILITY_TO_MIGRATE, | |
default => self::ICU_COMPATIBILITY_NO, | |
}; | |
} | |
private function migrateToICU(string $message): string | |
{ | |
// dumb approach | |
return preg_replace('/%(\w+)%/', '{\1}', $message) ?? throw new \Exception(sprintf( | |
'Unable to replace in message "%s", reason "%s".', | |
$message, | |
preg_last_error_msg() | |
)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment