Skip to content

Instantly share code, notes, and snippets.

@Kocal
Last active June 14, 2024 20:21
Show Gist options
  • Select an option

  • Save Kocal/dd3f99475521dd6525f08b2050c660fe to your computer and use it in GitHub Desktop.

Select an option

Save Kocal/dd3f99475521dd6525f08b2050c660fe to your computer and use it in GitHub Desktop.

Revisions

  1. Kocal revised this gist Jun 14, 2024. No changes.
  2. Kocal revised this gist Jun 14, 2024. No changes.
  3. Kocal created this gist Mar 24, 2023.
    124 changes: 124 additions & 0 deletions MergeNonICUAndICUTranslations.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,124 @@
    <?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()
    ));
    }
    }