diff --git a/src/Contracts/Factories/LanguageNamespaceKeyFactory.php b/src/Contracts/Factories/LanguageNamespaceKeyFactory.php new file mode 100644 index 0000000..572ccf7 --- /dev/null +++ b/src/Contracts/Factories/LanguageNamespaceKeyFactory.php @@ -0,0 +1,10 @@ +getExtension(); + + if ($this->manager->isEnabled($extension)) { + $reader = $this->manager->driver($extension); + + if ($reader instanceof LanguageNamespaceKeyFactoryContract) { + return $reader->getNamespaceHintedKey($file, $locale, $namespaceHint, $key); + } + } + + return $key; + } +} diff --git a/src/LaravelTranslationLinterServiceProvider.php b/src/LaravelTranslationLinterServiceProvider.php index 3115a7f..508eac8 100644 --- a/src/LaravelTranslationLinterServiceProvider.php +++ b/src/LaravelTranslationLinterServiceProvider.php @@ -10,6 +10,7 @@ use Fidum\LaravelTranslationLinter\Contracts\Collections\UnusedFieldCollection as UnusedFieldCollectionContract; use Fidum\LaravelTranslationLinter\Contracts\Collections\UnusedFilterCollection as UnusedFilterCollectionContract; use Fidum\LaravelTranslationLinter\Contracts\Factories\LanguageKeyFactory as LanguageKeyFactoryContract; +use Fidum\LaravelTranslationLinter\Contracts\Factories\LanguageNamespaceKeyFactory as LanguageNamespaceKeyFactoryContract; use Fidum\LaravelTranslationLinter\Contracts\Finders\ApplicationFileFinder as ApplicationFileFinderContract; use Fidum\LaravelTranslationLinter\Contracts\Finders\LanguageFileFinder as LanguageFileFinderContract; use Fidum\LaravelTranslationLinter\Contracts\Finders\LanguageNamespaceFinder as LanguageNamespaceFinderContract; @@ -18,6 +19,7 @@ use Fidum\LaravelTranslationLinter\Contracts\Readers\ApplicationFileReader as ApplicationFileReaderContract; use Fidum\LaravelTranslationLinter\Contracts\Readers\LanguageFileReader as LanguageFileReaderContract; use Fidum\LaravelTranslationLinter\Factories\LanguageKeyFactory; +use Fidum\LaravelTranslationLinter\Factories\LanguageNamespaceKeyFactory; use Fidum\LaravelTranslationLinter\Finders\ApplicationFileFinder; use Fidum\LaravelTranslationLinter\Finders\LanguageFileFinder; use Fidum\LaravelTranslationLinter\Finders\LanguageNamespaceFinder; @@ -74,6 +76,8 @@ public function registeringPackage() $this->app->bind(LanguageNamespaceFinderContract::class, LanguageNamespaceFinder::class); + $this->app->bind(LanguageNamespaceKeyFactoryContract::class, LanguageNamespaceKeyFactory::class); + $this->app->bind(ResultObjectCollectionContract::class, ResultObjectCollection::class); $this->app->bind(UnusedFieldCollectionContract::class, function (Application $app) { @@ -102,6 +106,7 @@ public function provides() LanguageFileReaderManager::class, LanguageKeyFactoryContract::class, LanguageNamespaceFinderContract::class, + LanguageNamespaceKeyFactoryContract::class, ResultObjectCollectionContract::class, UnusedFieldCollectionContract::class, UnusedFilterCollectionContract::class, diff --git a/src/Linters/UnusedTranslationLinter.php b/src/Linters/UnusedTranslationLinter.php index a4f9cf2..b53d2d4 100644 --- a/src/Linters/UnusedTranslationLinter.php +++ b/src/Linters/UnusedTranslationLinter.php @@ -4,6 +4,7 @@ use Fidum\LaravelTranslationLinter\Contracts\Collections\ResultObjectCollection; use Fidum\LaravelTranslationLinter\Contracts\Factories\LanguageKeyFactory; +use Fidum\LaravelTranslationLinter\Contracts\Factories\LanguageNamespaceKeyFactory; use Fidum\LaravelTranslationLinter\Contracts\Finders\LanguageFileFinder; use Fidum\LaravelTranslationLinter\Contracts\Finders\LanguageNamespaceFinder; use Fidum\LaravelTranslationLinter\Contracts\Linters\UnusedTranslationLinter as UnusedTranslationLinterContract; @@ -21,8 +22,9 @@ public function __construct( protected ApplicationFileReader $used, protected LanguageFileFinder $files, protected LanguageFileReader $translations, - protected LanguageKeyFactory $factory, + protected LanguageKeyFactory $languageKeyFactory, protected LanguageNamespaceFinder $namespaces, + protected LanguageNamespaceKeyFactory $namespaceKeyFactory, protected ResultObjectCollection $results, protected array $locales, ) {} @@ -42,17 +44,23 @@ public function execute(): ResultObjectCollection $translations = $this->translations->getTranslations($file); foreach ($translations as $field => $children) { - $group = $this->factory->getLanguageKey($file, $locale, $field); - foreach (Arr::dot(Arr::wrap($children)) as $key => $value) { - $groupedKey = Str::of($group) + $fieldKey = Str::of($field) ->when(is_string($key), fn (Stringable $str) => $str->append(".$key")) ->toString(); - $namespacedKey = Str::of($namespace) - ->whenNotEmpty(fn (Stringable $str) => $str->append('::')) - ->append($groupedKey) - ->toString(); + $groupedKey = $this->languageKeyFactory->getLanguageKey( + file: $file, + locale: $locale, + key: $fieldKey + ); + + $namespacedKey = $this->namespaceKeyFactory->getNamespaceHintedKey( + file: $file, + locale: $locale, + namespaceHint: $namespace, + key: $groupedKey + ); if ($used->doesntContain($namespacedKey)) { $this->results->push(new ResultObject( diff --git a/src/Readers/PhpFileReader.php b/src/Readers/PhpFileReader.php index 51e3598..0047553 100644 --- a/src/Readers/PhpFileReader.php +++ b/src/Readers/PhpFileReader.php @@ -3,12 +3,14 @@ namespace Fidum\LaravelTranslationLinter\Readers; use Fidum\LaravelTranslationLinter\Contracts\Factories\LanguageKeyFactory as LanguageKeyFactoryContract; +use Fidum\LaravelTranslationLinter\Contracts\Factories\LanguageNamespaceKeyFactory as LanguageNamespaceKeyFactoryContract; use Fidum\LaravelTranslationLinter\Contracts\Readers\LanguageFileReader as LanguageFileReaderContract; use Illuminate\Support\Str; +use Illuminate\Support\Stringable; use InvalidArgumentException; use Symfony\Component\Finder\SplFileInfo; -class PhpFileReader implements LanguageFileReaderContract, LanguageKeyFactoryContract +class PhpFileReader implements LanguageFileReaderContract, LanguageKeyFactoryContract, LanguageNamespaceKeyFactoryContract { public function getLanguageKey(SplFileInfo $file, string $locale, string $key): string { @@ -21,6 +23,14 @@ public function getLanguageKey(SplFileInfo $file, string $locale, string $key): ->toString(); } + public function getNamespaceHintedKey(SplFileInfo $file, string $locale, string $namespaceHint, string $key): string + { + return Str::of($namespaceHint) + ->whenNotEmpty(fn (Stringable $str) => $str->append('::')) + ->append($key) + ->toString(); + } + public function getTranslations(SplFileInfo $file): array { $translations = include $file->getPathname(); diff --git a/tests/.pest/snapshots/Commands/UnusedCommandTest/it_errors_with_default_no_filters.snap b/tests/.pest/snapshots/Commands/UnusedCommandTest/it_errors_with_default_no_filters.snap index 17c566f..72b1826 100644 --- a/tests/.pest/snapshots/Commands/UnusedCommandTest/it_errors_with_default_no_filters.snap +++ b/tests/.pest/snapshots/Commands/UnusedCommandTest/it_errors_with_default_no_filters.snap @@ -1,9 +1,12 @@ - ERROR 19 unused translations found. + ERROR 22 unused translations found. +--------+-----------+------------------------------------+------------------------------+ | Locale | Namespace | Key | Value | +--------+-----------+------------------------------------+------------------------------+ +| en | example | Unused Vendor PHP Class | I am unused in php class | +| en | example | Unused Vendor Blade File | I am unused in blade | +| en | example | Unused Vendor Vue Component | I am unused in vue component | | en | example | example.unused | I am unused in php class | | en | example | example.blade.choice.unused | I am unused in blade | | en | example | example.blade.lang.unused | I am unused in blade | diff --git a/tests/.pest/snapshots/Commands/UnusedCommandTest/it_errors_with_multiple_locales_and_no_filters.snap b/tests/.pest/snapshots/Commands/UnusedCommandTest/it_errors_with_multiple_locales_and_no_filters.snap new file mode 100644 index 0000000..227335b --- /dev/null +++ b/tests/.pest/snapshots/Commands/UnusedCommandTest/it_errors_with_multiple_locales_and_no_filters.snap @@ -0,0 +1,43 @@ + + ERROR 36 unused translations found. + ++--------+-----------+------------------------------------+----------------------------------------------------+ +| Locale | Namespace | Key | Value | ++--------+-----------+------------------------------------+----------------------------------------------------+ +| en | example | Unused Vendor PHP Class | I am unused in php class | +| en | example | Unused Vendor Blade File | I am unused in blade | +| en | example | Unused Vendor Vue Component | I am unused in vue component | +| en | example | example.unused | I am unused in php class | +| en | example | example.blade.choice.unused | I am unused in blade | +| en | example | example.blade.lang.unused | I am unused in blade | +| en | example | example.vue.unused | I am unused in vue component | +| en | example | folder/example.unused | I am unused in php class | +| en | example | folder/example.blade.choice.unused | I am unused in blade | +| en | example | folder/example.blade.lang.unused | I am unused in blade | +| en | example | folder/example.vue.unused | I am unused in vue component | +| en | | Unused PHP Class | I am unused in php class | +| en | | Unused Blade File | I am unused in blade | +| en | | Unused Vue Component | I am unused in vue component | +| en | | example.unused | I am unused in php class | +| en | | example.blade.choice.unused | I am unused in blade | +| en | | example.blade.lang.unused | I am unused in blade | +| en | | example.vue.unused | I am unused in vue component | +| en | | folder/example.unused | I am unused in php class | +| en | | folder/example.blade.choice.unused | I am unused in blade | +| en | | folder/example.blade.lang.unused | I am unused in blade | +| en | | folder/example.vue.unused | I am unused in vue component | +| de | example | Unused Vendor PHP Class | Ich werde in einer PHP-Klasse nicht verwendet | +| de | example | Unused Vendor Blade File | Ich werde in Blade nicht verwendet | +| de | example | Unused Vendor Vue Component | Ich werde in einem Vue-Komponenten nicht verwendet | +| de | | Unused PHP Class | Ich werde in einer PHP-Klasse nicht verwendet | +| de | | Unused Blade File | Ich werde in Blade nicht verwendet | +| de | | Unused Vue Component | Ich werde in einem Vue-Komponenten nicht verwendet | +| de | | example.unused | Ich werde in einer PHP-Klasse nicht verwendet | +| de | | example.blade.choice.unused | Ich werde in Blade nicht verwendet | +| de | | example.blade.lang.unused | Ich werde in Blade nicht verwendet | +| de | | example.vue.unused | Ich werde in einem Vue-Komponenten nicht verwendet | +| de | | folder/example.unused | Ich werde in einer PHP-Klasse nicht verwendet | +| de | | folder/example.blade.choice.unused | Ich werde in Blade nicht verwendet | +| de | | folder/example.blade.lang.unused | Ich werde in Blade nicht verwendet | +| de | | folder/example.vue.unused | Ich werde in einem Vue-Komponenten nicht verwendet | ++--------+-----------+------------------------------------+----------------------------------------------------+ diff --git a/tests/Commands/UnusedCommandTest.php b/tests/Commands/UnusedCommandTest.php index efbbc47..3577482 100644 --- a/tests/Commands/UnusedCommandTest.php +++ b/tests/Commands/UnusedCommandTest.php @@ -55,6 +55,18 @@ ->toMatchSnapshot(); }); +it('errors with multiple locales and no filters', function () { + config()->set('translation-linter.lang.locales', ['en', 'de']); + config()->set('translation-linter.unused.fields.namespace', true); + config()->set('translation-linter.unused.filters', []); + + withoutMockingConsoleOutput(); + expect(artisan('translation:unused')) + ->toBe(1) + ->and(Artisan::output()) + ->toMatchSnapshot(); +}); + it('outputs success message when no unused translations found', function () { config()->set('translation-linter.lang.locales', []); withoutMockingConsoleOutput(); diff --git a/workbench/app/ExampleJson.php b/workbench/app/ExampleJson.php index bae871e..bc574f6 100644 --- a/workbench/app/ExampleJson.php +++ b/workbench/app/ExampleJson.php @@ -7,5 +7,6 @@ class ExampleJson public function handle() { __('Used PHP Class'); + __('Used Vendor PHP Class'); } } diff --git a/workbench/resources/js/ExampleComponent.vue b/workbench/resources/js/ExampleComponent.vue index 912e77e..d24942e 100644 --- a/workbench/resources/js/ExampleComponent.vue +++ b/workbench/resources/js/ExampleComponent.vue @@ -15,4 +15,8 @@ const exampleC = computed(() => __( 'Used Vue Component', {foo: 'bar'}, )) +const exampleD = computed(() => __( + 'Used Vendor Vue Component', + {foo: 'bar'}, +)) diff --git a/workbench/resources/views/welcome.blade.php b/workbench/resources/views/welcome.blade.php index 0b7002c..f407553 100644 --- a/workbench/resources/views/welcome.blade.php +++ b/workbench/resources/views/welcome.blade.php @@ -9,6 +9,7 @@ ) {{ __('Used Blade File') }} +{{ __('Used Vendor Blade File') }} @if(true) @choice('example.blade.choice.used', 1) diff --git a/workbench/vendor/example/lang/de.json b/workbench/vendor/example/lang/de.json new file mode 100644 index 0000000..d5363e8 --- /dev/null +++ b/workbench/vendor/example/lang/de.json @@ -0,0 +1,8 @@ +{ + "Used Vendor PHP Class": "Ich werde in einer PHP-Klasse verwendet", + "Unused Vendor PHP Class": "Ich werde in einer PHP-Klasse nicht verwendet", + "Used Vendor Blade File": "Ich werde in Blade verwendet", + "Unused Vendor Blade File": "Ich werde in Blade nicht verwendet", + "Used Vendor Vue Component": "Ich werde in einem Vue-Komponenten verwendet", + "Unused Vendor Vue Component": "Ich werde in einem Vue-Komponenten nicht verwendet" +} diff --git a/workbench/vendor/example/lang/en.json b/workbench/vendor/example/lang/en.json new file mode 100644 index 0000000..52ec4d3 --- /dev/null +++ b/workbench/vendor/example/lang/en.json @@ -0,0 +1,8 @@ +{ + "Used Vendor PHP Class": "I am used in php class", + "Unused Vendor PHP Class": "I am unused in php class", + "Used Vendor Blade File": "I am used in blade", + "Unused Vendor Blade File": "I am unused in blade", + "Used Vendor Vue Component": "I am used in vue component", + "Unused Vendor Vue Component": "I am unused in vue component" +}