Skip to content

Commit

Permalink
Feat: list translations command
Browse files Browse the repository at this point in the history
  • Loading branch information
VasasA committed Dec 29, 2024
1 parent 5279639 commit 40469c8
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 0 deletions.
7 changes: 7 additions & 0 deletions docs/10-about/02-contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 [[email protected]](mailto:[email protected]). All security vulnerabilities will be promptly addressed.
Expand Down
172 changes: 172 additions & 0 deletions packages/support/src/Commands/ListTranslationsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<?php

namespace Filament\Support\Commands;

use Illuminate\Console\Command;
use Illuminate\Contracts\Console\PromptsForMissingInput;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Exception\InvalidOptionException;
use Symfony\Component\Finder\SplFileInfo;

use function Laravel\Prompts\info;
use function Laravel\Prompts\table;
use function Laravel\Prompts\warning;

#[AsCommand(name: 'filament:list-translations')]
class ListTranslationsCommand extends Command implements PromptsForMissingInput
{
protected $signature = 'filament:list-translations
{locales* : The locales to list.}
{--source=vendor : The directory containing the translations to list - either \'vendor\' or \'app\'.}';

protected $description = 'List translations.';

public function handle(): int
{
$this->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']),
],
);
});
});
}
}
2 changes: 2 additions & 0 deletions packages/support/src/SupportServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -50,6 +51,7 @@ public function configurePackage(Package $package): void
->hasCommands([
AssetsCommand::class,
CheckTranslationsCommand::class,
ListTranslationsCommand::class,
FilamentAboutCommand::class,
InstallCommand::class,
MakeIssueCommand::class,
Expand Down

0 comments on commit 40469c8

Please sign in to comment.