diff --git a/composer.json b/composer.json index f7c340c..cdbbba5 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "symfony/framework-bundle": "^7.0|^8.0", "symfony/yaml": "^7.0|^8.0", "symfony/translation": "^7.0|^8.0", + "ext-intl": "*", "assoconnect/php-quality-config": "^2.2", "phpstan/phpstan-symfony": "^2" }, diff --git a/tests/TranslationCatalogueTest.php b/tests/TranslationCatalogueTest.php index fa4c1b8..378a4de 100644 --- a/tests/TranslationCatalogueTest.php +++ b/tests/TranslationCatalogueTest.php @@ -4,6 +4,8 @@ namespace AssoConnect\SmtpToolbox\Tests; +use IntlException; +use MessageFormatter; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Yaml\Yaml; @@ -16,10 +18,10 @@ class TranslationCatalogueTest extends TestCase { private const string TRANSLATIONS_DIR = __DIR__ . '/../translations'; private const string DOMAIN = 'assoconnect_smtp_toolbox+intl-icu'; - private const string REFERENCE_LOCALE = 'en_US'; + private const string REFERENCE_LOCALE = 'en'; /** Every locale the catalogues are expected to cover. */ - private const array EXPECTED_LOCALES = ['en_US', 'fr_FR', 'es_ES']; + private const array EXPECTED_LOCALES = ['en', 'fr', 'es']; /** @return iterable */ public static function provideLocales(): iterable @@ -74,6 +76,44 @@ public function testEveryMessageIsTranslated(string $locale): void } } + /** + * The domain carries the +intl-icu suffix, so every message is an ICU pattern. `{{ name }}` is a + * syntax error there, and the translator throws on render rather than at load time. + */ + #[DataProvider('provideLocales')] + public function testEveryMessageIsAValidIcuPattern(string $locale): void + { + $invalid = []; + + foreach ($this->messages($locale) as $key => $message) { + if ('' === trim($message)) { + continue; + } + + try { + new MessageFormatter($locale, $message); + } catch (IntlException $exception) { + $invalid[] = sprintf('%s (%s)', $key, $exception->getMessage()); + } + } + + self::assertSame([], $invalid, sprintf('Invalid ICU pattern(s) in the %s catalogue.', $locale)); + } + + #[DataProvider('provideLocales')] + public function testEveryMessageCarriesTheReferencePlaceholders(string $locale): void + { + $messages = $this->messages($locale); + + foreach ($this->messages(self::REFERENCE_LOCALE) as $key => $reference) { + self::assertSame( + self::placeholders($reference), + self::placeholders($messages[$key] ?? ''), + sprintf('Message "%s" does not carry the same placeholders in %s.', $key, $locale) + ); + } + } + private static function cataloguePath(string $locale): string { return sprintf('%s/%s.%s.yml', self::TRANSLATIONS_DIR, self::DOMAIN, $locale); @@ -113,4 +153,14 @@ private static function flatten(array $catalogue, string $prefix = ''): array return $messages; } + + /** @return list */ + private static function placeholders(string $message): array + { + preg_match_all('/\{(\w+)\}/', $message, $matches); + $placeholders = array_unique($matches[1]); + sort($placeholders); + + return $placeholders; + } } diff --git a/translations/assoconnect_smtp_toolbox+intl-icu.en_US.yml b/translations/assoconnect_smtp_toolbox+intl-icu.en.yml similarity index 73% rename from translations/assoconnect_smtp_toolbox+intl-icu.en_US.yml rename to translations/assoconnect_smtp_toolbox+intl-icu.en.yml index f8e9778..ddf9b92 100644 --- a/translations/assoconnect_smtp_toolbox+intl-icu.en_US.yml +++ b/translations/assoconnect_smtp_toolbox+intl-icu.en.yml @@ -11,8 +11,8 @@ bounce: none: "" review: "Please check the email address for typo." invalid_address: - no_at_symbol: "The email address {{ email }} is missing the @ symbol" - no_mx_servers: "No email servers are set up for {{ domain }}" + no_at_symbol: "The email address {email} is missing the @ symbol" + no_mx_servers: "No email servers are set up for {domain}" unknown_user: > - The email address {{ email }} doesn't exist at {{ domain }} : - {{ smtpResponse }} + The email address {email} doesn't exist at {domain}: + {smtpResponse} diff --git a/translations/assoconnect_smtp_toolbox+intl-icu.es_ES.yml b/translations/assoconnect_smtp_toolbox+intl-icu.es.yml similarity index 82% rename from translations/assoconnect_smtp_toolbox+intl-icu.es_ES.yml rename to translations/assoconnect_smtp_toolbox+intl-icu.es.yml index 5d5f1e6..ae2fad4 100644 --- a/translations/assoconnect_smtp_toolbox+intl-icu.es_ES.yml +++ b/translations/assoconnect_smtp_toolbox+intl-icu.es.yml @@ -11,8 +11,8 @@ bounce: none: "" review: "Compruebe si la dirección de correo electrónico contiene alguna errata." invalid_address: - no_at_symbol: "A la dirección {{ email }} le falta el símbolo @" - no_mx_servers: "No hay ningún servidor de correo configurado para {{ domain }}" + no_at_symbol: "A la dirección {email} le falta el símbolo @" + no_mx_servers: "No hay ningún servidor de correo configurado para {domain}" unknown_user: > - La dirección {{ email }} no existe en {{ domain }} : - {{ smtpResponse }} + La dirección {email} no existe en {domain}: + {smtpResponse} diff --git a/translations/assoconnect_smtp_toolbox+intl-icu.fr_FR.yml b/translations/assoconnect_smtp_toolbox+intl-icu.fr.yml similarity index 81% rename from translations/assoconnect_smtp_toolbox+intl-icu.fr_FR.yml rename to translations/assoconnect_smtp_toolbox+intl-icu.fr.yml index b14b49c..ac91a42 100644 --- a/translations/assoconnect_smtp_toolbox+intl-icu.fr_FR.yml +++ b/translations/assoconnect_smtp_toolbox+intl-icu.fr.yml @@ -11,8 +11,8 @@ bounce: none: "" review: "Vérifiez l'adresse enregistrée : elle contient peut-être une faute de frappe." invalid_address: - no_at_symbol: "L'adresse {{ email }} ne contient pas le signe @" - no_mx_servers: "Aucun serveur email n'est configuré pour le domain {{ domain }}" + no_at_symbol: "L'adresse {email} ne contient pas le signe @" + no_mx_servers: "Aucun serveur email n'est configuré pour le domain {domain}" unknown_user: > - L'adresse {{ email }} n'existe pas chez {{ domain }} : - {{ smtpResponse }} + L'adresse {email} n'existe pas chez {domain} : + {smtpResponse}