From 40469c82569da3c9b66041a1952c7664c51c8e2b Mon Sep 17 00:00:00 2001 From: VasasA Date: Thu, 19 Dec 2024 15:59:34 +0100 Subject: [PATCH] Feat: list translations command --- docs/10-about/02-contributing.md | 7 + .../src/Commands/ListTranslationsCommand.php | 172 ++++++++++++++++++ .../support/src/SupportServiceProvider.php | 2 + 3 files changed, 181 insertions(+) create mode 100644 packages/support/src/Commands/ListTranslationsCommand.php diff --git a/docs/10-about/02-contributing.md b/docs/10-about/02-contributing.md index 97593a6e3b7..f6e8c2dc72e 100644 --- a/docs/10-about/02-contributing.md +++ b/docs/10-about/02-contributing.md @@ -78,6 +78,13 @@ If you've published the translations into your app and you'd like to check those php artisan filament:check-translations es --source=app ``` +You need to fix the translations where the string has changed. If you want to check the strings, you can show all translations: + +```bash +php artisan filament:list-translations es +php artisan filament:check-translations es --source=app +``` + ## Security vulnerabilities If you discover a security vulnerability within Filament, please email Dan Harrin via [dan@danharrin.com](mailto:dan@danharrin.com). All security vulnerabilities will be promptly addressed. diff --git a/packages/support/src/Commands/ListTranslationsCommand.php b/packages/support/src/Commands/ListTranslationsCommand.php new file mode 100644 index 00000000000..9dc743a8ae9 --- /dev/null +++ b/packages/support/src/Commands/ListTranslationsCommand.php @@ -0,0 +1,172 @@ +scan('filament'); + $this->scan('panels'); + $this->scan('actions'); + $this->scan('forms'); + $this->scan('infolists'); + $this->scan('notifications'); + $this->scan('spark-billing-provider'); + $this->scan('spatie-laravel-google-fonts-plugin'); + $this->scan('spatie-laravel-media-library-plugin'); + $this->scan('spatie-laravel-settings-plugin'); + $this->scan('spatie-laravel-tags-plugin'); + $this->scan('spatie-laravel-translatable-plugin'); + $this->scan('support'); + $this->scan('tables'); + $this->scan('widgets'); + + return self::SUCCESS; + } + + protected function scan(string $package): void + { + $localeRootDirectory = match ($source = $this->option('source')) { + 'app' => $package == 'support' + ? lang_path('vendor/filament') + : lang_path("vendor/filament-{$package}"), + 'vendor' => base_path("vendor/filament/{$package}/resources/lang"), + default => throw new InvalidOptionException("{$source} is not a valid translation source. Must be `vendor` or `app`."), + }; + + $filesystem = app(Filesystem::class); + + if (! $filesystem->exists($localeRootDirectory)) { + return; + } + collect($filesystem->directories($localeRootDirectory)) + ->mapWithKeys(static fn (string $directory): array => [$directory => (string) str($directory)->afterLast(DIRECTORY_SEPARATOR)]) + ->when( + $locales = $this->argument('locales'), + fn (Collection $availableLocales): Collection => $availableLocales->filter(fn (string $locale): bool => in_array($locale, $locales)) + ) + ->each(function (string $locale, string $localeDir) use ($filesystem, $localeRootDirectory, $package) { + $files = $filesystem->allFiles($localeDir); + $baseFiles = $filesystem->allFiles(implode(DIRECTORY_SEPARATOR, [$localeRootDirectory, 'en'])); + + $localeFiles = collect($files)->map(fn ($file) => (string) str($file->getPathname())->after(DIRECTORY_SEPARATOR . $locale . DIRECTORY_SEPARATOR)); + $englishFiles = collect($baseFiles)->map(fn ($file) => (string) str($file->getPathname())->after(DIRECTORY_SEPARATOR . 'en' . DIRECTORY_SEPARATOR)); + $missingFiles = $englishFiles->diff($localeFiles); + $removedFiles = $localeFiles->diff($englishFiles); + $path = implode(DIRECTORY_SEPARATOR, [$localeRootDirectory, $locale]); + + if ($missingFiles->count() > 0 && $removedFiles->count() > 0) { + warning("[!] Package filament/{$package} has {$missingFiles->count()} missing translation " . Str::plural('file', $missingFiles->count()) . " and {$removedFiles->count()} removed translation " . Str::plural('file', $missingFiles->count()) . ' for ' . locale_get_display_name($locale, 'en') . ".\n"); + } elseif ($missingFiles->count() > 0) { + warning("[!] Package filament/{$package} has {$missingFiles->count()} missing translation " . Str::plural('file', $missingFiles->count()) . ' for ' . locale_get_display_name($locale, 'en') . ".\n"); + } elseif ($removedFiles->count() > 0) { + warning("[!] Package filament/{$package} has {$removedFiles->count()} removed translation " . Str::plural('file', $removedFiles->count()) . ' for ' . locale_get_display_name($locale, 'en') . ".\n"); + } + + if ($missingFiles->count() > 0 || $removedFiles->count() > 0) { + table( + [$path, ''], + array_merge( + array_map(fn (string $file): array => [$file, 'Missing'], $missingFiles->toArray()), + array_map(fn (string $file): array => [$file, 'Removed'], $removedFiles->toArray()), + ), + ); + } + + collect($files) + ->reject(function ($file) use ($localeRootDirectory) { + return ! file_exists(implode(DIRECTORY_SEPARATOR, [$localeRootDirectory, 'en', $file->getRelativePathname()])); + }) + ->mapWithKeys(function (SplFileInfo $file) use ($localeDir, $localeRootDirectory) { + $expectedKeys = require implode(DIRECTORY_SEPARATOR, [$localeRootDirectory, 'en', $file->getRelativePathname()]); + $actualKeys = require $file->getPathname(); + $expectedKeysFlat = Arr::dot($expectedKeys); + $actualKeysFlat = Arr::dot($actualKeys); + $translations = collect($expectedKeysFlat) + ->map(function ($expectedKey, $key) use ($actualKeysFlat) { + $translation = $actualKeysFlat[$key] ?? '<<<<< MISSING! >>>>>'; + + return $expectedKey . ' --> ' . $translation; + }) + ->toArray(); + $removedKeys = collect($actualKeysFlat) + ->reject(function ($actualKey, $key) use ($expectedKeysFlat) { + return isset($expectedKeysFlat[$key]); + }) + ->map(function ($actualKey, $key) { + return 'Removed translation key: <<<<< ' . $key . ' >>>>>'; + }) + ->toArray(); + $translations += $removedKeys; + $expectedKeysCount = count($expectedKeysFlat); + $removedKeysCount = count($removedKeys); + $missingKeysCount = $expectedKeysCount - (count($actualKeysFlat) - $removedKeysCount); + + return [ + (string) str($file->getPathname())->after("{$localeDir}/") => [ + 'expected_keys_count' => $expectedKeysCount, + 'missing_keys_count' => $missingKeysCount, + 'removed_keys_count' => $removedKeysCount, + 'translation' => $translations, + ], + ]; + }) + ->tap(function (Collection $files) use ($locale, $package) { + $expectedKeysCount = $files->sum(fn ($file): int => $file['expected_keys_count']); + $missingKeysCount = $files->sum(fn ($file): int => $file['missing_keys_count']); + $removedKeysCount = $files->sum(fn ($file): int => $file['removed_keys_count']); + + $locale = locale_get_display_name($locale, 'en'); + info("Package filament/{$package} has {$expectedKeysCount} translation " . Str::plural('string', $expectedKeysCount) . " for {$locale}."); + $message = "{$missingKeysCount} missing translation " . Str::plural('string', $missingKeysCount) . '.'; + if ($missingKeysCount) { + warning($message); + } else { + info($message); + } + $message = "{$removedKeysCount} removed translation " . Str::plural('string', $removedKeysCount); + if ($removedKeysCount) { + warning($message); + } else { + info($message); + } + }) + ->each(function ($keys, string $file) { + $counts = [ + 'expected_keys_count' => '- Number of expected keys: ' . $keys['expected_keys_count'], + 'missing_keys_count' => '- Number of missing keys: ' . $keys['missing_keys_count'], + 'removed_keys_count' => '- Number of removed keys: ' . $keys['removed_keys_count'], + ]; + $keys['translation'] += $counts; + table( + [$file], + [ + ...array_map(fn (string $key): array => [$key], $keys['translation']), + ], + ); + }); + }); + } +} diff --git a/packages/support/src/SupportServiceProvider.php b/packages/support/src/SupportServiceProvider.php index dbc5cee7557..fc54f294393 100644 --- a/packages/support/src/SupportServiceProvider.php +++ b/packages/support/src/SupportServiceProvider.php @@ -11,6 +11,7 @@ use Filament\Support\Commands\AboutCommand as FilamentAboutCommand; use Filament\Support\Commands\AssetsCommand; use Filament\Support\Commands\CheckTranslationsCommand; +use Filament\Support\Commands\ListTranslationsCommand; use Filament\Support\Commands\InstallCommand; use Filament\Support\Commands\MakeIssueCommand; use Filament\Support\Commands\OptimizeClearCommand; @@ -50,6 +51,7 @@ public function configurePackage(Package $package): void ->hasCommands([ AssetsCommand::class, CheckTranslationsCommand::class, + ListTranslationsCommand::class, FilamentAboutCommand::class, InstallCommand::class, MakeIssueCommand::class,