parser = new IcuParser(); $this->validator = new MessageValidator(new PlaceholderExtractor()); } public function testIdenticalPlaceholdersPass(): void { $issues = $this->validate('Bonjour {prenom}, vous avez {solde} points', 'Hola {prenom}, tienes {solde} puntos'); self::assertSame([], $issues); } /** * L'ordre des variables change souvent d'une langue à l'autre. Seule leur * PRÉSENCE compte — exiger le même ordre rendrait l'outil inutilisable. */ public function testReorderedPlaceholdersPass(): void { $issues = $this->validate('{prenom} a invité {invite}', '{invite} wurde von {prenom} eingeladen'); self::assertSame([], $issues); } public function testMissingPlaceholderIsBlocking(): void { $issues = $this->validate('Bonjour {prenom}', 'Hola'); self::assertCount(1, $issues); self::assertSame('placeholder.missing', $issues[0]->code); self::assertTrue($issues[0]->isBlocking()); self::assertStringContainsString('prenom', $issues[0]->message); } /** * Le cas le plus dangereux : la traduction référence une variable que * l'application ne fournira jamais. */ public function testUnknownPlaceholderIsBlocking(): void { $issues = $this->validate('Bonjour {prenom}', 'Hola {nombre}'); $codes = array_map(static fn (ValidationIssue $i): string => $i->code, $issues); self::assertContains('placeholder.unknown', $codes); self::assertContains('placeholder.missing', $codes); foreach ($issues as $issue) { self::assertTrue($issue->isBlocking()); } } /** * Le message d'erreur doit lister les variables disponibles : un traducteur * qui lit « {nombre} n'existe pas » sans savoir quoi mettre à la place est * bloqué. */ public function testUnknownPlaceholderMessageListsAvailableOnes(): void { $issues = $this->validate('Bonjour {prenom}', 'Hola {nombre} {prenom}'); $unknown = array_values(array_filter($issues, static fn (ValidationIssue $i): bool => 'placeholder.unknown' === $i->code)); self::assertCount(1, $unknown); self::assertStringContainsString('{prenom}', $unknown[0]->message); } public function testPluralVariableCountsAsPlaceholder(): void { $issues = $this->validate( '{count, plural, one {# séance} other {# séances}}', '{count, plural, one {# sesión} many {# sesiones} other {# sesiones}}', ['one', 'many', 'other'], ); self::assertSame([], $issues); } /** * L'arabe exige six formes. En livrer trois produit des phrases fausses pour * la majorité des valeurs — d'où le caractère bloquant. */ public function testMissingPluralCategoryIsBlocking(): void { $issues = $this->validate( '{count, plural, one {# session} other {# sessions}}', '{count, plural, one {جلسة} other {# جلسة}}', self::ARABIC, ); $missing = array_values(array_filter($issues, static fn (ValidationIssue $i): bool => 'plural.missing_category' === $i->code)); self::assertCount(4, $missing, 'zero, two, few et many manquent.'); foreach ($missing as $issue) { self::assertTrue($issue->isBlocking()); } } public function testUnusedPluralCategoryIsOnlyAWarning(): void { $issues = $this->validate( '{count, plural, one {# séance} other {# séances}}', '{count, plural, one {# session} two {# sessions} other {# sessions}}', ['one', 'other'], ); self::assertCount(1, $issues); self::assertSame('plural.unused_category', $issues[0]->code); self::assertFalse($issues[0]->isBlocking()); } public function testLengthOverrunIsOnlyAWarning(): void { $issues = $this->validate('Enregistrer', 'Guardar los cambios del formulario', self::FRENCH, 20); self::assertCount(1, $issues); self::assertSame('length.exceeded', $issues[0]->code); self::assertFalse($issues[0]->isBlocking(), 'Un dépassement tronque un libellé, il ne casse pas l\'application.'); } /** * Les branches d'un pluriel s'excluent mutuellement : la longueur du message * est celle de la plus longue, pas leur somme. Additionner ferait crier au * dépassement sur tous les messages pluriels. */ public function testPluralLengthUsesLongestBranchNotSum(): void { $issues = $this->validate( '{count, plural, one {# article} other {# articles}}', '{count, plural, one {# artículo} other {# artículos}}', ['one', 'other'], 15, ); self::assertSame([], $issues, 'La somme des branches dépasserait 15, la plus longue non.'); } public function testEmptyTranslationIsNotValidated(): void { $issues = $this->validate('Bonjour {prenom}', ''); self::assertSame([], $issues, 'Une traduction vide est « à faire », pas « en erreur ».'); } /** * @param list $categories * * @return list */ private function validate(string $source, string $target, array $categories = self::FRENCH, ?int $maxLength = null): array { return $this->validator->validate( $this->parser->parse($source), $this->parser->parse($target), $categories, $maxLength, ); } }