├── 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 | ![Alt text](http://www.robinfranssen.be/screenshot0.png "Scan everything") 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 | ![Alt text](http://www.robinfranssen.be/screenshot1.png "Scan everything") 35 | 36 | `php artisan locale:invalid` will show you the invalid keys. 37 | 38 | ![Alt text](http://www.robinfranssen.be/screenshot2.png "Scan invalid keys") 39 | 40 | `php artisan locale:untranslated` will show you the untranslated keys. 41 | 42 | ![Alt text](http://www.robinfranssen.be/screenshot3.png "Scan untranslated keys") 43 | 44 | `php artisan locale:allkeys` will show you a table with untranslated and invalid keys. 45 | 46 | ![Alt text](http://www.robinfranssen.be/screenshot4.png "scan all keys") 47 | 48 | Every command supports the --locale flag. 49 | For example: `php artisan locale:allkeys --locale=nl` 50 | 51 | ![Alt text](http://www.robinfranssen.be/screenshot5.png "Scan with different locale") 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 | } --------------------------------------------------------------------------------