diff --git a/src/Commands/ExportTranslations.php b/src/Commands/ExportTranslations.php new file mode 100644 index 0000000..71fcc92 --- /dev/null +++ b/src/Commands/ExportTranslations.php @@ -0,0 +1,41 @@ +option('dir')); + $tM->load(); + + $this->info('Lookup directory: '.$tM->getRootLocalePath()); + $this->info('Export directory: '.$this->option('output')); + + $tM->createExportFile($this->option('output')); + + return $this->exitCode; + } +} diff --git a/src/Commands/ValidateTranslations.php b/src/Commands/ValidateTranslations.php index 56daec5..0ad5ccb 100644 --- a/src/Commands/ValidateTranslations.php +++ b/src/Commands/ValidateTranslations.php @@ -4,21 +4,18 @@ namespace Fschirinzi\TranslationManager\Commands; +use Fschirinzi\TranslationManager\Support\TranslationManager; use Illuminate\Console\Command; -use Illuminate\Support\Facades\File; -use Symfony\Component\Finder\Exception\DirectoryNotFoundException; class ValidateTranslations extends Command { - private const DEFAULT_LANG_DIRNAME = 'lang'; - private $locales = []; - private $translations; - /** * The name and signature of the console command. * @var string */ - protected $signature = 'translations:validate {--dir= : Relative path to lang directory (e.g. "/resources/lang")}'; + protected $signature = 'translations:validate + {--dir= : Relative path to lang directory (e.g. "/resources/lang")} + {--s|separator=. : Character to separate nested keys}'; /** * The console command description. @@ -31,122 +28,23 @@ class ValidateTranslations extends Command /** @inheritDoc */ public function handle(): int { - if ($this->option('dir') === null) { - $pathToLocates = resource_path(self::DEFAULT_LANG_DIRNAME); - } elseif (File::isDirectory($this->option('dir'))) { - $pathToLocates = $this->option('dir'); - } elseif (File::isDirectory(base_path($this->option('dir')))) { - $pathToLocates = base_path($this->option('dir')); - } else { - throw new DirectoryNotFoundException("Specified resource directory {$this->option('dir')} does not exist."); - } - - $this->translations = collect(); - $localeDirectories = File::directories($pathToLocates); - - $this->loadAllLocales($localeDirectories); - - $this->validateMissingLocales(); + $tM = new TranslationManager($this->option('dir')); + $tM->setSeparator($this->option('separator')); + $tM->load(); - $this->info('Lookup directory: '.$pathToLocates); + $this->info('Lookup directory: '.$tM->getRootLocalePath()); - $itemsThatMissTranslation = $this->translations->where('missingIn'); + $itemsThatMissTranslation = $tM->getTranslations(true); if ($itemsThatMissTranslation->count()) { - $itemsThatMissTranslation = $itemsThatMissTranslation->map(function ($item) { - return $item - ->put('missingIn', join(',', $item->get('missingIn'))) - ->put('foundIn', join(',', $item->get('foundIn'))) - ->only(['file', 'key', 'foundIn', 'missingIn']); - }) - ->sortBy('key')->sortBy('file') // Reverse sort; see https://github.com/laravel/ideas/issues/11 - ->toArray(); - - $this->table(['File', 'Key', 'Found in', 'Missing in'], $itemsThatMissTranslation); + $this->exitCode = 1; + $this->table( + ['File', 'Key', 'Found in', 'Missing in'], + $tM->formatItemsForConsoleTable($itemsThatMissTranslation) + ); } $this->info('Successfully compared all languages.'); return $this->exitCode; } - - private function loadAllLocales($localeDirectories) - { - foreach ($localeDirectories as $localeDirectory) { - $rootLocalePath = dirname($localeDirectory); - $locale = basename($localeDirectory); - if (! in_array($locale, $this->locales)) { - array_push($this->locales, $locale); - } - - $baseLocaleDirectoryPath = $localeDirectory; - $baseLocaleFiles = $this->getFilenames($baseLocaleDirectoryPath); - - foreach ($baseLocaleFiles as $file) { - $loadedLocale = File::getRequire("{$baseLocaleDirectoryPath}/{$file}"); - $keys = $this->getAbolsutePathRecursive($loadedLocale); - - foreach ($keys as $key) { - $item = $this->translations - ->where('folder', $rootLocalePath) - ->where('file', $file) - ->where('key', $key) - ->first(); - - if ($item) { - $item->put('foundIn', array_merge($item->get('foundIn'), [$locale])); - } else { - $this->translations->push(collect([ - 'folder' => $rootLocalePath, - 'file' => $file, - 'key' => $key, - 'foundIn' => [$locale], - ])); - } - } - } - } - } - - private function getAbolsutePathRecursive($arr, $parentKey = null) - { - $keys = []; - - foreach ($arr as $key => $val) { - if (is_array($val)) { - $keys = array_merge($keys, $this->getAbolsutePathRecursive($val, $key)); - } else { - array_push($keys, ($parentKey ? "{$parentKey}." : '').$key); - } - } - - return $keys; - } - - private function validateMissingLocales() - { - $this->translations->each(function ($item) { - $item->put('missingIn', array_diff($this->locales, $item->get('foundIn'))); - }); - - if ($this->translations->where('missingIn')->count()) { - $this->exitCode = 1; - } - } - - /** - * Get filenames of directory. - */ - private function getFilenames(string $directory): array - { - $fileNames = []; - - /** @var \Symfony\Component\Finder\SplFileInfo[] $filesInFolder */ - $filesInFolder = File::allFiles($directory); - - foreach ($filesInFolder as $fileInfo) { - $fileNames[] = $fileInfo->getRelativePathname(); - } - - return $fileNames; - } } diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index b1fc318..35363e7 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -4,6 +4,7 @@ namespace Fschirinzi\TranslationManager; +use Fschirinzi\TranslationManager\Commands\ExportTranslations; use Fschirinzi\TranslationManager\Commands\ValidateTranslations; use Illuminate\Support\ServiceProvider as BaseServiceProvider; @@ -18,6 +19,7 @@ public function boot() if ($this->app->runningInConsole()) { $this->commands([ ValidateTranslations::class, + ExportTranslations::class, ]); } } diff --git a/src/Support/TranslationManager.php b/src/Support/TranslationManager.php new file mode 100644 index 0000000..7c36ba1 --- /dev/null +++ b/src/Support/TranslationManager.php @@ -0,0 +1,236 @@ +translations = collect(); + $this->setSeparator(''); + $this->setRootLocalePath($rootLocalePath); + } + + /** + * @return Collection + */ + public function getTranslations(bool $onlyMissingTranslation): Collection + { + return $this->translations + ->when($onlyMissingTranslation, function ($q) { + return $q->where('missingIn'); + }); + } + + /** + * @return string + */ + public function getSeparator(): string + { + return $this->separator; + } + + /** + * @param string $separator + */ + public function setSeparator(string $separator): void + { + $this->separator = $separator == '' + ? self::DEFAULT_KEY_SEPARATOR + : $separator; + } + + /** + * @return string + */ + public function getRootLocalePath(): string + { + return $this->rootLocalePath; + } + + /** + * @param string $rootLocalePath + */ + public function setRootLocalePath(string $rootLocalePath): void + { + if ($rootLocalePath == '') { + $this->rootLocalePath = resource_path(self::DEFAULT_LANG_DIRNAME); + + return; + } + + if (File::isDirectory($rootLocalePath)) { + $this->rootLocalePath = $rootLocalePath; + + return; + } + + if (File::isDirectory(base_path($rootLocalePath))) { + $this->rootLocalePath = base_path($rootLocalePath); + + return; + } + + throw new DirectoryNotFoundException("Specified resource directory {$rootLocalePath} does not exist."); + } + + public function load() + { + $this->parseDirectories(File::directories($this->getRootLocalePath())); + $this->validateMissingLocales(); + } + + public function formatItemsForConsoleTable(Collection $items) + { + return $items->map(function ($item) { + return $item + ->put('missingIn', join(',', $item->get('missingIn'))) + ->put('foundIn', join(',', $item->get('foundIn'))) + ->only(['file', 'key', 'foundIn', 'missingIn']); + })->toArray(); + } + + public function getItemsForExport(): Collection + { + return $this->getTranslations(false) + ->map(function ($item) { + foreach ($item->get('translations') as $key => $translation) { + $item->put("translation_{$key}", $translation); + } + $item->forget('folder'); + $item->forget('translations'); + $item->forget('foundIn'); + $item->forget('missingIn'); + + return $item; + }); + } + + public function createExportFile($outputPath) + { + $items = $this->getItemsForExport(); + $headers = $items->map->keys()->flatten()->unique()->toArray(); + + $fp = fopen($outputPath, 'w'); + + fputcsv($fp, $headers); + foreach ($items as $row) { + $formattedRow = collect(); + foreach ($headers as $header) { + $formattedRow->put($header, $row->get($header, '')); + } + fputcsv($fp, $formattedRow->toArray()); + } + + fclose($fp); + } + + public function parseDirectories($localeDirectories) + { + foreach ($localeDirectories as $localeDirectory) { + $rootLocalePath = dirname($localeDirectory); + $locale = basename($localeDirectory); + if (! in_array($locale, $this->locales)) { + array_push($this->locales, $locale); + } + + $baseLocaleDirectoryPath = $localeDirectory; + $baseLocaleFiles = $this->getFilenames($baseLocaleDirectoryPath); + + foreach ($baseLocaleFiles as $file) { + $loadedLocale = File::getRequire("{$baseLocaleDirectoryPath}/{$file}"); + $keys = $this->getAbolsutePathRecursive($loadedLocale); + + foreach ($keys as $key) { + $item = $this->translations + ->where('folder', $rootLocalePath) + ->where('file', $file) + ->where('key', $key) + ->first(); + + $pathAsArray = explode($this->getSeparator(), $key); + $translation = $this->getNestedItems($loadedLocale, $pathAsArray); + + if ($item) { + $item->put('foundIn', array_merge($item->get('foundIn'), [$locale])); + $item->get('translations')->put($locale, $translation); + } else { + $this->translations->push(collect([ + 'folder' => $rootLocalePath, + 'file' => $file, + 'key' => $key, + 'foundIn' => [$locale], + 'translations' => collect([$locale => $translation]), + ])); + } + } + } + } + + $this->translations + ->sortBy('key') + ->sortBy('file'); // Reverse sort; see https://github.com/laravel/ideas/issues/11 + } + + private function validateMissingLocales() + { + $this->translations->each(function ($item) { + $item->put('missingIn', array_diff($this->locales, $item->get('foundIn'))); + }); + } + + private function getAbolsutePathRecursive($arr, $parentKey = null) + { + $keys = []; + + foreach ($arr as $key => $val) { + if (is_array($val)) { + $keys = array_merge($keys, $this->getAbolsutePathRecursive($val, $key)); + } else { + array_push($keys, ($parentKey ? "{$parentKey}{$this->getSeparator()}" : '').$key); + } + } + + return $keys; + } + + /** + * Get filenames of directory. + */ + private function getFilenames(string $directory): array + { + $fileNames = []; + + /** @var \Symfony\Component\Finder\SplFileInfo[] $filesInFolder */ + $filesInFolder = File::allFiles($directory); + + foreach ($filesInFolder as $fileInfo) { + $fileNames[] = $fileInfo->getRelativePathname(); + } + + return $fileNames; + } + + public function getNestedItems($input, $levels = []) + { + $output = $input; + + foreach ($levels as $level) { + $output = $output[$level]; + } + + return $output; + } +} diff --git a/tests/Commands/locale-country/unsync_lang_files/de/b.php b/tests/Commands/locale-country/unsync_lang_files/de/b.php deleted file mode 100644 index 43f9920..0000000 --- a/tests/Commands/locale-country/unsync_lang_files/de/b.php +++ /dev/null @@ -1,5 +0,0 @@ - 'Ja', -]; diff --git a/tests/Commands/sync_lang_files/en/sub-dir/sub-file.php b/tests/Commands/sync_lang_files/en/sub-dir/sub-file.php deleted file mode 100644 index caca41b..0000000 --- a/tests/Commands/sync_lang_files/en/sub-dir/sub-file.php +++ /dev/null @@ -1,9 +0,0 @@ - 'Так', - - 'group' => [ - 'Help' => 'Дапамога', - ], -]; diff --git a/tests/Integration/Commands/ExportTranslationsTest.php b/tests/Integration/Commands/ExportTranslationsTest.php new file mode 100644 index 0000000..6c08793 --- /dev/null +++ b/tests/Integration/Commands/ExportTranslationsTest.php @@ -0,0 +1,27 @@ +withoutMockingConsoleOutput(); + + $dir = __DIR__.'/../../Templates/locale-country/unsync_lang_files'; + $tmpOutputDir = '/tmp'; + $tmpOutputFilepath = $tmpOutputDir.'/locale-country_unsync_lang_files.csv'; + $exitCode = $this->artisan( + 'translations:export', ['--dir' => $dir, '-o' => $tmpOutputFilepath] + ); + + $original = __DIR__.'/../../Templates/Exports/locale-country_unsync_lang_files.csv'; + $this->assertEquals(file_get_contents($original), file_get_contents($tmpOutputFilepath)); + $this->assertSame(0, $exitCode); + } +} diff --git a/tests/Commands/ValidateTranslationsTest.php b/tests/Integration/Commands/ValidateTranslationsTest.php similarity index 81% rename from tests/Commands/ValidateTranslationsTest.php rename to tests/Integration/Commands/ValidateTranslationsTest.php index bc74b7d..cc89579 100644 --- a/tests/Commands/ValidateTranslationsTest.php +++ b/tests/Integration/Commands/ValidateTranslationsTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Fschirinzi\TranslationManager\Tests\Commands; +namespace Fschirinzi\TranslationManager\Tests\Integration\Commands; use Fschirinzi\TranslationManager\Tests\TestCase; use Illuminate\Support\Facades\Artisan; @@ -14,7 +14,7 @@ public function it_does_not_report_about_synchronized_files() { $this->withoutMockingConsoleOutput(); - $dir = __DIR__.'/sync_lang_files'; + $dir = __DIR__.'/../../Templates/sync_lang_files'; $exitCode = $this->artisan('translations:validate', ['--dir' => $dir]); $output = Artisan::output(); @@ -27,7 +27,7 @@ public function it_reports_about_missing_translation_keys() { $this->withoutMockingConsoleOutput(); - $dir = __DIR__.'/unsync_lang_files'; + $dir = __DIR__.'/../../Templates/unsync_lang_files'; $exitCode = $this->artisan('translations:validate', ['--dir' => $dir]); $output = Artisan::output(); @@ -42,7 +42,7 @@ public function it_does_not_report_about_missing_translation_keys_by_locale_coun { $this->withoutMockingConsoleOutput(); - $dir = __DIR__.'/locale-country/sync_lang_files'; + $dir = __DIR__.'/../../Templates/locale-country/sync_lang_files'; $exitCode = $this->artisan('translations:validate', ['--dir' => $dir]); $output = Artisan::output(); @@ -55,18 +55,16 @@ public function it_reports_about_missing_translation_keys_by_locale_country_comb { $this->withoutMockingConsoleOutput(); - $dir = __DIR__.'/locale-country/unsync_lang_files'; + $dir = __DIR__.'/../../Templates/locale-country/unsync_lang_files'; $exitCode = $this->artisan('translations:validate', ['--dir' => $dir]); $output = Artisan::output(); $this->assertSame(1, $exitCode); - $this->assertStringContainsString('| a.php | OK | en | de,de-CH,fr-CH |', $output); - $this->assertStringContainsString('| a.php | only_in_de | de | de-CH,en,fr-CH |', $output); - $this->assertStringContainsString('| a.php | only_in_de-CH | de-CH | de,en,fr-CH |', $output); - $this->assertStringContainsString('| a.php | only_in_en | en | de,de-CH,fr-CH |', $output); - $this->assertStringContainsString('| a.php | only_in_fr-CH | fr-CH | de,de-CH,en |', $output); - - $this->assertStringContainsString('| b.php | CheckMe | de | de-CH,en,fr-CH |', $output); + $this->assertStringContainsString('| a.php | only_in_de | de | de-CH,en,fr-CH |', $output); + $this->assertStringContainsString('| a.php | only_in_de-CH | de-CH | de,en,fr-CH |', $output); + $this->assertStringContainsString('| a.php | only_in_en | en | de,de-CH,fr-CH |', $output); + $this->assertStringContainsString('| a.php | only_in_fr-CH | fr-CH | de,de-CH,en |', $output); + $this->assertStringContainsString('| b.php | File_only_in_de | de | de-CH,en,fr-CH |', $output); } /** @test */ @@ -74,7 +72,7 @@ public function it_does_not_report_about_missing_translation_from_children_direc { $this->withoutMockingConsoleOutput(); - $dir = __DIR__.'/sync_sub_dirs'; + $dir = __DIR__.'/../../Templates/sync_sub_dirs'; $exitCode = $this->artisan('translations:validate', ['--dir' => $dir]); $output = Artisan::output(); @@ -89,7 +87,7 @@ public function it_reports_about_missing_translation_keys_from_children_director $DS = \DIRECTORY_SEPARATOR; - $dir = __DIR__.'/unsync_sub_dirs'; + $dir = __DIR__.'/../../Templates/unsync_sub_dirs'; $exitCode = $this->artisan('translations:validate', ['--dir' => $dir]); $output = Artisan::output(); diff --git a/tests/Templates/Exports/locale-country_unsync_lang_files.csv b/tests/Templates/Exports/locale-country_unsync_lang_files.csv new file mode 100644 index 0000000..0366d2e --- /dev/null +++ b/tests/Templates/Exports/locale-country_unsync_lang_files.csv @@ -0,0 +1,8 @@ +file,key,translation_de,translation_de-CH,translation_en,translation_fr-CH +a.php,Yes,Ja,Ja,Yes,Oui +a.php,No,Nein,Nein,No,Non +a.php,only_in_de,only_in_de,,, +b.php,File_only_in_de,Ja,,, +a.php,only_in_de-CH,,only_in_de-CH,, +a.php,only_in_en,,,only_in_en, +a.php,only_in_fr-CH,,,,only_in_fr-CH diff --git a/tests/Commands/locale-country/sync_lang_files/de-CH/a.php b/tests/Templates/locale-country/sync_lang_files/de-CH/a.php similarity index 100% rename from tests/Commands/locale-country/sync_lang_files/de-CH/a.php rename to tests/Templates/locale-country/sync_lang_files/de-CH/a.php diff --git a/tests/Commands/locale-country/sync_lang_files/de/a.php b/tests/Templates/locale-country/sync_lang_files/de/a.php similarity index 100% rename from tests/Commands/locale-country/sync_lang_files/de/a.php rename to tests/Templates/locale-country/sync_lang_files/de/a.php diff --git a/tests/Commands/locale-country/sync_lang_files/en/a.php b/tests/Templates/locale-country/sync_lang_files/en/a.php similarity index 100% rename from tests/Commands/locale-country/sync_lang_files/en/a.php rename to tests/Templates/locale-country/sync_lang_files/en/a.php diff --git a/tests/Commands/locale-country/sync_lang_files/fr-CH/a.php b/tests/Templates/locale-country/sync_lang_files/fr-CH/a.php similarity index 100% rename from tests/Commands/locale-country/sync_lang_files/fr-CH/a.php rename to tests/Templates/locale-country/sync_lang_files/fr-CH/a.php diff --git a/tests/Commands/locale-country/unsync_lang_files/de-CH/a.php b/tests/Templates/locale-country/unsync_lang_files/de-CH/a.php similarity index 100% rename from tests/Commands/locale-country/unsync_lang_files/de-CH/a.php rename to tests/Templates/locale-country/unsync_lang_files/de-CH/a.php diff --git a/tests/Commands/locale-country/unsync_lang_files/de/a.php b/tests/Templates/locale-country/unsync_lang_files/de/a.php similarity index 100% rename from tests/Commands/locale-country/unsync_lang_files/de/a.php rename to tests/Templates/locale-country/unsync_lang_files/de/a.php diff --git a/tests/Templates/locale-country/unsync_lang_files/de/b.php b/tests/Templates/locale-country/unsync_lang_files/de/b.php new file mode 100644 index 0000000..86a1b9a --- /dev/null +++ b/tests/Templates/locale-country/unsync_lang_files/de/b.php @@ -0,0 +1,5 @@ + 'Ja', +]; diff --git a/tests/Commands/locale-country/unsync_lang_files/en/a.php b/tests/Templates/locale-country/unsync_lang_files/en/a.php similarity index 83% rename from tests/Commands/locale-country/unsync_lang_files/en/a.php rename to tests/Templates/locale-country/unsync_lang_files/en/a.php index 26d1e38..3c68513 100644 --- a/tests/Commands/locale-country/unsync_lang_files/en/a.php +++ b/tests/Templates/locale-country/unsync_lang_files/en/a.php @@ -3,7 +3,6 @@ return [ 'Yes' => 'Yes', 'No' => 'No', - 'OK' => 'OK', 'only_in_en' => 'only_in_en', ]; diff --git a/tests/Commands/locale-country/unsync_lang_files/fr-CH/a.php b/tests/Templates/locale-country/unsync_lang_files/fr-CH/a.php similarity index 100% rename from tests/Commands/locale-country/unsync_lang_files/fr-CH/a.php rename to tests/Templates/locale-country/unsync_lang_files/fr-CH/a.php diff --git a/tests/Commands/sync_lang_files/be/a.php b/tests/Templates/sync_lang_files/be/a.php similarity index 100% rename from tests/Commands/sync_lang_files/be/a.php rename to tests/Templates/sync_lang_files/be/a.php diff --git a/tests/Commands/sync_lang_files/be/sub-dir/sub-file.php b/tests/Templates/sync_lang_files/be/sub-dir/sub-file.php similarity index 100% rename from tests/Commands/sync_lang_files/be/sub-dir/sub-file.php rename to tests/Templates/sync_lang_files/be/sub-dir/sub-file.php diff --git a/tests/Commands/sync_lang_files/en/a.php b/tests/Templates/sync_lang_files/en/a.php similarity index 100% rename from tests/Commands/sync_lang_files/en/a.php rename to tests/Templates/sync_lang_files/en/a.php diff --git a/tests/Templates/sync_lang_files/en/sub-dir/sub-file.php b/tests/Templates/sync_lang_files/en/sub-dir/sub-file.php new file mode 100644 index 0000000..301a031 --- /dev/null +++ b/tests/Templates/sync_lang_files/en/sub-dir/sub-file.php @@ -0,0 +1,9 @@ + 'Yes', + + 'group' => [ + 'Help' => 'Help', + ], +]; diff --git a/tests/Commands/sync_sub_dirs/be/a.php b/tests/Templates/sync_sub_dirs/be/a.php similarity index 100% rename from tests/Commands/sync_sub_dirs/be/a.php rename to tests/Templates/sync_sub_dirs/be/a.php diff --git a/tests/Commands/sync_sub_dirs/be/sub-dir/sub-file.php b/tests/Templates/sync_sub_dirs/be/sub-dir/sub-file.php similarity index 100% rename from tests/Commands/sync_sub_dirs/be/sub-dir/sub-file.php rename to tests/Templates/sync_sub_dirs/be/sub-dir/sub-file.php diff --git a/tests/Commands/sync_sub_dirs/be/sub-dir/sub-sub-dir/sub-sub-file.php b/tests/Templates/sync_sub_dirs/be/sub-dir/sub-sub-dir/sub-sub-file.php similarity index 100% rename from tests/Commands/sync_sub_dirs/be/sub-dir/sub-sub-dir/sub-sub-file.php rename to tests/Templates/sync_sub_dirs/be/sub-dir/sub-sub-dir/sub-sub-file.php diff --git a/tests/Commands/sync_sub_dirs/en/a.php b/tests/Templates/sync_sub_dirs/en/a.php similarity index 100% rename from tests/Commands/sync_sub_dirs/en/a.php rename to tests/Templates/sync_sub_dirs/en/a.php diff --git a/tests/Commands/sync_sub_dirs/en/sub-dir/sub-file.php b/tests/Templates/sync_sub_dirs/en/sub-dir/sub-file.php similarity index 100% rename from tests/Commands/sync_sub_dirs/en/sub-dir/sub-file.php rename to tests/Templates/sync_sub_dirs/en/sub-dir/sub-file.php diff --git a/tests/Commands/sync_sub_dirs/en/sub-dir/sub-sub-dir/sub-sub-file.php b/tests/Templates/sync_sub_dirs/en/sub-dir/sub-sub-dir/sub-sub-file.php similarity index 100% rename from tests/Commands/sync_sub_dirs/en/sub-dir/sub-sub-dir/sub-sub-file.php rename to tests/Templates/sync_sub_dirs/en/sub-dir/sub-sub-dir/sub-sub-file.php diff --git a/tests/Commands/unsync_lang_files/be/a.php b/tests/Templates/unsync_lang_files/be/a.php similarity index 100% rename from tests/Commands/unsync_lang_files/be/a.php rename to tests/Templates/unsync_lang_files/be/a.php diff --git a/tests/Commands/unsync_lang_files/en/a.php b/tests/Templates/unsync_lang_files/en/a.php similarity index 100% rename from tests/Commands/unsync_lang_files/en/a.php rename to tests/Templates/unsync_lang_files/en/a.php diff --git a/tests/Commands/unsync_sub_dirs/be/a.php b/tests/Templates/unsync_sub_dirs/be/a.php similarity index 100% rename from tests/Commands/unsync_sub_dirs/be/a.php rename to tests/Templates/unsync_sub_dirs/be/a.php diff --git a/tests/Commands/unsync_sub_dirs/en/a.php b/tests/Templates/unsync_sub_dirs/en/a.php similarity index 100% rename from tests/Commands/unsync_sub_dirs/en/a.php rename to tests/Templates/unsync_sub_dirs/en/a.php diff --git a/tests/Commands/unsync_sub_dirs/en/sub-dir/sub-file.php b/tests/Templates/unsync_sub_dirs/en/sub-dir/sub-file.php similarity index 100% rename from tests/Commands/unsync_sub_dirs/en/sub-dir/sub-file.php rename to tests/Templates/unsync_sub_dirs/en/sub-dir/sub-file.php diff --git a/tests/Commands/unsync_sub_dirs/en/sub-dir/sub-sub-dir/sub-sub-file.php b/tests/Templates/unsync_sub_dirs/en/sub-dir/sub-sub-dir/sub-sub-file.php similarity index 100% rename from tests/Commands/unsync_sub_dirs/en/sub-dir/sub-sub-dir/sub-sub-file.php rename to tests/Templates/unsync_sub_dirs/en/sub-dir/sub-sub-dir/sub-sub-file.php