Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: list translations command option #15234

Open
wants to merge 1 commit into
base: 4.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 English strings have changed. If you want to check the strings, you can list all translations with `--list` option:

```bash
php artisan filament:check-translations es --list
php artisan filament:check-translations es --source=app --list
```

## 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
196 changes: 149 additions & 47 deletions packages/support/src/Commands/CheckTranslationsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#[AsCommand(name: 'filament:check-translations')]
class CheckTranslationsCommand extends Command implements PromptsForMissingInput
{
protected $description = 'Check for missing and removed translations';
protected $description = 'Check for missing and removed translations or list all translations';

protected $name = 'filament:check-translations';

Expand Down Expand Up @@ -52,12 +52,19 @@ protected function getOptions(): array
description: 'The directory containing the translations to check - either \'vendor\' or \'app\'',
default: 'vendor',
),
new InputOption(
name: 'list',
shortcut: 'l',
mode: InputOption::VALUE_NONE,
description: 'List translations',
),
];
}

public function handle(): int
{
$this->scan('filament');
$this->scan('panels');
$this->scan('actions');
$this->scan('forms');
$this->scan('infolists');
Expand All @@ -79,9 +86,11 @@ public function handle(): int
protected function scan(string $package): void
{
$localeRootDirectory = match ($source = $this->option('source')) {
'app' => lang_path("vendor/{$package}"),
'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`.")
default => throw new InvalidOptionException("{$source} is not a valid translation source. Must be `vendor` or `app`."),
};

$filesystem = app(Filesystem::class);
Expand Down Expand Up @@ -124,53 +133,146 @@ protected function scan(string $package): void
);
}

collect($files)
$existingFiles = 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();

return [
(string) str($file->getPathname())->after("{$localeDir}/") => [
'missing' => array_keys(array_diff_key(
Arr::dot($expectedKeys),
Arr::dot($actualKeys)
)),
'removed' => array_keys(array_diff_key(
Arr::dot($actualKeys),
Arr::dot($expectedKeys)
)),
],
];
})
->tap(function (Collection $files) use ($locale, $package) {
$missingKeysCount = $files->sum(fn ($file): int => count($file['missing']));
$removedKeysCount = $files->sum(fn ($file): int => count($file['removed']));

$locale = locale_get_display_name($locale, 'en');

if ((! $missingKeysCount) && (! $removedKeysCount)) {
info("[✓] Package filament/{$package} has no missing or removed translation keys for {$locale}!\n");
} elseif ($missingKeysCount && $removedKeysCount) {
warning("[!] Package filament/{$package} has {$missingKeysCount} missing translation " . Str::plural('key', $missingKeysCount) . " and {$removedKeysCount} removed translation " . Str::plural('key', $removedKeysCount) . " for {$locale}.\n");
} elseif ($missingKeysCount) {
warning("[!] Package filament/{$package} has {$missingKeysCount} missing translation " . Str::plural('key', $missingKeysCount) . " for {$locale}.\n");
} elseif ($removedKeysCount) {
warning("[!] Package filament/{$package} has {$removedKeysCount} removed translation " . Str::plural('key', $removedKeysCount) . " for {$locale}.\n");
}
})
->filter(static fn ($keys): bool => count($keys['missing']) || count($keys['removed']))
->each(function ($keys, string $file) {
table(
[$file, ''],
[
...array_map(fn (string $key): array => [$key, 'Missing'], $keys['missing']),
...array_map(fn (string $key): array => [$key, 'Removed'], $keys['removed']),
],
);
});
if ($this->option('list')) {
$translations = $this->translations($existingFiles, $localeDir, $localeRootDirectory);
$this->displayTranslations($translations, $locale, $package);
} else {
$missings = $this->check($existingFiles, $localeDir, $localeRootDirectory);
$this->displayMissings($missings, $locale, $package);
}
});
}

protected function translations(Collection $files, string $localeDir, string $localeRootDirectory): Collection
{
return $files->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,
],
];
});
}

protected function displayTranslations(Collection $missings, string $locale, string $package): void
{
collect($missings)
->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']),
],
);
});
}

protected function check(Collection $files, string $localeDir, string $localeRootDirectory): Collection
{
return $files->mapWithKeys(function (SplFileInfo $file) use ($localeDir, $localeRootDirectory) {
$expectedKeys = require implode(DIRECTORY_SEPARATOR, [$localeRootDirectory, 'en', $file->getRelativePathname()]);
$actualKeys = require $file->getPathname();

return [
(string) str($file->getPathname())->after("{$localeDir}/") => [
'missing' => array_keys(array_diff_key(
Arr::dot($expectedKeys),
Arr::dot($actualKeys)
)),
'removed' => array_keys(array_diff_key(
Arr::dot($actualKeys),
Arr::dot($expectedKeys)
)),
],
];
});
}

protected function displayMissings(Collection $missings, string $locale, string $package): void
{
collect($missings)
->tap(function (Collection $files) use ($locale, $package) {
$missingKeysCount = $files->sum(fn ($file): int => count($file['missing']));
$removedKeysCount = $files->sum(fn ($file): int => count($file['removed']));

$locale = locale_get_display_name($locale, 'en');

if ((! $missingKeysCount) && (! $removedKeysCount)) {
info("[✓] Package filament/{$package} has no missing or removed translation keys for {$locale}!\n");
} elseif ($missingKeysCount && $removedKeysCount) {
warning("[!] Package filament/{$package} has {$missingKeysCount} missing translation " . Str::plural('key', $missingKeysCount) . " and {$removedKeysCount} removed translation " . Str::plural('key', $removedKeysCount) . " for {$locale}.\n");
} elseif ($missingKeysCount) {
warning("[!] Package filament/{$package} has {$missingKeysCount} missing translation " . Str::plural('key', $missingKeysCount) . " for {$locale}.\n");
} elseif ($removedKeysCount) {
warning("[!] Package filament/{$package} has {$removedKeysCount} removed translation " . Str::plural('key', $removedKeysCount) . " for {$locale}.\n");
}
})
->filter(static fn ($keys): bool => count($keys['missing']) || count($keys['removed']))
->each(function ($keys, string $file) {
table(
[$file, ''],
[
...array_map(fn (string $key): array => [$key, 'Missing'], $keys['missing']),
...array_map(fn (string $key): array => [$key, 'Removed'], $keys['removed']),
],
);
});
}
}
Loading