├── README.md
├── composer.json
└── src
├── Console
└── Commands
│ ├── AllKeysCommand.php
│ ├── AnalyzeLocaleCommand.php
│ ├── InvalidCommand.php
│ └── UntranslatedCommand.php
└── Providers
└── ServiceProvider.php
/README.md:
--------------------------------------------------------------------------------
1 | # Translation helpers for Laravel 5
2 | -----
3 |
4 | **NOTE: As of laravel 5.2.20 you need version ^0.1.4. For laravel 5.2.19 or lower you need version 0.1.3 of this package.**
5 |
6 | -----
7 | This package provides you with commands to analyze the translation keys used in your Laravel 5 application.
8 |
9 | These commands can detect untranslated and invalid keys.
10 |
11 | 
12 |
13 | ## Installation
14 |
15 | ```bash
16 | composer require robinfranssen/analyzelocale --dev
17 | ```
18 |
19 | Add the service provider:
20 | ```php
21 | // config/app.php
22 |
23 | 'providers' => [
24 | ...
25 | 'RobinFranssen\AnalyzeLocale\Providers\ServiceProvider',
26 | ...
27 | ],
28 | ```
29 | ## Usage
30 |
31 | From the command line, run `php artisan locale:scan` to see a full overview of the analyzation.
32 | This will show you all information provided by the three other locale commands.
33 |
34 | 
35 |
36 | `php artisan locale:invalid` will show you the invalid keys.
37 |
38 | 
39 |
40 | `php artisan locale:untranslated` will show you the untranslated keys.
41 |
42 | 
43 |
44 | `php artisan locale:allkeys` will show you a table with untranslated and invalid keys.
45 |
46 | 
47 |
48 | Every command supports the --locale flag.
49 | For example: `php artisan locale:allkeys --locale=nl`
50 |
51 | 
52 |
53 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "robinfranssen/analyzelocale",
3 | "description": "This scans your laravel project for used locale keys that aren't translated by your locale files.",
4 | "keywords": ["artisan", "command", "analyze", "translation", "locale"],
5 | "type": "library",
6 | "homepage": "https://github.com/aXent/laravel-analyze-locale",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Robin Franssen",
11 | "email": "robin@dev-it.be",
12 | "homepage": "http://www.dev-it.be"
13 | }
14 | ],
15 | "require": {
16 | "php": ">=5.4.0"
17 | },
18 | "autoload": {
19 | "psr-4": {
20 | "RobinFranssen\\AnalyzeLocale\\": "src/"
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/Console/Commands/AllKeysCommand.php:
--------------------------------------------------------------------------------
1 | call('locale:scan',
42 | ['--show' => ['allkeys'], '--locale' => $this->option('locale')]);
43 | }
44 |
45 | /**
46 | * Get the console command options.
47 | *
48 | * @return array
49 | */
50 | protected function getOptions()
51 | {
52 | return [
53 | ['locale', 'l', InputOption::VALUE_OPTIONAL, 'Scan specified locale for keys.', \App::getLocale()],
54 | ];
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/Console/Commands/AnalyzeLocaleCommand.php:
--------------------------------------------------------------------------------
1 | option('show') as $option) {
77 | if (! in_array($option, $this->availableOptions)) {
78 | $this->error(sprintf('Undefined --show option "%s" used.', $option));
79 | exit();
80 | }
81 | }
82 |
83 | //Setting app locale while command is running.
84 | //setting fallback locale to same locale.
85 | //else Lang::has(key) will report a key translated when the key is not for the defined locale.
86 | \Lang::setLocale($this->option('locale'));
87 | \Lang::setFallback($this->option('locale'));
88 |
89 | //Make regexiterator with php files.
90 | $this->info('Preparing files');
91 | $this->findPHPFiles();
92 |
93 | //Count total files.
94 | $totalFiles = iterator_count($this->phpFiles);
95 |
96 | //Search for the translation keys in file.
97 | $this->info('Searching translationkeys in '. $totalFiles .' files');
98 | $this->findTranslationKeysInFiles();
99 |
100 | $this->info('Analyzing translation keys for '.$this->option('locale').' locale');
101 | $this->findInvalidKeys();
102 | $this->findUntranslatedKeys();
103 |
104 | if (in_array('all', $this->option('show'))
105 | || in_array('allkeys', $this->option('show'))) {
106 | $this->info('Keys used by this laravel application');
107 | $this->showAllResults();
108 | }
109 |
110 | if (in_array('all', $this->option('show'))
111 | || in_array('invalid', $this->option('show'))) {
112 | $this->info('Invalid keys used: ');
113 | $this->showInvalidKeys();
114 | }
115 |
116 | if (in_array('all', $this->option('show'))
117 | || in_array('untranslated', $this->option('show'))) {
118 | $this->info('Untranslated keys:');
119 | $this->showUntranslatedKeys();
120 | }
121 | }
122 |
123 | /**
124 | * Get the console command arguments.
125 | *
126 | * @return array
127 | */
128 | protected function getArguments()
129 | {
130 | return [
131 | //['example', InputArgument::REQUIRED, 'An example argument.'],
132 | ];
133 | }
134 |
135 | /**
136 | * Get the console command options.
137 | *
138 | * @return array
139 | */
140 | protected function getOptions()
141 | {
142 | return [
143 | ['locale', 'l', InputOption::VALUE_OPTIONAL, 'Scan files for specified locale', \App::getLocale()],
144 | [
145 | 'show',
146 | null,
147 | InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
148 | 'Show all information about the translation keys. --show=[all|allkeys|untranslated|invalid]',
149 | ['all']],
150 | ['debug', null, InputOption::VALUE_NONE, 'Show debug information', null],
151 | ];
152 | }
153 |
154 | /**
155 | *
156 | */
157 | private function findPHPFiles()
158 | {
159 | $directory = new \RecursiveDirectoryIterator(base_path());
160 | $iterator = new \RecursiveIteratorIterator($directory);
161 | $this->phpFiles = new \RegexIterator($iterator, '/^.+\.php$/i', \RecursiveRegexIterator::GET_MATCH);
162 | }
163 |
164 | /**
165 | * Scan all file contents for the lang functions off laravel.
166 | * @lang(key), lang(key) and Lang::get(key[, [something something])
167 | */
168 | private function findTranslationKeysInFiles()
169 | {
170 | $this->output->progressStart(iterator_count($this->phpFiles));
171 |
172 | $all = [];
173 |
174 | foreach ($this->phpFiles as $file => $a) {
175 | $this->output->progressAdvance();
176 |
177 | $keys = [];
178 | $matches = [];
179 | $fileContent = file_get_contents($file);
180 |
181 | /**
182 | * Match all @lang and trans functioncalls in file. Usually only in blade.php files.
183 | *
184 | * Ignores symfony's translator. Always translates through $instance->trans()
185 | * This translator is found in testfiles.
186 | *
187 | **/
188 |
189 | $localePattern = "/((?<=lang::get\\([\\'|\"])|(?<=(?)trans\\([\\'|\"])|(?<=(?all = $all; //Only unique values.
200 |
201 | $this->output->progressFinish();
202 | }
203 |
204 | /**
205 | * Helper function for nice presentation.
206 | *
207 | * @param array $keys
208 | * @return array
209 | */
210 | private function analyzeKeys(array $keys)
211 | {
212 | $all = [];
213 |
214 | foreach ($keys as $key => $file)
215 | {
216 | list($namespace, $group, $item) = \Lang::parseKey($key);
217 | $all[$namespace][$group][] = $item;
218 | }
219 |
220 |
221 | ksort($all);
222 |
223 | return $all;
224 | }
225 |
226 |
227 | /**
228 | * Finds the invalid keys from the result array.
229 | *
230 | * @return array returns array with valid translation keys.
231 | */
232 | private function findInvalidKeys()
233 | {
234 | foreach($this->all as $key => $file) {
235 | if ($this->option('debug')) {
236 | $this->comment('Checking if key is invalid: ' . $key);
237 | }
238 | if (strpos($key, '.') === false) {
239 | $this->invalidKeys[$key] = $file;
240 | }
241 | }
242 | }
243 |
244 | /**
245 | * Finds the untranslated keys from the result array.
246 | *
247 | * @return array
248 | */
249 | private function findUntranslatedKeys()
250 | {
251 | foreach(array_diff(array_keys($this->all), array_keys($this->invalidKeys)) as $key) {
252 | if ($this->option('debug')) {
253 | $format = 'Translation for %s in locale %s found: %s';
254 | $this->comment(sprintf($format, $key, $this->option('locale'), (\Lang::has($key) ? 'Yes' : 'No')));
255 | }
256 | if (!\Lang::has($key)) {
257 | $this->untranslatedKeys[$key] = $this->all[$key];
258 | }
259 | }
260 | }
261 |
262 | /**
263 | * Show table with invalid keys.
264 | */
265 | private function showInvalidKeys()
266 | {
267 | if (empty($this->invalidKeys)) {
268 | $this->info('No invalid keys found in project files');
269 | return;
270 | }
271 | $table = new Table($this->output);
272 | $table->setHeaders([
273 | 'Key',
274 | 'File'
275 | ]);
276 |
277 | foreach ($this->invalidKeys as $key => $file)
278 | {
279 | $table->addRow([$key, str_replace(base_path(), '', $file)]);
280 | }
281 |
282 | $table->render();
283 | }
284 |
285 | /**
286 | *
287 | */
288 | private function showUntranslatedKeys()
289 | {
290 | if (empty($this->untranslatedKeys)) {
291 | $this->info('All keys seem te be translated, congratz!');
292 | return;
293 | }
294 |
295 | $analyzedKeys = $this->analyzeKeys($this->untranslatedKeys);
296 |
297 | $table = new Table($this->output);
298 | $table->setHeaders([
299 | 'Package',
300 | 'File',
301 | 'Item',
302 | 'Full key',
303 | ]);
304 |
305 |
306 | foreach ($analyzedKeys as $packageName => $package)
307 | {
308 |
309 | foreach ($package as $file => $keys)
310 | {
311 | $isPackage = (!empty($packageName) && $packageName != "*");
312 | $row[0] = $isPackage ? $packageName : 'No package';
313 | $row[1] = $file;
314 |
315 | foreach ($keys as $key)
316 | {
317 | $row[2] = $key;
318 | $row[3] = sprintf('%s%s.%s', ($isPackage ? $packageName.'::': ''), $file, $key);
319 |
320 | $table->addRow($row);
321 | }
322 | }
323 | }
324 |
325 | $table->render();
326 | }
327 |
328 | /**
329 | *
330 | */
331 | private function showAllResults()
332 | {
333 | $checkmark = mb_convert_encoding('✔', 'UTF-8', 'HTML-ENTITIES');
334 | $crossmark = mb_convert_encoding('❌', 'UTF-8', 'HTML-ENTITIES');
335 |
336 | $info = '%s';
337 |
338 | $table = new Table($this->output);
339 | $table->setHeaders([
340 | 'Key',
341 | 'Translated item',
342 | 'Invalid item',
343 | 'File',
344 | ]);
345 |
346 | foreach ($this->all as $key => $file)
347 | {
348 | $translated = (!in_array($key, array_keys($this->untranslatedKeys)));
349 | $invalid = (in_array($key, array_keys($this->invalidKeys)));
350 |
351 | $table->addRow([
352 | $key,
353 | sprintf($info, ($translated && !$invalid ? $checkmark: '')),
354 | sprintf($info, ($invalid ? $checkmark: '')),
355 | str_replace(base_path(), '', $file),
356 | ]);
357 | }
358 |
359 | $table->render();
360 |
361 | }
362 |
363 | }
364 |
--------------------------------------------------------------------------------
/src/Console/Commands/InvalidCommand.php:
--------------------------------------------------------------------------------
1 | call('locale:scan',
42 | ['--show' => ['invalid'], '--locale' => $this->option('locale')]);
43 | }
44 |
45 | /**
46 | * Get the console command options.
47 | *
48 | * @return array
49 | */
50 | protected function getOptions()
51 | {
52 | return [
53 | ['locale', 'l', InputOption::VALUE_OPTIONAL, 'Scan specified locale for invalid keys.', \App::getLocale()],
54 | ];
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/Console/Commands/UntranslatedCommand.php:
--------------------------------------------------------------------------------
1 | call('locale:scan',
41 | ['--show' => ['untranslated'], '--locale' => $this->option('locale')]);
42 | }
43 |
44 | /**
45 | * Get the console command options.
46 | *
47 | * @return array
48 | */
49 | protected function getOptions()
50 | {
51 | return [
52 | ['locale', 'l', InputOption::VALUE_OPTIONAL, 'Scan specified locale for untranslated keys.', \App::getLocale()],
53 | ];
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Providers/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->singleton('command.locale.scan', function () {
14 | return new AnalyzeLocaleCommand();
15 | });
16 |
17 | $this->app->singleton('command.locale.allkeys', function () {
18 | return new AllKeysCommand();
19 | });
20 |
21 | $this->app->singleton('command.locale.untranslated', function () {
22 | return new UntranslatedCommand();
23 | });
24 |
25 | $this->app->singleton('command.locale.invalid', function () {
26 | return new InvalidCommand();
27 | });
28 |
29 | $this->commands([
30 | 'command.locale.scan',
31 | 'command.locale.allkeys',
32 | 'command.locale.untranslated',
33 | 'command.locale.invalid',
34 | ]);
35 | }
36 | }
--------------------------------------------------------------------------------