Skip to content

Commit a9472fe

Browse files
authored
refactor: migrate lang:* commands as modern commands (#10306)
1 parent 4cba698 commit a9472fe

6 files changed

Lines changed: 210 additions & 169 deletions

File tree

system/Commands/Translation/LocalizationFinder.php

Lines changed: 114 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
namespace CodeIgniter\Commands\Translation;
1515

16-
use CodeIgniter\CLI\BaseCommand;
16+
use CodeIgniter\CLI\AbstractCommand;
17+
use CodeIgniter\CLI\Attributes\Command;
1718
use CodeIgniter\CLI\CLI;
19+
use CodeIgniter\CLI\Input\Option;
1820
use CodeIgniter\Helpers\Array\ArrayHelper;
1921
use Config\App;
2022
use Locale;
@@ -23,73 +25,85 @@
2325
use SplFileInfo;
2426

2527
/**
26-
* @see \CodeIgniter\Commands\Translation\LocalizationFinderTest
28+
* Finds and saves available phrases to translate.
2729
*/
28-
class LocalizationFinder extends BaseCommand
30+
#[Command(
31+
name: 'lang:find',
32+
description: 'Find and save available phrases to translate.',
33+
group: 'Translation',
34+
)]
35+
class LocalizationFinder extends AbstractCommand
2936
{
30-
protected $group = 'Translation';
31-
protected $name = 'lang:find';
32-
protected $description = 'Find and save available phrases to translate.';
33-
protected $usage = 'lang:find [options]';
34-
protected $arguments = [];
35-
protected $options = [
36-
'--locale' => 'Specify locale (en, ru, etc.) to save files.',
37-
'--dir' => 'Directory to search for translations relative to APPPATH.',
38-
'--show-new' => 'Show only new translations in table. Does not write to files.',
39-
'--verbose' => 'Output detailed information.',
40-
];
37+
private string $languagePath;
4138

42-
/**
43-
* Flag for output detailed information
44-
*/
45-
private bool $verbose = false;
39+
protected function configure(): void
40+
{
41+
$this
42+
->addOption(new Option(
43+
name: 'locale',
44+
description: 'Specify locale (en, ru, etc.) to save files.',
45+
requiresValue: true,
46+
default: '',
47+
))
48+
->addOption(new Option(
49+
name: 'dir',
50+
description: 'Directory to search for translations relative to APPPATH.',
51+
requiresValue: true,
52+
default: '',
53+
))
54+
->addOption(new Option(
55+
name: 'show-new',
56+
description: 'Show only new translations in table. Does not write to files.',
57+
))
58+
->addOption(new Option(
59+
name: 'verbose',
60+
description: 'Output detailed information.',
61+
));
62+
}
4663

47-
/**
48-
* Flag for showing only translations, without saving
49-
*/
50-
private bool $showNew = false;
64+
protected function execute(array $arguments, array $options): int
65+
{
66+
$locale = $options['locale'];
67+
assert(is_string($locale));
5168

52-
private string $languagePath;
69+
$dir = $options['dir'];
70+
assert(is_string($dir));
5371

54-
public function run(array $params)
55-
{
56-
$this->verbose = array_key_exists('verbose', $params);
57-
$this->showNew = array_key_exists('show-new', $params);
58-
$optionLocale = $params['locale'] ?? null;
59-
$optionDir = $params['dir'] ?? null;
60-
$currentLocale = Locale::getDefault();
61-
$currentDir = APPPATH;
62-
$this->languagePath = $currentDir . 'Language';
72+
$currentLocale = Locale::getDefault();
6373

64-
if (service('environment')->isTesting()) {
65-
$currentDir = SUPPORTPATH . 'Services' . DIRECTORY_SEPARATOR;
66-
$this->languagePath = SUPPORTPATH . 'Language';
67-
}
74+
['currentDir' => $currentDir, 'languagePath' => $this->languagePath] = $this->resolvePaths();
75+
76+
if ($locale !== '') {
77+
$supportedLocales = config(App::class)->supportedLocales;
6878

69-
if (is_string($optionLocale)) {
70-
if (! in_array($optionLocale, config(App::class)->supportedLocales, true)) {
79+
if (! in_array($locale, $supportedLocales, true)) {
7180
CLI::error(
72-
'Error: "' . $optionLocale . '" is not supported. Supported locales: '
73-
. implode(', ', config(App::class)->supportedLocales),
81+
sprintf(
82+
'Error: "%s" is not supported. Supported locales: %s',
83+
$locale,
84+
implode(', ', $supportedLocales),
85+
),
86+
'light_gray',
87+
'red',
7488
);
7589

7690
return EXIT_USER_INPUT;
7791
}
7892

79-
$currentLocale = $optionLocale;
93+
$currentLocale = $locale;
8094
}
8195

82-
if (is_string($optionDir)) {
83-
$tempCurrentDir = realpath($currentDir . $optionDir);
96+
if ($dir !== '') {
97+
$tempCurrentDir = realpath($currentDir . $dir);
8498

8599
if ($tempCurrentDir === false) {
86-
CLI::error('Error: Directory must be located in "' . $currentDir . '"');
100+
CLI::error(sprintf('Error: Directory must be located in "%s"', $currentDir), 'light_gray', 'red');
87101

88102
return EXIT_USER_INPUT;
89103
}
90104

91-
if ($this->isSubDirectory($tempCurrentDir, $this->languagePath)) {
92-
CLI::error('Error: Directory "' . $this->languagePath . '" restricted to scan.');
105+
if ($this->isSubdirectory($tempCurrentDir, $this->languagePath)) {
106+
CLI::error(sprintf('Error: Directory "%s" restricted to scan.', $this->languagePath), 'light_gray', 'red');
93107

94108
return EXIT_USER_INPUT;
95109
}
@@ -99,18 +113,42 @@ public function run(array $params)
99113

100114
$this->process($currentDir, $currentLocale);
101115

102-
CLI::write('All operations done!');
116+
CLI::write('All operations done!', 'green');
103117

104118
return EXIT_SUCCESS;
105119
}
106120

121+
/**
122+
* Resolves the directory to scan and the directory that holds the language
123+
* files, swapping in the test fixtures under the testing environment.
124+
*
125+
* @return array{currentDir: string, languagePath: string}
126+
*/
127+
private function resolvePaths(): array
128+
{
129+
if (service('environment')->isTesting()) {
130+
return [
131+
'currentDir' => SUPPORTPATH . 'Services' . DIRECTORY_SEPARATOR,
132+
'languagePath' => SUPPORTPATH . 'Language',
133+
];
134+
}
135+
136+
return [
137+
'currentDir' => APPPATH,
138+
'languagePath' => APPPATH . 'Language',
139+
];
140+
}
141+
107142
private function process(string $currentDir, string $currentLocale): void
108143
{
144+
$showNew = $this->getValidatedOption('show-new') === true;
145+
$verbose = $this->getValidatedOption('verbose') === true;
146+
109147
$tableRows = [];
110148
$countNewKeys = 0;
111149

112150
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($currentDir));
113-
$files = iterator_to_array($iterator, true);
151+
$files = iterator_to_array($iterator);
114152
ksort($files);
115153

116154
[
@@ -121,10 +159,7 @@ private function process(string $currentDir, string $currentLocale): void
121159

122160
ksort($foundLanguageKeys);
123161

124-
$languageDiff = [];
125-
$languageFoundGroups = array_unique(array_keys($foundLanguageKeys));
126-
127-
foreach ($languageFoundGroups as $langFileName) {
162+
foreach ($foundLanguageKeys as $langFileName => $foundKeys) {
128163
$languageStoredKeys = [];
129164
$languageFilePath = $this->languagePath . DIRECTORY_SEPARATOR . $currentLocale . DIRECTORY_SEPARATOR . $langFileName . '.php';
130165

@@ -137,38 +172,38 @@ private function process(string $currentDir, string $currentLocale): void
137172
// are not new and must not be re-reported or written.
138173
$resolvedKeys = $this->findResolvedTranslations($langFileName, $currentLocale);
139174

140-
$languageDiff = ArrayHelper::recursiveDiff($foundLanguageKeys[$langFileName], $resolvedKeys);
175+
$languageDiff = ArrayHelper::recursiveDiff($foundKeys, $resolvedKeys);
141176
$countNewKeys += ArrayHelper::recursiveCount($languageDiff);
142177

143-
if ($this->showNew) {
178+
if ($showNew) {
144179
$tableRows = array_merge($this->arrayToTableRows($langFileName, $languageDiff), $tableRows);
145180
} else {
146181
$newLanguageKeys = array_replace_recursive($languageDiff, $languageStoredKeys);
147182

148183
if ($languageDiff !== []) {
149184
if (file_put_contents($languageFilePath, $this->templateFile($newLanguageKeys)) === false) {
150-
$this->writeIsVerbose('Lang file ' . $langFileName . ' (error write).', 'red');
185+
$this->writeIsVerbose(sprintf('Lang file %s (error write).', $langFileName), 'red');
151186
} else {
152-
$this->writeIsVerbose('Lang file "' . $langFileName . '" successful updated!', 'green');
187+
$this->writeIsVerbose(sprintf('Lang file "%s" successful updated!', $langFileName), 'green');
153188
}
154189
}
155190
}
156191
}
157192

158-
if ($this->showNew && $tableRows !== []) {
193+
if ($showNew && $tableRows !== []) {
159194
sort($tableRows);
160195
CLI::table($tableRows, ['File', 'Key']);
161196
}
162197

163-
if (! $this->showNew && $countNewKeys > 0) {
198+
if (! $showNew && $countNewKeys > 0) {
164199
CLI::write('Note: You need to run your linting tool to fix coding standards issues.', 'white', 'red');
165200
}
166201

167-
$this->writeIsVerbose('Files found: ' . $countFiles);
168-
$this->writeIsVerbose('New translates found: ' . $countNewKeys);
169-
$this->writeIsVerbose('Bad translates found: ' . count($badLanguageKeys));
202+
$this->writeIsVerbose(sprintf('Files found: %d', $countFiles));
203+
$this->writeIsVerbose(sprintf('New translates found: %d', $countNewKeys));
204+
$this->writeIsVerbose(sprintf('Bad translates found: %d', count($badLanguageKeys)));
170205

171-
if ($this->verbose && $badLanguageKeys !== []) {
206+
if ($verbose && $badLanguageKeys !== []) {
172207
$tableBadRows = [];
173208

174209
foreach ($badLanguageKeys as $value) {
@@ -211,19 +246,13 @@ private function findResolvedTranslations(string $langFileName, string $currentL
211246
}
212247

213248
/**
214-
* @param SplFileInfo|string $file
215-
*
216-
* @return array<string, array>
249+
* @return array{foundLanguageKeys: array<string, mixed>, badLanguageKeys: list<array{string, string}>}
217250
*/
218-
private function findTranslationsInFile($file): array
251+
private function findTranslationsInFile(SplFileInfo $file): array
219252
{
220253
$foundLanguageKeys = [];
221254
$badLanguageKeys = [];
222255

223-
if (is_string($file) && is_file($file)) {
224-
$file = new SplFileInfo($file);
225-
}
226-
227256
$fileContent = file_get_contents($file->getRealPath());
228257
preg_match_all('/lang\(\'([._a-z0-9\-]+)\'\)/ui', $fileContent, $matches);
229258

@@ -266,13 +295,16 @@ private function findTranslationsInFile($file): array
266295

267296
private function isIgnoredFile(SplFileInfo $file): bool
268297
{
269-
if ($file->isDir() || $this->isSubDirectory($file->getRealPath(), $this->languagePath)) {
298+
if ($file->isDir() || $this->isSubdirectory($file->getRealPath(), $this->languagePath)) {
270299
return true;
271300
}
272301

273302
return $file->getExtension() !== 'php';
274303
}
275304

305+
/**
306+
* @param array<array-key, mixed> $language
307+
*/
276308
private function templateFile(array $language = []): string
277309
{
278310
if ($language !== []) {
@@ -337,6 +369,10 @@ private function replaceArraySyntax(string $code): string
337369

338370
/**
339371
* Create multidimensional array from another keys
372+
*
373+
* @param list<string> $fromKeys
374+
*
375+
* @return array<string, mixed>
340376
*/
341377
private function buildMultiArray(array $fromKeys, string $lastArrayValue = ''): array
342378
{
@@ -356,6 +392,10 @@ private function buildMultiArray(array $fromKeys, string $lastArrayValue = ''):
356392

357393
/**
358394
* Convert multi arrays to specific CLI table rows (flat array)
395+
*
396+
* @param array<array-key, mixed> $array
397+
*
398+
* @return list<array{string, string}>
359399
*/
360400
private function arrayToTableRows(string $langFileName, array $array): array
361401
{
@@ -381,12 +421,12 @@ private function arrayToTableRows(string $langFileName, array $array): array
381421
*/
382422
private function writeIsVerbose(string $text = '', ?string $foreground = null, ?string $background = null): void
383423
{
384-
if ($this->verbose) {
424+
if ($this->getValidatedOption('verbose') === true) {
385425
CLI::write($text, $foreground, $background);
386426
}
387427
}
388428

389-
private function isSubDirectory(string $directory, string $rootDirectory): bool
429+
private function isSubdirectory(string $directory, string $rootDirectory): bool
390430
{
391431
return 0 === strncmp($directory, $rootDirectory, strlen($directory));
392432
}
@@ -407,7 +447,7 @@ private function findLanguageKeysInFiles(array $files): array
407447
continue;
408448
}
409449

410-
$this->writeIsVerbose('File found: ' . mb_substr($file->getRealPath(), mb_strlen(APPPATH)));
450+
$this->writeIsVerbose(sprintf('File found: %s', mb_substr($file->getRealPath(), mb_strlen(APPPATH))));
411451
$countFiles++;
412452

413453
$findInFile = $this->findTranslationsInFile($file);

0 commit comments

Comments
 (0)