Skip to content

Commit

Permalink
Feat: list translations command option
Browse files Browse the repository at this point in the history
  • Loading branch information
VasasA committed Jan 3, 2025
1 parent 464b343 commit 04c5d16
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 47 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 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']),
],
);
});
}
}

0 comments on commit 04c5d16

Please sign in to comment.