diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b2c004..f99604b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,9 @@ using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. - Add new **resx** storage format to support Windows applications. - Add new **Docker Image** for PHPUnuhi to run it in a container without installing in your project -- Add new version specific XSD files in the schema folder for upcoming releases +- Add new version specific XSD files in the schema folder for upcoming releases +- Include new error hints for missing and empty keys, particularly when the base language may also lack the same translation. In such cases, this could be an expected behavior. +- Add base locale indicator in terminal output of errors (next to the locale name). ## [1.22.0] diff --git a/src/Commands/ValidateSimilarityCommand.php b/src/Commands/ValidateSimilarityCommand.php index e425319..b2bea84 100644 --- a/src/Commands/ValidateSimilarityCommand.php +++ b/src/Commands/ValidateSimilarityCommand.php @@ -147,7 +147,7 @@ private function buildTestValidation(array $pair, Locale $locale): ValidationTes return new ValidationTest( $key1, - $locale->getName(), + $locale, 'Test Similarity', '', 0, diff --git a/src/Components/Validator/CaseStyleValidator.php b/src/Components/Validator/CaseStyleValidator.php index ca846aa..ef99032 100644 --- a/src/Components/Validator/CaseStyleValidator.php +++ b/src/Components/Validator/CaseStyleValidator.php @@ -95,7 +95,7 @@ public function validate(TranslationSet $set, StorageInterface $storage): Valida $tests[] = new ValidationTest( $translation->getKey(), - $locale->getName(), + $locale, "Test case-style of key '" . $translation->getKey() . "' to be one of: " . $allowedCaseStylesText, $locale->getFilename(), $locale->findLineNumber($translation->getKey()), diff --git a/src/Components/Validator/EmptyContentValidator.php b/src/Components/Validator/EmptyContentValidator.php index 2de11d2..e8e0cec 100644 --- a/src/Components/Validator/EmptyContentValidator.php +++ b/src/Components/Validator/EmptyContentValidator.php @@ -8,6 +8,7 @@ use PHPUnuhi\Components\Validator\EmptyContent\AllowEmptyContent; use PHPUnuhi\Components\Validator\Model\ValidationResult; use PHPUnuhi\Components\Validator\Model\ValidationTest; +use PHPUnuhi\Exceptions\TranslationNotFoundException; use PHPUnuhi\Models\Translation\Locale; use PHPUnuhi\Models\Translation\Translation; use PHPUnuhi\Models\Translation\TranslationSet; @@ -29,7 +30,6 @@ public function __construct(array $allowList) } - public function getTypeIdentifier(): string { return 'EMPTY_CONTENT'; @@ -40,12 +40,22 @@ public function validate(TranslationSet $set, StorageInterface $storage): Valida { $tests = []; + # first search if we have a base locale + $baseLocale = null; + + foreach ($set->getLocales() as $locale) { + if ($locale->isBase()) { + $baseLocale = $locale; + break; + } + } + foreach ($set->getLocales() as $locale) { foreach ($locale->getTranslations() as $translation) { $testPassed = !$translation->isEmpty(); + $baseLocaleWarning = false; if (!$testPassed) { - # check if we have an allow list entry foreach ($this->allowList as $allowEntry) { if ($allowEntry->getKey() === $translation->getKey() && $allowEntry->isLocaleAllowed($locale->getName())) { @@ -55,13 +65,29 @@ public function validate(TranslationSet $set, StorageInterface $storage): Valida } } + + # we haven't the required key in our current locale + # let's figure out if we have a base locale and if it's also missing there + # if so, then let the user know + if (!$testPassed && ($baseLocale instanceof Locale && $locale->getName() !== $baseLocale->getName())) { + try { + $baseLocaleTranslation = $baseLocale->findTranslation($translation->getKey()); + + # if this is empty, then we also want to warn, that the base local is also empty + # and that it might be just fine... + $baseLocaleWarning = $baseLocaleTranslation->isEmpty(); + } catch (TranslationNotFoundException $e) { + $baseLocaleWarning = true; + } + } + if ($translation->getGroup() !== '') { $identifier = $translation->getGroup() . ' (group) => ' . $translation->getKey(); } else { $identifier = $translation->getID(); } - $tests[] = $this->buildValidationTest($identifier, $locale, $translation, $testPassed); + $tests[] = $this->buildValidationTest($identifier, $locale, $translation, $baseLocaleWarning, $testPassed); } } @@ -69,16 +95,26 @@ public function validate(TranslationSet $set, StorageInterface $storage): Valida } - private function buildValidationTest(string $identifier, Locale $locale, Translation $translation, bool $testPassed): ValidationTest + private function buildValidationTest(string $identifier, Locale $locale, Translation $translation, bool $baseLocaleDeprecationInfo, bool $testPassed): ValidationTest { + $suffix = ''; + + if ($baseLocaleDeprecationInfo) { + $suffix = ' (your base locale is also missing this translation. Maybe this is an expected behavior?)'; + } + + if ($locale->isBase()) { + $suffix = ' (this is a base locale. Maybe this is an expected behavior?)'; + } + return new ValidationTest( $identifier, - $locale->getName(), + $locale, 'Test existing translation for key: ' . $translation->getKey(), $locale->getFilename(), $locale->findLineNumber($translation->getKey()), $this->getTypeIdentifier(), - 'Translation for key ' . $translation->getKey() . ' does not have a value', + 'Translation for key ' . $translation->getKey() . ' does not have a value.' . $suffix, $testPassed ); } diff --git a/src/Components/Validator/MissingStructureValidator.php b/src/Components/Validator/MissingStructureValidator.php index a57e469..de01d92 100644 --- a/src/Components/Validator/MissingStructureValidator.php +++ b/src/Components/Validator/MissingStructureValidator.php @@ -7,6 +7,7 @@ use PHPUnuhi\Bundles\Storage\StorageInterface; use PHPUnuhi\Components\Validator\Model\ValidationResult; use PHPUnuhi\Components\Validator\Model\ValidationTest; +use PHPUnuhi\Exceptions\TranslationNotFoundException; use PHPUnuhi\Models\Translation\Locale; use PHPUnuhi\Models\Translation\TranslationSet; @@ -24,6 +25,16 @@ public function validate(TranslationSet $set, StorageInterface $storage): Valida $tests = []; + # first search if we have a base locale + $baseLocale = null; + + foreach ($set->getLocales() as $locale) { + if ($locale->isBase()) { + $baseLocale = $locale; + break; + } + } + foreach ($set->getLocales() as $locale) { $localeKeys = $locale->getTranslationIDs(); @@ -31,19 +42,30 @@ public function validate(TranslationSet $set, StorageInterface $storage): Valida # as our global suite keys list $structureValid = $this->isStructureEqual($localeKeys, $allKeys); - $same = $this->getSame($localeKeys, $allKeys); if (!$structureValid) { $filtered = $this->getDiff($localeKeys, $allKeys); foreach ($filtered as $key) { - $tests[] = $this->buildValidationTest($key, $locale, false); + $baseLocaleWarning = false; + + # we haven't the required key in our current locale + # let's figure out if we have a base locale and if it's also missing there + # if so, then let the user know + if ($baseLocale instanceof Locale && $locale->getName() !== $baseLocale->getName()) { + try { + $baseLocale->findTranslation($key); + } catch (TranslationNotFoundException $e) { + $baseLocaleWarning = true; + } + } + $tests[] = $this->buildValidationTest($key, $locale, $baseLocaleWarning, false); } } foreach ($same as $key) { - $tests[] = $this->buildValidationTest($key, $locale, true); + $tests[] = $this->buildValidationTest($key, $locale, false, true); } } @@ -89,16 +111,26 @@ private function getSame(array $a, array $b): array } - private function buildValidationTest(string $key, Locale $locale, bool $success): ValidationTest + private function buildValidationTest(string $key, Locale $locale, bool $baseLocaleDeprecationInfo, bool $success): ValidationTest { + $suffix = ''; + + if ($baseLocaleDeprecationInfo) { + $suffix = ' (your base locale is also missing this key. Maybe this is an expected behavior?)'; + } + + if ($locale->isBase()) { + $suffix = ' (this is a base locale. Maybe this is an expected behavior?)'; + } + return new ValidationTest( $key, - $locale->getName(), + $locale, 'Test structure of key: ' . $key, $locale->getFilename(), $locale->findLineNumber($key), $this->getTypeIdentifier(), - 'Found missing structure in locale. Key is missing: ' . $key, + 'Found missing structure in locale. Key is missing: ' . $key . $suffix, $success ); } diff --git a/src/Components/Validator/Model/ValidationTest.php b/src/Components/Validator/Model/ValidationTest.php index 272ebed..7f3d61b 100644 --- a/src/Components/Validator/Model/ValidationTest.php +++ b/src/Components/Validator/Model/ValidationTest.php @@ -4,11 +4,13 @@ namespace PHPUnuhi\Components\Validator\Model; +use PHPUnuhi\Models\Translation\Locale; + class ValidationTest { private string $translationKey; - private string $locale; + private ?Locale $locale; private string $title; @@ -23,8 +25,7 @@ class ValidationTest private bool $success; - - public function __construct(string $translationKey, string $locale, string $title, string $filename, int $lineNumber, string $classification, string $failureMessage, bool $success) + public function __construct(string $translationKey, ?Locale $locale, string $title, string $filename, int $lineNumber, string $classification, string $failureMessage, bool $success) { $this->translationKey = $translationKey; $this->locale = $locale; @@ -45,7 +46,9 @@ public function getTranslationKey(): string public function getTitle(): string { - return '[' . $this->locale . '] ' . $this->title; + $localeName = $this->locale instanceof Locale ? $this->locale->getName() : '-'; + + return '[' . $localeName . '] ' . $this->title; } @@ -77,8 +80,7 @@ public function isSuccess(): bool return $this->success; } - - public function getLocale(): string + public function getLocale(): ?Locale { return $this->locale; } diff --git a/src/Components/Validator/Rules/DisallowedTextsRule.php b/src/Components/Validator/Rules/DisallowedTextsRule.php index 6a3e6a0..f338f26 100644 --- a/src/Components/Validator/Rules/DisallowedTextsRule.php +++ b/src/Components/Validator/Rules/DisallowedTextsRule.php @@ -60,7 +60,7 @@ public function validate(TranslationSet $set, StorageInterface $storage): Valida $tests[] = new ValidationTest( $identifier, - $locale->getName(), + $locale, "Test against disallowed texts for key: '" . $translation->getKey(), $locale->getFilename(), $locale->findLineNumber($translation->getKey()), diff --git a/src/Components/Validator/Rules/DuplicateContentRule.php b/src/Components/Validator/Rules/DuplicateContentRule.php index b5bed80..c435270 100644 --- a/src/Components/Validator/Rules/DuplicateContentRule.php +++ b/src/Components/Validator/Rules/DuplicateContentRule.php @@ -108,7 +108,7 @@ private function buildTestEntry(Locale $locale, string $translationKey, bool $pa { return new ValidationTest( $translationKey, - $locale->getName(), + $locale, "Testing for duplicate content in key: '" . $translationKey . "'", $locale->getFilename(), $locale->findLineNumber($translationKey), diff --git a/src/Components/Validator/Rules/EmptyContentRule.php b/src/Components/Validator/Rules/EmptyContentRule.php index 12c5b5f..b7d1250 100644 --- a/src/Components/Validator/Rules/EmptyContentRule.php +++ b/src/Components/Validator/Rules/EmptyContentRule.php @@ -43,7 +43,7 @@ private function buildTestEntry(Locale $locale, string $translationKey, bool $pa { return new ValidationTest( $translationKey, - $locale->getName(), + $locale, 'Testing for empty content of key: ' . $translationKey, $locale->getFilename(), $locale->findLineNumber($translationKey), diff --git a/src/Components/Validator/Rules/MaxKeyLengthRule.php b/src/Components/Validator/Rules/MaxKeyLengthRule.php index e49a823..3214510 100644 --- a/src/Components/Validator/Rules/MaxKeyLengthRule.php +++ b/src/Components/Validator/Rules/MaxKeyLengthRule.php @@ -68,7 +68,7 @@ public function validate(TranslationSet $set, StorageInterface $storage): Valida $tests[] = new ValidationTest( $identifier, - $locale->getName(), + $locale, "Test length of key '" . $translation->getKey(), $locale->getFilename(), $locale->findLineNumber($translation->getKey()), diff --git a/src/Components/Validator/Rules/NestingDepthRule.php b/src/Components/Validator/Rules/NestingDepthRule.php index dc50197..18680f3 100644 --- a/src/Components/Validator/Rules/NestingDepthRule.php +++ b/src/Components/Validator/Rules/NestingDepthRule.php @@ -76,7 +76,7 @@ private function buildTestEntry(Locale $locale, string $translationKey, int $dep { return new ValidationTest( $translationKey, - $locale->getName(), + $locale, "Test nesting-depth of key '" . $translationKey, $locale->getFilename(), $locale->findLineNumber($translationKey), diff --git a/src/Facades/CLI/MessValidatorCliFacade.php b/src/Facades/CLI/MessValidatorCliFacade.php index 351b323..0983972 100644 --- a/src/Facades/CLI/MessValidatorCliFacade.php +++ b/src/Facades/CLI/MessValidatorCliFacade.php @@ -77,7 +77,7 @@ private function buildTestValidation(string $translationID, bool $success): Vali { return new ValidationTest( $translationID, - '-', + null, 'Test if translation for key ' . $translationID . ' exists in any locale', '', 0, diff --git a/src/Facades/CLI/SpellingValidatorCliFacade.php b/src/Facades/CLI/SpellingValidatorCliFacade.php index d01082c..fcc7c5d 100644 --- a/src/Facades/CLI/SpellingValidatorCliFacade.php +++ b/src/Facades/CLI/SpellingValidatorCliFacade.php @@ -124,7 +124,7 @@ private function buildTestValidation(string $translationID, string $originalText return new ValidationTest( $translationID, - $spellingResult->getLocale(), + $set->getLocale($spellingResult->getLocale()), 'Test if translation for key ' . $translationID . ' is spelled correctly', '', 0, diff --git a/src/Models/Translation/TranslationSet.php b/src/Models/Translation/TranslationSet.php index 610629f..80f9c27 100644 --- a/src/Models/Translation/TranslationSet.php +++ b/src/Models/Translation/TranslationSet.php @@ -346,4 +346,15 @@ public function findPlaceholders(string $text): array return $foundPlaceholders; } + + public function getLocale(string $name): Locale + { + foreach ($this->locales as $locale) { + if ($locale->getName() === $name) { + return $locale; + } + } + + throw new Exception('Locale not found: ' . $name); + } } diff --git a/src/Traits/CommandOutputTrait.php b/src/Traits/CommandOutputTrait.php index c95075d..100980b 100644 --- a/src/Traits/CommandOutputTrait.php +++ b/src/Traits/CommandOutputTrait.php @@ -5,6 +5,7 @@ namespace PHPUnuhi\Traits; use PHPUnuhi\Components\Validator\Model\ValidationTest; +use PHPUnuhi\Models\Translation\Locale; use PHPUnuhi\Services\OpenAI\OpenAIUsageTracker; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Helper\TableCell; @@ -103,10 +104,22 @@ public function showErrorTable(array $rows, OutputInterface $output): void continue; } + $localeName = '-'; + + $locale = $row->getLocale(); + + if ($locale instanceof Locale) { + $localeName = $locale->getName(); + + if ($locale->isBase()) { + $localeName .= ' (base)'; + } + } + $rowData[] = [ $intRowIndex, ' ' . $row->getClassification() . ' ', - ' ' . $row->getLocale() . ' ', + ' ' . $localeName . ' ', ' ' . $row->getTranslationKey() . ' ', $row->getFailureMessage() ]; diff --git a/tests/phpunit/Components/Validator/Model/ValidationResultTest.php b/tests/phpunit/Components/Validator/Model/ValidationResultTest.php index ef42b24..e4d6b1b 100644 --- a/tests/phpunit/Components/Validator/Model/ValidationResultTest.php +++ b/tests/phpunit/Components/Validator/Model/ValidationResultTest.php @@ -18,7 +18,7 @@ public function setUp(): void { $test = new ValidationTest( '', - '', + null, '', '', 5, diff --git a/tests/phpunit/Components/Validator/Model/ValidationTestTest.php b/tests/phpunit/Components/Validator/Model/ValidationTestTest.php index 46a0457..c0b4d58 100644 --- a/tests/phpunit/Components/Validator/Model/ValidationTestTest.php +++ b/tests/phpunit/Components/Validator/Model/ValidationTestTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use PHPUnuhi\Components\Validator\Model\ValidationTest; +use PHPUnuhi\Models\Translation\Locale; class ValidationTestTest extends TestCase { @@ -15,9 +16,11 @@ class ValidationTestTest extends TestCase public function setUp(): void { + $locale = new Locale('en_US', false, '', ''); + $this->test = new ValidationTest( 'btnCancel', - 'en_US', + $locale, 'Testing btnCancel', 'en.json', 15,