From b226d9c73b8eb336ea44e64ad07c6991b13f838e Mon Sep 17 00:00:00 2001 From: Florian Guimier Date: Tue, 25 Aug 2026 16:41:43 +0200 Subject: [PATCH] feat(translations): add the Spanish catalogue The bundle shipped English and French only, so a Spanish-speaking user read the English message. The new catalogue mirrors the two existing ones, unit for unit. A test now pins the catalogues against each other, so a missing locale or an untranslated message fails the suite. Nothing failed before: an absent translation silently falls back to the English default, which is exactly why the gap went unnoticed. Refs IT9PE1-30149 Co-Authored-By: Claude Opus 5 (1M context) --- tests/TranslationCatalogueTest.php | 92 ++++++++++++++++++++++++++++++ translations/validators.es.xlf | 67 ++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 tests/TranslationCatalogueTest.php create mode 100644 translations/validators.es.xlf diff --git a/tests/TranslationCatalogueTest.php b/tests/TranslationCatalogueTest.php new file mode 100644 index 0000000..b3442f3 --- /dev/null +++ b/tests/TranslationCatalogueTest.php @@ -0,0 +1,92 @@ + */ + public static function provideLocales(): iterable + { + foreach (self::EXPECTED_LOCALES as $locale) { + yield $locale => ['locale' => $locale]; + } + } + + #[DataProvider('provideLocales')] + public function testCatalogueExists(string $locale): void + { + self::assertFileExists(self::catalogPath($locale)); + } + + #[DataProvider('provideLocales')] + public function testCatalogueCoversEveryReferenceMessage(string $locale): void + { + self::assertSame( + $this->sources(self::REFERENCE_LOCALE), + $this->sources($locale), + sprintf('The %s catalogue does not cover the same messages as the %s one.', $locale, self::REFERENCE_LOCALE) + ); + } + + #[DataProvider('provideLocales')] + public function testEveryMessageIsTranslated(string $locale): void + { + foreach ($this->units($locale) as $unit) { + self::assertNotSame( + '', + trim((string) $unit->children()->target), + sprintf('Message "%s" has no %s translation.', $unit->children()->source, $locale) + ); + } + } + + private static function catalogPath(string $locale): string + { + return sprintf('%s/validators.%s.xlf', self::TRANSLATIONS_DIR, $locale); + } + + /** @return list */ + private function sources(string $locale): array + { + $sources = []; + foreach ($this->units($locale) as $unit) { + $sources[] = (string) $unit->children()->source; + } + sort($sources); + + return $sources; + } + + /** @return list */ + private function units(string $locale): array + { + $catalog = simplexml_load_file(self::catalogPath($locale)); + self::assertInstanceOf( + SimpleXMLElement::class, + $catalog, + sprintf('The %s catalogue is not valid XML.', $locale) + ); + + // local-name() keeps the query independent from the XLIFF namespace declared on the root element + $units = $catalog->xpath('//*[local-name()="trans-unit"]'); + self::assertIsArray($units, sprintf('The %s catalogue holds no translation unit.', $locale)); + + return array_values($units); + } +} diff --git a/translations/validators.es.xlf b/translations/validators.es.xlf new file mode 100644 index 0000000..f84f5b6 --- /dev/null +++ b/translations/validators.es.xlf @@ -0,0 +1,67 @@ + + + + + + The value {{ domain }} is not a valid domain name. + El valor {{ domain }} no es un nombre de dominio válido. + + + The domain {{ domain }} is not setup to received emails. + El dominio {{ domain }} no está configurado para recibir correos electrónicos. + + + The value {{ value }} is not a valid phone number. + El valor {{ value }} no es un número de teléfono válido. + + + The value {{ value }} is not formatted as an international phone number. + El valor {{ value }} no tiene el formato de un número de teléfono internacional. + + + The phone number {{ value }} is too short. + El número de teléfono {{ value }} es demasiado corto. + + + The phone number {{ value }} is too long. + El número de teléfono {{ value }} es demasiado largo. + + + The phone number {{ value }} does not exist. + El número de teléfono {{ value }} no existe. + + + The value {{ value }} is not an accepted phone number type. + El valor {{ value }} no es un tipo de número de teléfono aceptado. + + + The value {{ value }} is not a valid landline phone number. + El valor {{ value }} no es un número de teléfono fijo válido. + + + The value {{ value }} is not a valid mobile phone number. + El valor {{ value }} no es un número de teléfono móvil válido. + + + The float precision is limited to {{ precision }} numbers. + La precisión está limitada a {{ precision }} decimales. + + + The value {{ value }} is not a valid timezone. + El valor {{ value }} no es una zona horaria válida. + + + The value {{ value }} is not a valid SIREN number. + El valor {{ value }} no es un número SIREN válido. + + + The value {{ value }} is not a valid RNA identifier. + El valor {{ value }} no es un identificador RNA válido. + + + The intra-EU VAT number {{ value }} is not valid. + El número de IVA intracomunitario {{ value }} no es válido. + + + +