From 9ead16fe08f2013dcccdbdfe8300b55118e417ae Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 5 Jul 2024 20:09:26 +0300 Subject: [PATCH 01/12] feat: Add `lang:sync` command --- .../Commands/Translation/LocalizationSync.php | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 system/Commands/Translation/LocalizationSync.php diff --git a/system/Commands/Translation/LocalizationSync.php b/system/Commands/Translation/LocalizationSync.php new file mode 100644 index 000000000000..b2bff0c1d473 --- /dev/null +++ b/system/Commands/Translation/LocalizationSync.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Commands\Translation; + +use CodeIgniter\CLI\BaseCommand; +use CodeIgniter\CLI\CLI; +use CodeIgniter\Helpers\Array\ArrayHelper; +use Config\App; +use Locale; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use SplFileInfo; + +/** + * @see \CodeIgniter\Commands\Translation\LocalizationSyncTest + */ +class LocalizationSync extends BaseCommand +{ + protected $group = 'Translation'; + protected $name = 'lang:sync'; + protected $description = 'Synchronize translation files from one language to another.'; + protected $usage = 'lang:sync [options]'; + protected $arguments = []; + protected $options = [ + '--locale' => 'The original locale (en, ru, etc.).', + '--target' => 'Target locale (en, ru, etc.).', + ]; + private string $languagePath; + + public function run(array $params) + { + $optionTargetLocale = ''; + $optionLocale = $params['locale'] ?? Locale::getDefault(); + $this->languagePath = APPPATH . 'Language'; + + if (isset($params['target']) && $params['target'] !== '') { + $optionTargetLocale = $params['target']; + } + + if (! in_array($optionLocale, config(App::class)->supportedLocales, true)) { + CLI::error( + 'Error: "' . $optionLocale . '" is not supported. Supported locales: ' + . implode(', ', config(App::class)->supportedLocales) + ); + + return EXIT_USER_INPUT; + } + + if ($optionTargetLocale === '') { + CLI::error( + 'Error: "--target" is not configured. Supported locales: ' + . implode(', ', config(App::class)->supportedLocales) + ); + + return EXIT_USER_INPUT; + } + + if (! in_array($optionTargetLocale, config(App::class)->supportedLocales, true)) { + CLI::error( + 'Error: "' . $optionTargetLocale . '" is not supported. Supported locales: ' + . implode(', ', config(App::class)->supportedLocales) + ); + + return EXIT_USER_INPUT; + } + + if ($optionTargetLocale === $optionLocale) { + CLI::error( + 'Error: You cannot have the same values "--target" and "--locale".' + ); + + return EXIT_USER_INPUT; + } + + if (ENVIRONMENT === 'testing') { + $this->languagePath = SUPPORTPATH . 'Language'; + } + + $this->process($optionLocale, $optionTargetLocale); + + CLI::write('All operations done!'); + + return EXIT_SUCCESS; + } + + private function process(string $originalLocale, string $targetLocale): void + { + $originalLocaleDir = $this->languagePath . DIRECTORY_SEPARATOR . $originalLocale; + $targetLocaleDir = $this->languagePath . DIRECTORY_SEPARATOR . $targetLocale; + + if (! is_dir($originalLocaleDir)) { + CLI::error( + 'Error: The "' . $originalLocaleDir . '" directory was not found.' + ); + } + + if (! is_dir($targetLocaleDir) && ! mkdir($targetLocaleDir, 0775)) { + CLI::error( + 'Error: The target directory "' . $targetLocaleDir . '" cannot be accessed.' + ); + } + + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($originalLocaleDir)); + + /** + * @var list $files + */ + $files = iterator_to_array($iterator, true); + ksort($files); + + foreach ($files as $originalLanguageFile) { + if ($this->isIgnoredFile($originalLanguageFile)) { + continue; + } + + $targetLanguageFile = $targetLocaleDir . DIRECTORY_SEPARATOR . $originalLanguageFile->getFilename(); + + $targetLanguageKeys = []; + $originalLanguageKeys = include $originalLanguageFile; + + if (is_file($targetLanguageFile)) { + $targetLanguageKeys = include $targetLanguageFile; + } + + $targetLanguageKeys = $this->mergeLanguageKeys($originalLanguageKeys, $targetLanguageKeys, $originalLanguageFile->getBasename('.php')); + ksort($targetLanguageKeys); + + $content = "|string|null> $originalLanguageKeys + * @param array|string|null> $targetLanguageKeys + * + * @return array|string|null> + */ + private function mergeLanguageKeys(array $originalLanguageKeys, array $targetLanguageKeys, string $prefix = ''): array + { + foreach ($originalLanguageKeys as $key => $value) { + $placeholderValue = $prefix === '' ? $prefix . '.' . $key : $key; + + if (! is_array($value)) { + // Keep the old value + // TODO: The value type may not match the original one + if (array_key_exists($key, $targetLanguageKeys)) { + continue; + } + + // Set new key with placeholder + $targetLanguageKeys[$key] = $placeholderValue; + } else { + if (! array_key_exists($key, $targetLanguageKeys)) { + $targetLanguageKeys[$key] = []; + } + + $targetLanguageKeys[$key] = $this->mergeLanguageKeys($value, $targetLanguageKeys[$key], $placeholderValue); + } + } + + return ArrayHelper::intersectKeyRecursive($targetLanguageKeys, $originalLanguageKeys); + } + + private function isIgnoredFile(SplFileInfo $file): bool + { + return $file->isDir() || $file->getFilename() === '.' || $file->getFilename() === '..' || $file->getExtension() !== 'php'; + } +} From 0c7a01033deb7eba31b33e95601b1973a4f9cb50 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 5 Jul 2024 20:10:24 +0300 Subject: [PATCH 02/12] test: Add tests for `lang:sync` command --- .../Translation/LocalizationSyncTest.php | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 tests/system/Commands/Translation/LocalizationSyncTest.php diff --git a/tests/system/Commands/Translation/LocalizationSyncTest.php b/tests/system/Commands/Translation/LocalizationSyncTest.php new file mode 100644 index 000000000000..3a682cbea4ef --- /dev/null +++ b/tests/system/Commands/Translation/LocalizationSyncTest.php @@ -0,0 +1,214 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Commands\Translation; + +use CodeIgniter\Test\CIUnitTestCase; +use CodeIgniter\Test\StreamFilterTrait; +use Config\App; +use Locale; +use PHPUnit\Framework\Attributes\Group; + +/** + * @internal + */ +#[Group('Others')] +final class LocalizationSyncTest extends CIUnitTestCase +{ + use StreamFilterTrait; + + private static string $locale; + private static string $languageTestPath; + + /** + * @var array|string|null> + */ + private array $expectedKeys = [ + 'a' => 'Sync.a', + 'b' => 'Sync.b', + 'c' => 'Sync.c', + 'd' => [], + 'e' => 'Sync.e', + 'f' => [ + 'g' => 'Sync.f.g', + 'h' => [ + 'i' => 'Sync.f.h.i', + ], + ], + ]; + + protected function setUp(): void + { + parent::setUp(); + + config(App::class)->supportedLocales = ['en', 'ru', 'test']; + + self::$locale = Locale::getDefault(); + self::$languageTestPath = SUPPORTPATH . 'Language' . DIRECTORY_SEPARATOR; + $this->makeLanguageFiles(); + } + + protected function tearDown(): void + { + parent::tearDown(); + + $this->clearGeneratedFiles(); + } + + public function testSyncDefaultLocale(): void + { + command('lang:sync --target test'); + + $langFile = self::$languageTestPath . 'test/Sync.php'; + + $this->assertFileExists($langFile); + + $langKeys = include $langFile; + + $this->assertIsArray($langKeys); + $this->assertSame($this->expectedKeys, $langKeys); + } + + public function testSyncWithLocaleOption(): void + { + command('lang:sync --locale ru --target test'); + + $langFile = self::$languageTestPath . 'test/Sync.php'; + + $this->assertFileExists($langFile); + + $langKeys = include $langFile; + + $this->assertIsArray($langKeys); + $this->assertSame($this->expectedKeys, $langKeys); + } + + public function testSyncWithExistTranslation(): void + { + // First run, add new keys + command('lang:sync --target test'); + + $langFile = self::$languageTestPath . 'test/Sync.php'; + + $this->assertFileExists($langFile); + + $langKeys = include $langFile; + + $this->assertIsArray($langKeys); + $this->assertSame($this->expectedKeys, $langKeys); + + // Second run, save old keys + $oldLangKeys = [ + 'a' => 'old value 1', + 'b' => 2000, + 'c' => null, + 'd' => [], + 'e' => '', + 'f' => [ + 'g' => 'old value 2', + 'h' => [ + 'i' => 'old value 3', + ], + ], + ]; + + $lang = <<<'TEXT_WRAP' + 'old value 1', + 'b' => 2000, + 'c' => null, + 'd' => [], + 'e' => '', + 'f' => [ + 'g' => 'old value 2', + 'h' => [ + 'i' => 'old value 3', + ], + ], + ]; + TEXT_WRAP; + + file_put_contents(self::$languageTestPath . 'test/Sync.php', $lang); + + command('lang:sync --target test'); + + $langFile = self::$languageTestPath . 'test/Sync.php'; + + $this->assertFileExists($langFile); + + $langKeys = include $langFile; + $this->assertIsArray($langKeys); + $this->assertSame($oldLangKeys, $langKeys); + } + + public function testSyncWithIncorrectLocaleOption(): void + { + command('lang:sync --locale test_locale_incorrect --target test'); + + $this->assertStringContainsString('is not supported', $this->getStreamFilterBuffer()); + } + + public function testSyncWithIncorrectTargetOption(): void + { + command('lang:sync --locale en --target test_locale_incorrect'); + + $this->assertStringContainsString('is not supported', $this->getStreamFilterBuffer()); + } + + private function makeLanguageFiles(): void + { + $lang = <<<'TEXT_WRAP' + 'value 1', + 'b' => 2, + 'c' => null, + 'd' => [], + 'e' => '', + 'f' => [ + 'g' => 'value 2', + 'h' => [ + 'i' => 'value 3', + ], + ], + ]; + TEXT_WRAP; + + file_put_contents(self::$languageTestPath . self::$locale . '/Sync.php', $lang); + file_put_contents(self::$languageTestPath . 'ru/Sync.php', $lang); + } + + private function clearGeneratedFiles(): void + { + if (is_file(self::$languageTestPath . self::$locale . '/Sync.php')) { + unlink(self::$languageTestPath . self::$locale . '/Sync.php'); + } + + if (is_file(self::$languageTestPath . 'ru/Sync.php')) { + unlink(self::$languageTestPath . 'ru/Sync.php'); + } + + if (is_dir(self::$languageTestPath . 'test')) { + $files = glob(self::$languageTestPath . 'test/*', GLOB_MARK); + + foreach ($files as $file) { + unlink($file); + } + + rmdir(self::$languageTestPath . 'test'); + } + } +} From e5bb6cff561b22fe1a7401ed598890e55ef59d3f Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 5 Jul 2024 20:11:33 +0300 Subject: [PATCH 03/12] feat: Add array helper `intersectKeyRecursive` --- system/Helpers/Array/ArrayHelper.php | 23 +++ .../ArrayHelperIntersectKeyRecursiveTest.php | 188 ++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 tests/system/Helpers/Array/ArrayHelperIntersectKeyRecursiveTest.php diff --git a/system/Helpers/Array/ArrayHelper.php b/system/Helpers/Array/ArrayHelper.php index f3eff03ba7a2..76d97358d114 100644 --- a/system/Helpers/Array/ArrayHelper.php +++ b/system/Helpers/Array/ArrayHelper.php @@ -315,4 +315,27 @@ public static function sortValuesByNatural(array &$array, $sortByIndex = null): return strnatcmp((string) $currentValue, (string) $nextValue); }); } + + /** + * Returns a new array from $target with the keys that are in $original + * + * @param array|string|null> $target + * @param array|string|null> $original + * + * @return array|string|null> + */ + public static function intersectKeyRecursive(array $target, array $original): array + { + $result = []; + + foreach ($target as $key => $value) { + if (is_array($value) && isset($original[$key]) && is_array($original[$key])) { + $result[$key] = self::intersectKeyRecursive($value, $original[$key]); + } elseif (array_key_exists($key, $original)) { + $result[$key] = $value; + } + } + + return $result; + } } diff --git a/tests/system/Helpers/Array/ArrayHelperIntersectKeyRecursiveTest.php b/tests/system/Helpers/Array/ArrayHelperIntersectKeyRecursiveTest.php new file mode 100644 index 000000000000..a252eb9b0c2f --- /dev/null +++ b/tests/system/Helpers/Array/ArrayHelperIntersectKeyRecursiveTest.php @@ -0,0 +1,188 @@ + + * + * 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; +use PHPUnit\Framework\Attributes\Group; + +/** + * @internal + */ +#[Group('Others')] +final class ArrayHelperIntersectKeyRecursiveTest extends CIUnitTestCase +{ + /** + * @var array|string|null> + */ + private array $targetArray; + + protected function setUp(): void + { + parent::setUp(); + + $this->targetArray = [ + 'a' => [ + 'b' => [ + 'c' => [ + 'd' => 'value1', + ], + ], + 'e' => 'value2', + 'f' => [ + 'g' => 'value3', + 'h' => 'value4', + 'i' => [ + 'j' => 'value5', + ], + ], + 'k' => null, + 'l' => [], + 'm' => '', + ], + ]; + } + + public function testShuffleCopy(): void + { + $original = [ + 'a' => [ + 'l' => [], + 'k' => null, + 'e' => 'value2', + 'f' => [ + 'i' => [ + 'j' => 'value5', + ], + 'h' => 'value4', + 'g' => 'value3', + ], + 'm' => '', + 'b' => [ + 'c' => [ + 'd' => 'value1', + ], + ], + ], + ]; + + // var_dump(ArrayHelper::intersectKeyRecursive($original, $this->targetArray)); + + $this->assertSame($original, ArrayHelper::intersectKeyRecursive($original, $this->targetArray)); + } + + public function testCopyWithAnotherValues(): void + { + $original = [ + 'a' => [ + 'b' => [ + 'c' => [ + 'd' => 'value1_1', + ], + ], + 'e' => 'value2_2', + 'f' => [ + 'g' => 'value3_3', + 'h' => 'value4_4', + 'i' => [ + 'j' => 'value5_5', + ], + ], + 'k' => [], + 'l' => null, + 'm' => 'value6_6', + ], + ]; + + $this->assertSame($original, ArrayHelper::intersectKeyRecursive($original, $this->targetArray)); + } + + public function testEmptyCompare(): void + { + $this->assertSame([], ArrayHelper::intersectKeyRecursive($this->targetArray, [])); + } + + public function testEmptyOriginal(): void + { + $this->assertSame([], ArrayHelper::intersectKeyRecursive([], $this->targetArray)); + } + + public function testCompletelyDifferent(): void + { + $original = [ + 'new_a' => [ + 'new_b' => [ + 'new_c' => [ + 'new_d' => 'value1_1', + ], + ], + 'new_e' => 'value2_2', + 'new_f' => [ + 'new_g' => 'value3_3', + 'new_h' => 'value4_4', + 'new_i' => [ + 'new_j' => 'value5_5', + ], + ], + 'new_k' => [], + 'new_l' => null, + 'new_m' => '', + ], + ]; + + $this->assertSame([], ArrayHelper::intersectKeyRecursive($original, $this->targetArray)); + } + + public function testRecursiveDiffPartlyDifferent(): void + { + $original = [ + 'a' => [ + 'b' => [ + 'new_c' => [ + 'd' => 'value1', + ], + ], + 'e' => 'value2', + 'f' => [ + 'g' => 'value3', + 'new_h' => 'value4', + 'i' => [ + 'new_j' => 'value5', + ], + ], + 'k' => null, + 'new_l' => [], + 'm' => [ + 'new_n' => '', + ], + ], + ]; + + $intersect = [ + 'a' => [ + 'b' => [], + 'e' => 'value2', + 'f' => [ + 'g' => 'value3', + 'i' => [], + ], + 'k' => null, + 'm' => [ + 'new_n' => '', + ], + ], + ]; + + $this->assertSame($intersect, ArrayHelper::intersectKeyRecursive($original, $this->targetArray)); + } +} From e8f6549dbadd85f14622a0a070b9e2e6acb233b6 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 5 Jul 2024 20:11:58 +0300 Subject: [PATCH 04/12] docs: Add changelog --- user_guide_src/source/changelogs/v4.6.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/user_guide_src/source/changelogs/v4.6.0.rst b/user_guide_src/source/changelogs/v4.6.0.rst index d3dc4529d512..305121476de9 100644 --- a/user_guide_src/source/changelogs/v4.6.0.rst +++ b/user_guide_src/source/changelogs/v4.6.0.rst @@ -184,6 +184,7 @@ Commands - The ``spark routes`` and ``spark filter:check`` commands now display filter arguments. - The ``spark filter:check`` command now displays filter classnames. +- The ``spark lang:sync`` command to synchronize translation files. See :ref:`sync-translations-command` Routing ======= From c67616d86bdd2d886540dd44c5865ea378baf903 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 5 Jul 2024 20:12:32 +0300 Subject: [PATCH 05/12] docs: Add description for command --- .../source/outgoing/localization.rst | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/user_guide_src/source/outgoing/localization.rst b/user_guide_src/source/outgoing/localization.rst index c5fba00adbc5..41542af4ad09 100644 --- a/user_guide_src/source/outgoing/localization.rst +++ b/user_guide_src/source/outgoing/localization.rst @@ -366,3 +366,29 @@ Detailed information can be found by running the command: .. code-block:: console php spark lang:find --help + +.. _sync-translations-command: + +Synchronization Translation Files via Command +======================================== + +.. versionadded:: 4.6.0 + +After working on your translation for the current language for a long time, in some cases you will need to create files for another language. +The ``spark lang:find`` command can be used, it is not prohibited. But it cannot fully detect absolutely all translations. Especially if the parameters are dynamically set as ``lang('App.status.' . $key, ['payload' => 'John'], 'en')``. +In this case, the best solution is to copy the finished language files and translate them. This way you can save your unique keys that the command did not find. + +All you need to do is execute: + +.. code-block:: console + + // Specify the locale for new/updated translations + php spark lang:sync --target ru + + // or set the original locale + php spark lang:sync --locale en --target ru + +As a result, you will receive files with the transfer keys. +If there were duplicate keys in the target locale, they are saved. + +.. warning:: Non-matching keys in new translations are deleted! From e1e8931b49cefe05ed5f27e66816df93ef7d540a Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Fri, 5 Jul 2024 20:29:07 +0300 Subject: [PATCH 06/12] fix: Improve condition --- system/Commands/Translation/LocalizationSync.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Commands/Translation/LocalizationSync.php b/system/Commands/Translation/LocalizationSync.php index b2bff0c1d473..f534f2f0889d 100644 --- a/system/Commands/Translation/LocalizationSync.php +++ b/system/Commands/Translation/LocalizationSync.php @@ -150,7 +150,7 @@ private function process(string $originalLocale, string $targetLocale): void private function mergeLanguageKeys(array $originalLanguageKeys, array $targetLanguageKeys, string $prefix = ''): array { foreach ($originalLanguageKeys as $key => $value) { - $placeholderValue = $prefix === '' ? $prefix . '.' . $key : $key; + $placeholderValue = $prefix !== '' ? $prefix . '.' . $key : $key; if (! is_array($value)) { // Keep the old value From 0b89adcef7dfafb07c761db65dc22fee0703320a Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Sat, 6 Jul 2024 12:52:27 +0300 Subject: [PATCH 07/12] fix: docs title underline too short --- user_guide_src/source/outgoing/localization.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/outgoing/localization.rst b/user_guide_src/source/outgoing/localization.rst index 41542af4ad09..8ab24c5a0cda 100644 --- a/user_guide_src/source/outgoing/localization.rst +++ b/user_guide_src/source/outgoing/localization.rst @@ -370,7 +370,7 @@ Detailed information can be found by running the command: .. _sync-translations-command: Synchronization Translation Files via Command -======================================== +============================================== .. versionadded:: 4.6.0 From eeef98085cb3b40e7e2e10877fd914f24ae9f007 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Mon, 5 Aug 2024 08:34:21 +0300 Subject: [PATCH 08/12] fix: typo --- user_guide_src/source/outgoing/localization.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/user_guide_src/source/outgoing/localization.rst b/user_guide_src/source/outgoing/localization.rst index 8ab24c5a0cda..f1ae44282bec 100644 --- a/user_guide_src/source/outgoing/localization.rst +++ b/user_guide_src/source/outgoing/localization.rst @@ -369,8 +369,8 @@ Detailed information can be found by running the command: .. _sync-translations-command: -Synchronization Translation Files via Command -============================================== +Synchronization Translation Files via Command +============================================= .. versionadded:: 4.6.0 @@ -388,7 +388,7 @@ All you need to do is execute: // or set the original locale php spark lang:sync --locale en --target ru -As a result, you will receive files with the transfer keys. +As a result, you will receive files with the translation keys. If there were duplicate keys in the target locale, they are saved. .. warning:: Non-matching keys in new translations are deleted! From 81e570e1d09dd2867462f8b7b1fbe143395927fe Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Mon, 5 Aug 2024 08:34:46 +0300 Subject: [PATCH 09/12] fix: Delete debug info --- .../Helpers/Array/ArrayHelperIntersectKeyRecursiveTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/system/Helpers/Array/ArrayHelperIntersectKeyRecursiveTest.php b/tests/system/Helpers/Array/ArrayHelperIntersectKeyRecursiveTest.php index a252eb9b0c2f..c5c2adfc5e21 100644 --- a/tests/system/Helpers/Array/ArrayHelperIntersectKeyRecursiveTest.php +++ b/tests/system/Helpers/Array/ArrayHelperIntersectKeyRecursiveTest.php @@ -76,8 +76,6 @@ public function testShuffleCopy(): void ], ]; - // var_dump(ArrayHelper::intersectKeyRecursive($original, $this->targetArray)); - $this->assertSame($original, ArrayHelper::intersectKeyRecursive($original, $this->targetArray)); } From 6460df02478486af14f109681123d2ce0434cf41 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Mon, 5 Aug 2024 08:35:26 +0300 Subject: [PATCH 10/12] fix: Remove sorting lang keys --- system/Commands/Translation/LocalizationSync.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Commands/Translation/LocalizationSync.php b/system/Commands/Translation/LocalizationSync.php index f534f2f0889d..38dc8c424bf6 100644 --- a/system/Commands/Translation/LocalizationSync.php +++ b/system/Commands/Translation/LocalizationSync.php @@ -134,7 +134,7 @@ private function process(string $originalLocale, string $targetLocale): void } $targetLanguageKeys = $this->mergeLanguageKeys($originalLanguageKeys, $targetLanguageKeys, $originalLanguageFile->getBasename('.php')); - ksort($targetLanguageKeys); + // ksort($targetLanguageKeys); $content = " Date: Thu, 8 Aug 2024 17:49:16 +0300 Subject: [PATCH 11/12] fix: key sorting --- system/Commands/Translation/LocalizationSync.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/system/Commands/Translation/LocalizationSync.php b/system/Commands/Translation/LocalizationSync.php index 38dc8c424bf6..d8342fa64bc8 100644 --- a/system/Commands/Translation/LocalizationSync.php +++ b/system/Commands/Translation/LocalizationSync.php @@ -15,7 +15,6 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; -use CodeIgniter\Helpers\Array\ArrayHelper; use Config\App; use Locale; use RecursiveDirectoryIterator; @@ -134,7 +133,6 @@ private function process(string $originalLocale, string $targetLocale): void } $targetLanguageKeys = $this->mergeLanguageKeys($originalLanguageKeys, $targetLanguageKeys, $originalLanguageFile->getBasename('.php')); - // ksort($targetLanguageKeys); $content = " $value) { $placeholderValue = $prefix !== '' ? $prefix . '.' . $key : $key; @@ -156,21 +156,25 @@ private function mergeLanguageKeys(array $originalLanguageKeys, array $targetLan // Keep the old value // TODO: The value type may not match the original one if (array_key_exists($key, $targetLanguageKeys)) { + $mergedLanguageKeys[$key] = $targetLanguageKeys[$key]; + continue; } // Set new key with placeholder - $targetLanguageKeys[$key] = $placeholderValue; + $mergedLanguageKeys[$key] = $placeholderValue; } else { if (! array_key_exists($key, $targetLanguageKeys)) { - $targetLanguageKeys[$key] = []; + $mergedLanguageKeys[$key] = $this->mergeLanguageKeys($value, [], $placeholderValue); + + continue; } - $targetLanguageKeys[$key] = $this->mergeLanguageKeys($value, $targetLanguageKeys[$key], $placeholderValue); + $mergedLanguageKeys[$key] = $this->mergeLanguageKeys($value, $targetLanguageKeys[$key], $placeholderValue); } } - return ArrayHelper::intersectKeyRecursive($targetLanguageKeys, $originalLanguageKeys); + return $mergedLanguageKeys; } private function isIgnoredFile(SplFileInfo $file): bool From 435d8d6a6342382d979aaf830a05ae67b62413f2 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Thu, 8 Aug 2024 17:51:49 +0300 Subject: [PATCH 12/12] Remove array helper `intersectKeyRecursive` --- system/Helpers/Array/ArrayHelper.php | 23 --- .../ArrayHelperIntersectKeyRecursiveTest.php | 186 ------------------ 2 files changed, 209 deletions(-) delete mode 100644 tests/system/Helpers/Array/ArrayHelperIntersectKeyRecursiveTest.php diff --git a/system/Helpers/Array/ArrayHelper.php b/system/Helpers/Array/ArrayHelper.php index 76d97358d114..f3eff03ba7a2 100644 --- a/system/Helpers/Array/ArrayHelper.php +++ b/system/Helpers/Array/ArrayHelper.php @@ -315,27 +315,4 @@ public static function sortValuesByNatural(array &$array, $sortByIndex = null): return strnatcmp((string) $currentValue, (string) $nextValue); }); } - - /** - * Returns a new array from $target with the keys that are in $original - * - * @param array|string|null> $target - * @param array|string|null> $original - * - * @return array|string|null> - */ - public static function intersectKeyRecursive(array $target, array $original): array - { - $result = []; - - foreach ($target as $key => $value) { - if (is_array($value) && isset($original[$key]) && is_array($original[$key])) { - $result[$key] = self::intersectKeyRecursive($value, $original[$key]); - } elseif (array_key_exists($key, $original)) { - $result[$key] = $value; - } - } - - return $result; - } } diff --git a/tests/system/Helpers/Array/ArrayHelperIntersectKeyRecursiveTest.php b/tests/system/Helpers/Array/ArrayHelperIntersectKeyRecursiveTest.php deleted file mode 100644 index c5c2adfc5e21..000000000000 --- a/tests/system/Helpers/Array/ArrayHelperIntersectKeyRecursiveTest.php +++ /dev/null @@ -1,186 +0,0 @@ - - * - * 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; -use PHPUnit\Framework\Attributes\Group; - -/** - * @internal - */ -#[Group('Others')] -final class ArrayHelperIntersectKeyRecursiveTest extends CIUnitTestCase -{ - /** - * @var array|string|null> - */ - private array $targetArray; - - protected function setUp(): void - { - parent::setUp(); - - $this->targetArray = [ - 'a' => [ - 'b' => [ - 'c' => [ - 'd' => 'value1', - ], - ], - 'e' => 'value2', - 'f' => [ - 'g' => 'value3', - 'h' => 'value4', - 'i' => [ - 'j' => 'value5', - ], - ], - 'k' => null, - 'l' => [], - 'm' => '', - ], - ]; - } - - public function testShuffleCopy(): void - { - $original = [ - 'a' => [ - 'l' => [], - 'k' => null, - 'e' => 'value2', - 'f' => [ - 'i' => [ - 'j' => 'value5', - ], - 'h' => 'value4', - 'g' => 'value3', - ], - 'm' => '', - 'b' => [ - 'c' => [ - 'd' => 'value1', - ], - ], - ], - ]; - - $this->assertSame($original, ArrayHelper::intersectKeyRecursive($original, $this->targetArray)); - } - - public function testCopyWithAnotherValues(): void - { - $original = [ - 'a' => [ - 'b' => [ - 'c' => [ - 'd' => 'value1_1', - ], - ], - 'e' => 'value2_2', - 'f' => [ - 'g' => 'value3_3', - 'h' => 'value4_4', - 'i' => [ - 'j' => 'value5_5', - ], - ], - 'k' => [], - 'l' => null, - 'm' => 'value6_6', - ], - ]; - - $this->assertSame($original, ArrayHelper::intersectKeyRecursive($original, $this->targetArray)); - } - - public function testEmptyCompare(): void - { - $this->assertSame([], ArrayHelper::intersectKeyRecursive($this->targetArray, [])); - } - - public function testEmptyOriginal(): void - { - $this->assertSame([], ArrayHelper::intersectKeyRecursive([], $this->targetArray)); - } - - public function testCompletelyDifferent(): void - { - $original = [ - 'new_a' => [ - 'new_b' => [ - 'new_c' => [ - 'new_d' => 'value1_1', - ], - ], - 'new_e' => 'value2_2', - 'new_f' => [ - 'new_g' => 'value3_3', - 'new_h' => 'value4_4', - 'new_i' => [ - 'new_j' => 'value5_5', - ], - ], - 'new_k' => [], - 'new_l' => null, - 'new_m' => '', - ], - ]; - - $this->assertSame([], ArrayHelper::intersectKeyRecursive($original, $this->targetArray)); - } - - public function testRecursiveDiffPartlyDifferent(): void - { - $original = [ - 'a' => [ - 'b' => [ - 'new_c' => [ - 'd' => 'value1', - ], - ], - 'e' => 'value2', - 'f' => [ - 'g' => 'value3', - 'new_h' => 'value4', - 'i' => [ - 'new_j' => 'value5', - ], - ], - 'k' => null, - 'new_l' => [], - 'm' => [ - 'new_n' => '', - ], - ], - ]; - - $intersect = [ - 'a' => [ - 'b' => [], - 'e' => 'value2', - 'f' => [ - 'g' => 'value3', - 'i' => [], - ], - 'k' => null, - 'm' => [ - 'new_n' => '', - ], - ], - ]; - - $this->assertSame($intersect, ArrayHelper::intersectKeyRecursive($original, $this->targetArray)); - } -}