1313
1414namespace CodeIgniter \Commands \Translation ;
1515
16- use CodeIgniter \CLI \BaseCommand ;
16+ use CodeIgniter \CLI \AbstractCommand ;
17+ use CodeIgniter \CLI \Attributes \Command ;
1718use CodeIgniter \CLI \CLI ;
19+ use CodeIgniter \CLI \Input \Option ;
1820use CodeIgniter \Helpers \Array \ArrayHelper ;
1921use Config \App ;
2022use Locale ;
2325use 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,44 @@ 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+ $ paths = [
130+ 'currentDir ' => APPPATH ,
131+ 'languagePath ' => APPPATH . 'Language ' ,
132+ ];
133+
134+ if (service ('environment ' )->isTesting ()) {
135+ $ paths = [
136+ 'currentDir ' => SUPPORTPATH . 'Services ' . DIRECTORY_SEPARATOR ,
137+ 'languagePath ' => SUPPORTPATH . 'Language ' ,
138+ ];
139+ }
140+
141+ return $ paths ;
142+ }
143+
107144 private function process (string $ currentDir , string $ currentLocale ): void
108145 {
146+ $ showNew = $ this ->getValidatedOption ('show-new ' ) === true ;
147+ $ verbose = $ this ->getValidatedOption ('verbose ' ) === true ;
148+
109149 $ tableRows = [];
110150 $ countNewKeys = 0 ;
111151
112152 $ iterator = new RecursiveIteratorIterator (new RecursiveDirectoryIterator ($ currentDir ));
113- $ files = iterator_to_array ($ iterator, true );
153+ $ files = iterator_to_array ($ iterator );
114154 ksort ($ files );
115155
116156 [
@@ -121,10 +161,7 @@ private function process(string $currentDir, string $currentLocale): void
121161
122162 ksort ($ foundLanguageKeys );
123163
124- $ languageDiff = [];
125- $ languageFoundGroups = array_unique (array_keys ($ foundLanguageKeys ));
126-
127- foreach ($ languageFoundGroups as $ langFileName ) {
164+ foreach ($ foundLanguageKeys as $ langFileName => $ foundKeys ) {
128165 $ languageStoredKeys = [];
129166 $ languageFilePath = $ this ->languagePath . DIRECTORY_SEPARATOR . $ currentLocale . DIRECTORY_SEPARATOR . $ langFileName . '.php ' ;
130167
@@ -133,38 +170,38 @@ private function process(string $currentDir, string $currentLocale): void
133170 $ languageStoredKeys = require $ languageFilePath ;
134171 }
135172
136- $ languageDiff = ArrayHelper::recursiveDiff ($ foundLanguageKeys [ $ langFileName ] , $ languageStoredKeys );
173+ $ languageDiff = ArrayHelper::recursiveDiff ($ foundKeys , $ languageStoredKeys );
137174 $ countNewKeys += ArrayHelper::recursiveCount ($ languageDiff );
138175
139- if ($ this -> showNew ) {
176+ if ($ showNew ) {
140177 $ tableRows = array_merge ($ this ->arrayToTableRows ($ langFileName , $ languageDiff ), $ tableRows );
141178 } else {
142- $ newLanguageKeys = array_replace_recursive ($ foundLanguageKeys [ $ langFileName ] , $ languageStoredKeys );
179+ $ newLanguageKeys = array_replace_recursive ($ foundKeys , $ languageStoredKeys );
143180
144181 if ($ languageDiff !== []) {
145182 if (file_put_contents ($ languageFilePath , $ this ->templateFile ($ newLanguageKeys )) === false ) {
146- $ this ->writeIsVerbose ('Lang file ' . $ langFileName . ' (error write). ' , 'red ' );
183+ $ this ->writeIsVerbose (sprintf ( 'Lang file %s (error write). ' , $ langFileName ) , 'red ' );
147184 } else {
148- $ this ->writeIsVerbose ('Lang file " ' . $ langFileName . ' " successful updated! ' , 'green ' );
185+ $ this ->writeIsVerbose (sprintf ( 'Lang file "%s " successful updated! ' , $ langFileName ) , 'green ' );
149186 }
150187 }
151188 }
152189 }
153190
154- if ($ this -> showNew && $ tableRows !== []) {
191+ if ($ showNew && $ tableRows !== []) {
155192 sort ($ tableRows );
156193 CLI ::table ($ tableRows , ['File ' , 'Key ' ]);
157194 }
158195
159- if (! $ this -> showNew && $ countNewKeys > 0 ) {
196+ if (! $ showNew && $ countNewKeys > 0 ) {
160197 CLI ::write ('Note: You need to run your linting tool to fix coding standards issues. ' , 'white ' , 'red ' );
161198 }
162199
163- $ this ->writeIsVerbose ('Files found: ' . $ countFiles );
164- $ this ->writeIsVerbose ('New translates found: ' . $ countNewKeys );
165- $ this ->writeIsVerbose ('Bad translates found: ' . count ($ badLanguageKeys ));
200+ $ this ->writeIsVerbose (sprintf ( 'Files found: %d ' , $ countFiles) );
201+ $ this ->writeIsVerbose (sprintf ( 'New translates found: %d ' , $ countNewKeys) );
202+ $ this ->writeIsVerbose (sprintf ( 'Bad translates found: %d ' , count ($ badLanguageKeys) ));
166203
167- if ($ this -> verbose && $ badLanguageKeys !== []) {
204+ if ($ verbose && $ badLanguageKeys !== []) {
168205 $ tableBadRows = [];
169206
170207 foreach ($ badLanguageKeys as $ value ) {
@@ -178,19 +215,13 @@ private function process(string $currentDir, string $currentLocale): void
178215 }
179216
180217 /**
181- * @param SplFileInfo|string $file
182- *
183- * @return array<string, array>
218+ * @return array{foundLanguageKeys: array<string, mixed>, badLanguageKeys: list<array{string, string}>}
184219 */
185- private function findTranslationsInFile ($ file ): array
220+ private function findTranslationsInFile (SplFileInfo $ file ): array
186221 {
187222 $ foundLanguageKeys = [];
188223 $ badLanguageKeys = [];
189224
190- if (is_string ($ file ) && is_file ($ file )) {
191- $ file = new SplFileInfo ($ file );
192- }
193-
194225 $ fileContent = file_get_contents ($ file ->getRealPath ());
195226 preg_match_all ('/lang\( \'([._a-z0-9\-]+) \'\)/ui ' , $ fileContent , $ matches );
196227
@@ -233,13 +264,16 @@ private function findTranslationsInFile($file): array
233264
234265 private function isIgnoredFile (SplFileInfo $ file ): bool
235266 {
236- if ($ file ->isDir () || $ this ->isSubDirectory ($ file ->getRealPath (), $ this ->languagePath )) {
267+ if ($ file ->isDir () || $ this ->isSubdirectory ($ file ->getRealPath (), $ this ->languagePath )) {
237268 return true ;
238269 }
239270
240271 return $ file ->getExtension () !== 'php ' ;
241272 }
242273
274+ /**
275+ * @param array<array-key, mixed> $language
276+ */
243277 private function templateFile (array $ language = []): string
244278 {
245279 if ($ language !== []) {
@@ -304,6 +338,10 @@ private function replaceArraySyntax(string $code): string
304338
305339 /**
306340 * Create multidimensional array from another keys
341+ *
342+ * @param list<string> $fromKeys
343+ *
344+ * @return array<string, mixed>
307345 */
308346 private function buildMultiArray (array $ fromKeys , string $ lastArrayValue = '' ): array
309347 {
@@ -323,6 +361,10 @@ private function buildMultiArray(array $fromKeys, string $lastArrayValue = ''):
323361
324362 /**
325363 * Convert multi arrays to specific CLI table rows (flat array)
364+ *
365+ * @param array<array-key, mixed> $array
366+ *
367+ * @return list<array{string, string}>
326368 */
327369 private function arrayToTableRows (string $ langFileName , array $ array ): array
328370 {
@@ -348,12 +390,12 @@ private function arrayToTableRows(string $langFileName, array $array): array
348390 */
349391 private function writeIsVerbose (string $ text = '' , ?string $ foreground = null , ?string $ background = null ): void
350392 {
351- if ($ this ->verbose ) {
393+ if ($ this ->getValidatedOption ( ' verbose ' ) === true ) {
352394 CLI ::write ($ text , $ foreground , $ background );
353395 }
354396 }
355397
356- private function isSubDirectory (string $ directory , string $ rootDirectory ): bool
398+ private function isSubdirectory (string $ directory , string $ rootDirectory ): bool
357399 {
358400 return 0 === strncmp ($ directory , $ rootDirectory , strlen ($ directory ));
359401 }
@@ -374,7 +416,7 @@ private function findLanguageKeysInFiles(array $files): array
374416 continue ;
375417 }
376418
377- $ this ->writeIsVerbose ('File found: ' . mb_substr ($ file ->getRealPath (), mb_strlen (APPPATH )));
419+ $ this ->writeIsVerbose (sprintf ( 'File found: %s ' , mb_substr ($ file ->getRealPath (), mb_strlen (APPPATH ) )));
378420 $ countFiles ++;
379421
380422 $ findInFile = $ this ->findTranslationsInFile ($ file );
0 commit comments