diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index d8fbbf1aad50..7abc3209c07d 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -80,7 +80,7 @@ public function run(array $params) if (is_string($optionDir)) { $tempCurrentDir = realpath($currentDir . $optionDir); - if (false === $tempCurrentDir) { + if ($tempCurrentDir === false) { CLI::error('Error: Directory must be located in "' . $currentDir . '"'); return EXIT_USER_INPUT; @@ -111,7 +111,12 @@ private function process(string $currentDir, string $currentLocale): void $files = iterator_to_array($iterator, true); ksort($files); - [$foundLanguageKeys, $countFiles] = $this->findLanguageKeysInFiles($files); + [ + 'foundLanguageKeys' => $foundLanguageKeys, + 'badLanguageKeys' => $badLanguageKeys, + 'countFiles' => $countFiles + ] = $this->findLanguageKeysInFiles($files); + ksort($foundLanguageKeys); $languageDiff = []; @@ -135,7 +140,7 @@ private function process(string $currentDir, string $currentLocale): void $newLanguageKeys = array_replace_recursive($foundLanguageKeys[$langFileName], $languageStoredKeys); if ($languageDiff !== []) { - if (false === file_put_contents($languageFilePath, $this->templateFile($newLanguageKeys))) { + if (file_put_contents($languageFilePath, $this->templateFile($newLanguageKeys)) === false) { $this->writeIsVerbose('Lang file ' . $langFileName . ' (error write).', 'red'); } else { $this->writeIsVerbose('Lang file "' . $langFileName . '" successful updated!', 'green'); @@ -155,14 +160,30 @@ private function process(string $currentDir, string $currentLocale): void $this->writeIsVerbose('Files found: ' . $countFiles); $this->writeIsVerbose('New translates found: ' . $countNewKeys); + $this->writeIsVerbose('Bad translates found: ' . count($badLanguageKeys)); + + if ($this->verbose && $badLanguageKeys !== []) { + $tableBadRows = []; + + foreach ($badLanguageKeys as $value) { + $tableBadRows[] = [$value[1], $value[0]]; + } + + ArrayHelper::sortValuesByNatural($tableBadRows, 0); + + CLI::table($tableBadRows, ['Bad Key', 'Filepath']); + } } /** * @param SplFileInfo|string $file + * + * @return array */ private function findTranslationsInFile($file): array { $foundLanguageKeys = []; + $badLanguageKeys = []; if (is_string($file) && is_file($file)) { $file = new SplFileInfo($file); @@ -172,7 +193,7 @@ private function findTranslationsInFile($file): array preg_match_all('/lang\(\'([._a-z0-9\-]+)\'\)/ui', $fileContent, $matches); if ($matches[1] === []) { - return []; + return compact('foundLanguageKeys', 'badLanguageKeys'); } foreach ($matches[1] as $phraseKey) { @@ -180,6 +201,8 @@ private function findTranslationsInFile($file): array // Language key not have Filename or Lang key if (count($phraseKeys) < 2) { + $badLanguageKeys[] = [mb_substr($file->getRealPath(), mb_strlen(ROOTPATH)), $phraseKey]; + continue; } @@ -189,6 +212,8 @@ private function findTranslationsInFile($file): array || ($languageFileName === '' && $phraseKeys[0] === ''); if ($isEmptyNestedArray) { + $badLanguageKeys[] = [mb_substr($file->getRealPath(), mb_strlen(ROOTPATH)), $phraseKey]; + continue; } @@ -201,7 +226,7 @@ private function findTranslationsInFile($file): array } } - return $foundLanguageKeys; + return compact('foundLanguageKeys', 'badLanguageKeys'); } private function isIgnoredFile(SplFileInfo $file): bool @@ -334,12 +359,13 @@ private function isSubDirectory(string $directory, string $rootDirectory): bool /** * @param SplFileInfo[] $files * - * @return array - * @phpstan-return list{0: array>, 1: int} + * @return array + * @phpstan-return array{'foundLanguageKeys': array>, 'badLanguageKeys': array>, 'countFiles': int} */ private function findLanguageKeysInFiles(array $files): array { $foundLanguageKeys = []; + $badLanguageKeys = []; $countFiles = 0; foreach ($files as $file) { @@ -349,9 +375,13 @@ private function findLanguageKeysInFiles(array $files): array $this->writeIsVerbose('File found: ' . mb_substr($file->getRealPath(), mb_strlen(APPPATH))); $countFiles++; - $foundLanguageKeys = array_replace_recursive($this->findTranslationsInFile($file), $foundLanguageKeys); + + $findInFile = $this->findTranslationsInFile($file); + + $foundLanguageKeys = array_replace_recursive($findInFile['foundLanguageKeys'], $foundLanguageKeys); + $badLanguageKeys = array_merge($findInFile['badLanguageKeys'], $badLanguageKeys); } - return [$foundLanguageKeys, $countFiles]; + return compact('foundLanguageKeys', 'badLanguageKeys', 'countFiles'); } } diff --git a/system/Helpers/Array/ArrayHelper.php b/system/Helpers/Array/ArrayHelper.php index 5b359a761384..04f4bd335623 100644 --- a/system/Helpers/Array/ArrayHelper.php +++ b/system/Helpers/Array/ArrayHelper.php @@ -19,7 +19,9 @@ * If there are any methods that should be provided, make them * public APIs via helper functions. * + * @see \CodeIgniter\Helpers\Array\ArrayHelperDotKeyExistsTest * @see \CodeIgniter\Helpers\Array\ArrayHelperRecursiveDiffTest + * @see \CodeIgniter\Helpers\Array\ArrayHelperSortValuesByNaturalTest */ final class ArrayHelper { @@ -297,4 +299,21 @@ public static function recursiveCount(array $array, int $counter = 0): int return $counter; } + + /** + * Sorts array values in natural order + * If the value is an array, you need to specify the $sortByIndex of the key to sort + * + * @param int|string|null $sortByIndex + */ + public static function sortValuesByNatural(array &$array, $sortByIndex = null): bool + { + return usort($array, static function ($currentValue, $nextValue) use ($sortByIndex) { + if ($sortByIndex !== null) { + return strnatcmp($currentValue[$sortByIndex], $nextValue[$sortByIndex]); + } + + return strnatcmp($currentValue, $nextValue); + }); + } } diff --git a/tests/_support/Services/Translation/TranslationTwo.php b/tests/_support/Services/Translation/TranslationTwo.php index 71e5e663b27e..aa6d1e0a9ee2 100644 --- a/tests/_support/Services/Translation/TranslationTwo.php +++ b/tests/_support/Services/Translation/TranslationTwo.php @@ -26,6 +26,13 @@ public function list() $translationError6 = lang('TranslationTwo...'); $translationError7 = lang('..invalid_nested_key..'); + $copyTranslationError1 = lang('TranslationTwo'); + $copyTranslationError2 = lang(' '); + $copyTranslationError3 = lang(''); + $copyTranslationError4 = lang('.invalid_key'); + $copyTranslationError5 = lang('TranslationTwo.'); + $copyTranslationError6 = lang('TranslationTwo...'); + $copyTranslationError7 = lang('..invalid_nested_key..'); // Empty in comments lang('') lang(' ') } } diff --git a/tests/system/Commands/Translation/LocalizationFinderTest.php b/tests/system/Commands/Translation/LocalizationFinderTest.php index f5cc38ec7b2f..fa976c752c24 100644 --- a/tests/system/Commands/Translation/LocalizationFinderTest.php +++ b/tests/system/Commands/Translation/LocalizationFinderTest.php @@ -103,6 +103,15 @@ public function testShowNewTranslation(): void $this->assertStringContainsString($this->getActualTableWithNewKeys(), $this->getStreamFilterBuffer()); } + public function testShowBadTranslation(): void + { + $this->makeLocaleDirectory(); + + command('lang:find --dir Translation --verbose'); + + $this->assertStringContainsString($this->getActualTableWithBadKeys(), $this->getStreamFilterBuffer()); + } + private function getActualTranslationOneKeys(): array { return [ @@ -193,6 +202,26 @@ private function getActualTableWithNewKeys(): string TEXT_WRAP; } + private function getActualTableWithBadKeys(): string + { + return <<<'TEXT_WRAP' + +------------------------+--------------------------------------------------------+ + | Bad Key | Filepath | + +------------------------+--------------------------------------------------------+ + | ..invalid_nested_key.. | tests/_support/Services/Translation/TranslationTwo.php | + | ..invalid_nested_key.. | tests/_support/Services/Translation/TranslationTwo.php | + | .invalid_key | tests/_support/Services/Translation/TranslationTwo.php | + | .invalid_key | tests/_support/Services/Translation/TranslationTwo.php | + | TranslationTwo | tests/_support/Services/Translation/TranslationTwo.php | + | TranslationTwo | tests/_support/Services/Translation/TranslationTwo.php | + | TranslationTwo. | tests/_support/Services/Translation/TranslationTwo.php | + | TranslationTwo. | tests/_support/Services/Translation/TranslationTwo.php | + | TranslationTwo... | tests/_support/Services/Translation/TranslationTwo.php | + | TranslationTwo... | tests/_support/Services/Translation/TranslationTwo.php | + +------------------------+--------------------------------------------------------+ + TEXT_WRAP; + } + private function assertTranslationsExistAndHaveTranslatedKeys(): void { $this->assertFileExists(self::$languageTestPath . self::$locale . '/TranslationOne.php'); diff --git a/tests/system/Helpers/Array/ArrayHelperSortValuesByNaturalTest.php b/tests/system/Helpers/Array/ArrayHelperSortValuesByNaturalTest.php new file mode 100644 index 000000000000..312eb0853c9d --- /dev/null +++ b/tests/system/Helpers/Array/ArrayHelperSortValuesByNaturalTest.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Helpers\Array; + +use CodeIgniter\Test\CIUnitTestCase; + +/** + * @group Others + * + * @internal + */ +final class ArrayHelperSortValuesByNaturalTest extends CIUnitTestCase +{ + private array $arrayWithStringValues = [ + 'apple10', + 'banana', + 'apple1', + 'банан', + 'Banana', + 'Apple', + 100000, + 'яблоко', + 1200, + 13000, + 'Банан', + 'Яблоко', + 'apple', + ]; + private array $arrayWithArrayValues = [ + ['apple', 'Banana'], + ['apple10', 'Apple'], + ['Яблоко', 1200], + [13000, 'Банан'], + ['Apple', 'apple1'], + ['banana', 'банан'], + [100000, 13000], + ['Banana', 'Яблоко'], + ['Банан', 'banana'], + [1200, 'apple'], + ['apple1', 'apple10'], + ['яблоко', 100000], + ['банан', 'яблоко'], + ]; + + public function testSortWithStringValues(): void + { + shuffle($this->arrayWithStringValues); + + ArrayHelper::sortValuesByNatural($this->arrayWithStringValues); + + $this->assertSame([ + 1200, + 13000, + 100000, + 'Apple', + 'Banana', + 'apple', + 'apple1', + 'apple10', + 'banana', + 'Банан', + 'Яблоко', + 'банан', + 'яблоко', + ], $this->arrayWithStringValues); + } + + public function testSortWithArrayValues(): void + { + shuffle($this->arrayWithArrayValues); + + // For first index + ArrayHelper::sortValuesByNatural($this->arrayWithArrayValues, 0); + + $this->assertSame([ + [1200, 'apple'], + [13000, 'Банан'], + [100000, 13000], + ['Apple', 'apple1'], + ['Banana', 'Яблоко'], + ['apple', 'Banana'], + ['apple1', 'apple10'], + ['apple10', 'Apple'], + ['banana', 'банан'], + ['Банан', 'banana'], + ['Яблоко', 1200], + ['банан', 'яблоко'], + ['яблоко', 100000], + ], $this->arrayWithArrayValues); + + shuffle($this->arrayWithArrayValues); + + // For other index + ArrayHelper::sortValuesByNatural($this->arrayWithArrayValues, 1); + + $this->assertSame([ + ['Яблоко', 1200], + [100000, 13000], + ['яблоко', 100000], + ['apple10', 'Apple'], + ['apple', 'Banana'], + [1200, 'apple'], + ['Apple', 'apple1'], + ['apple1', 'apple10'], + ['Банан', 'banana'], + [13000, 'Банан'], + ['Banana', 'Яблоко'], + ['banana', 'банан'], + ['банан', 'яблоко'], + ], $this->arrayWithArrayValues); + } +} diff --git a/user_guide_src/source/outgoing/localization.rst b/user_guide_src/source/outgoing/localization.rst index 837807989b0d..0aa680371255 100644 --- a/user_guide_src/source/outgoing/localization.rst +++ b/user_guide_src/source/outgoing/localization.rst @@ -284,6 +284,27 @@ Before updating, it is possible to preview the translations found by the command php spark lang:find --verbose --show-new +The detailed output of ``--verbose`` also shows a list of invalid keys. For example: + +.. code-block:: console + + ... + + Files found: 10 + New translates found: 30 + Bad translates found: 5 + +------------------------+---------------------------------+ + | Bad Key | Filepath | + +------------------------+---------------------------------+ + | ..invalid_nested_key.. | app/Controllers/Translation.php | + | .invalid_key | app/Controllers/Translation.php | + | TranslationBad | app/Controllers/Translation.php | + | TranslationBad. | app/Controllers/Translation.php | + | TranslationBad... | app/Controllers/Translation.php | + +------------------------+---------------------------------+ + + All operations done! + For a more accurate search, specify the desired locale or directory to scan. .. code-block:: console