diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 829b83b..b936463 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -2,7 +2,7 @@ name: run-tests -on: [pull_request] +on: [ pull_request ] jobs: build: @@ -10,8 +10,8 @@ jobs: strategy: fail-fast: false matrix: - php: [8.3, 8.2] - laravel: [11.*] + php: [ 8.3, 8.2 ] + laravel: [ 11.* ] dependency-version: [ prefer-lowest, prefer-stable ] include: - laravel: 11.* diff --git a/src/TranslationsManager.php b/src/TranslationsManager.php index e069dc5..e8d83df 100644 --- a/src/TranslationsManager.php +++ b/src/TranslationsManager.php @@ -78,19 +78,20 @@ public function getTranslations(string $locale): array ->when($this->filesystem->exists(lang_path($rootFileName)), function ($collection) use ($rootFileName) { return $collection->prepend($rootFileName); }) - ->filter(function ($file) { + ->filter(function ($file) use ($locale) { foreach (config('translations.exclude_files') as $excludeFile) { - if (fnmatch($excludeFile, $file)) { - return false; - } - if (fnmatch($excludeFile, basename($file))) { + + /** + *

File exclusion by wildcard

+ *

$file is with language like en/book/create.php while $excludedFile contains only wildcards or path like book/create.php

+ *

So, we need to remove the language part from $file before comparing with $excludeFile

+ */ + if (fnmatch($excludeFile, str_replace($locale.'/', '', $file))) { return false; } - - return true; } - return ! in_array($file, config('translations.exclude_files')); + return true; }) ->filter(function ($file) { return $this->filesystem->extension($file) == 'php' || $this->filesystem->extension($file) == 'json'; @@ -112,6 +113,36 @@ public function getTranslations(string $locale): array return $translations; } + public function download(): ?string + { + try { + $this->export(download: true); + + $zip = new ZipArchive(); + + $zipPath = storage_path('app/lang.zip'); + $zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE); + + $baseDir = storage_path('app/translations'); + $zip->addEmptyDir('lang'); + + $files = $this->filesystem->allFiles($baseDir); + + foreach ($files as $file) { + $relativePath = str_replace($baseDir.DIRECTORY_SEPARATOR, '', $file->getPathname()); + $zip->addFile($file->getPathname(), 'lang/'.$relativePath); + } + + $zip->close(); + + return $zipPath; + } catch (Exception $e) { + logger()->error($e->getMessage()); + + return null; + } + } + public function export($download = false): void { $translations = Translation::with('phrases')->get(); @@ -150,34 +181,4 @@ public function export($download = false): void } } } - - public function download(): ?string - { - try { - $this->export(download: true); - - $zip = new ZipArchive(); - - $zipPath = storage_path('app/lang.zip'); - $zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE); - - $baseDir = storage_path('app/translations'); - $zip->addEmptyDir('lang'); - - $files = $this->filesystem->allFiles($baseDir); - - foreach ($files as $file) { - $relativePath = str_replace($baseDir.DIRECTORY_SEPARATOR, '', $file->getPathname()); - $zip->addFile($file->getPathname(), 'lang/'.$relativePath); - } - - $zip->close(); - - return $zipPath; - } catch (Exception $e) { - logger()->error($e->getMessage()); - - return null; - } - } } diff --git a/tests/TranslationsManagerTest.php b/tests/TranslationsManagerTest.php index 13ea9af..35d37ab 100644 --- a/tests/TranslationsManagerTest.php +++ b/tests/TranslationsManagerTest.php @@ -24,6 +24,9 @@ createPhpLanguageFile('de/validation.php', []); + // Create nested folder structure + createPhpLanguageFile('en/book/create.php', []); + $translationsManager = new TranslationsManager(new Filesystem()); $locales = $translationsManager->getLocales(); @@ -41,7 +44,16 @@ 'title' => 'My title', ]); - Config::set('translations.exclude_files', ['validation.php']); + // Update to include nested folder + createPhpLanguageFile('en/book/create.php', [ + 'nested' => 'Nested test', + ]); + + createPhpLanguageFile('en/book/excluded.php', [ + 'nested' => 'Nested test', + ]); + + Config::set('translations.exclude_files', ['validation.php', 'book/excluded.php']); Config::set('translations.source_language', 'en'); $filesystem = new Filesystem(); @@ -50,12 +62,14 @@ expect($translations)->toBe([ 'en.json' => ['title' => 'My title'], 'en/auth.php' => ['test' => 'Test'], + 'en/book/create.php' => ['nested' => 'Nested test'], ]); $translations = $translationsManager->getTranslations(''); expect($translations)->toBe([ 'en.json' => ['title' => 'My title'], 'en/auth.php' => ['test' => 'Test'], + 'en/book/create.php' => ['nested' => 'Nested test'], ]); }); @@ -70,6 +84,11 @@ 'title' => 'My title', ]); + // Update to include nested folder + createPhpLanguageFile('en/book/create.php', [ + 'nested' => 'Nested test', + ]); + Config::set('translations.exclude_files', ['*.php']); Config::set('translations.source_language', 'en'); $filesystem = new Filesystem(); @@ -89,6 +108,8 @@ test('export creates a new translation file with the correct content', function () { $filesystem = new Filesystem(); createDirectoryIfNotExits(lang_path('en/auth.php')); + // Update to include nested folder + createDirectoryIfNotExits(lang_path('en/book/create.php')); $translation = Translation::factory([ 'source' => true, @@ -104,16 +125,35 @@ ]), ]))->create(); + $nestedTranslation = Translation::factory([ + 'source' => true, + 'language_id' => Language::factory([ + 'code' => 'en', + 'name' => 'English', + ]), + ])->has(Phrase::factory()->state([ + 'phrase_id' => null, + 'translation_file_id' => TranslationFile::factory([ + 'name' => 'book/create', + 'extension' => 'php', + ]), + ]))->create(); + $translationsManager = new TranslationsManager($filesystem); $translationsManager->export(); $fileName = lang_path('en/'.$translation->phrases[0]->file->name.'.'.$translation->phrases[0]->file->extension); + $nestedFileName = lang_path('en/'.$nestedTranslation->phrases[0]->file->name.'.'.$nestedTranslation->phrases[0]->file->extension); $fileNameInDisk = File::allFiles(lang_path($translation->language->code))[0]->getPathname(); + $nestedFileNameInDisk = File::allFiles(lang_path($nestedTranslation->language->code))[1]->getPathname(); expect($fileName)->toBe($fileNameInDisk) ->and(File::get($fileName)) - ->toBe("phrases->pluck('value', 'key')->toArray(), VarExporter::TRAILING_COMMA_IN_ARRAY).';'.PHP_EOL); + ->toBe("phrases->pluck('value', 'key')->toArray(), VarExporter::TRAILING_COMMA_IN_ARRAY).';'.PHP_EOL) + ->and($nestedFileName)->toBe($nestedFileNameInDisk) + ->and(File::get($nestedFileName)) + ->toBe("phrases->pluck('value', 'key')->toArray(), VarExporter::TRAILING_COMMA_IN_ARRAY).';'.PHP_EOL); File::deleteDirectory(lang_path()); }); @@ -121,6 +161,9 @@ test('export can handle PHP translation files', function () { createPhpLanguageFile('en/test.php', ['accepted' => 'The :attribute must be accepted.']); + // Update to include nested folder + createPhpLanguageFile('en/book/create.php', ['nested' => 'Nested :attribute must be accepted.']); + $filesystem = new Filesystem(); $translation = Translation::factory() @@ -134,19 +177,36 @@ ->for(Language::factory()->state(['code' => 'en'])) ->create(); + $nestedTranslation = Translation::factory() + ->has(Phrase::factory() + ->for(TranslationFile::factory()->state(['name' => 'book/create', 'extension' => 'php']), 'file') + ->state([ + 'key' => 'nested', + 'value' => 'Nested :attribute must be accepted.', + 'phrase_id' => null, + ])) + ->for(Language::factory()->state(['code' => 'en'])) + ->create(); + $translationsManager = new TranslationsManager($filesystem); $translationsManager->export(); $path = lang_path('en'.DIRECTORY_SEPARATOR.'test.php'); + $nestedPath = lang_path('en'.DIRECTORY_SEPARATOR.'book'.DIRECTORY_SEPARATOR.'create.php'); $pathInDisk = lang_path($translation->language->code.DIRECTORY_SEPARATOR.'test.php'); + $nestedPathInDisk = lang_path($nestedTranslation->language->code.DIRECTORY_SEPARATOR.'book'.DIRECTORY_SEPARATOR.'create.php'); - expect(File::get($path))->toBe(File::get($pathInDisk)); + expect(File::get($path))->toBe(File::get($pathInDisk)) + ->and(File::get($nestedPath))->toBe(File::get($nestedPathInDisk)); File::deleteDirectory(lang_path()); }); test('export can handle JSON translation files', function () { createJsonLanguageFile('en/test.json', ['accepted' => 'The :attribute must be accepted.']); + + // Update to include nested folder + createJsonLanguageFile('en/book/create.json', ['nested' => 'Nested test']); $filesystem = new Filesystem(); $translation = Translation::factory() @@ -160,13 +220,27 @@ ->for(Language::factory()->state(['code' => 'en'])) ->create(); + $nestedTranslation = Translation::factory() + ->has(Phrase::factory() + ->for(TranslationFile::factory()->state(['name' => 'book/create', 'extension' => 'json']), 'file') + ->state([ + 'key' => 'nested', + 'value' => 'Nested test', + 'phrase_id' => null, + ])) + ->for(Language::factory()->state(['code' => 'en'])) + ->create(); + $translationsManager = new TranslationsManager($filesystem); $translationsManager->export(); $path = lang_path('en'.DIRECTORY_SEPARATOR.'test.json'); + $nestedPath = lang_path('en'.DIRECTORY_SEPARATOR.'book'.DIRECTORY_SEPARATOR.'create.json'); $pathInDisk = lang_path($translation->language->code.DIRECTORY_SEPARATOR.'test.json'); + $nestedPathInDisk = lang_path($nestedTranslation->language->code.DIRECTORY_SEPARATOR.'book'.DIRECTORY_SEPARATOR.'create.json'); - expect(File::get($path))->toBe(File::get($pathInDisk)); + expect(File::get($path))->toBe(File::get($pathInDisk)) + ->and(File::get($nestedPath))->toBe(File::get($nestedPathInDisk)); File::deleteDirectory(lang_path()); });