diff --git a/README.md b/README.md index 48e781f..687dbf6 100644 --- a/README.md +++ b/README.md @@ -24,14 +24,14 @@ Begin by installing the package through Composer. Run the following command in y $ composer require themsaid/laravel-langman ``` -Once done, add the following line in your providers array of `config/app.php`: +Once done, check that the following line was added in your providers array of `config/app.php`: ```php Themsaid\Langman\LangmanServiceProvider::class ``` -This package has a single configuration option that points to the `resources/lang` directory, if only you need to change -the path then publish the config file: +This package has a single configuration option that points to the `resources/lang` directory. If you +only need to change the path then publish the config file: ``` php artisan vendor:publish --provider="Themsaid\Langman\LangmanServiceProvider" @@ -40,12 +40,34 @@ php artisan vendor:publish --provider="Themsaid\Langman\LangmanServiceProvider" ## Usage ### Showing lines of a translation file +``` +php artisan langman:show [file.][key] [--close] [--unused] [--lang ] +``` + +In the table returned by the Show command, if a translation is missing it'll be marked in red. + +``` +php artisan langman:show +``` +Shows all keys in the JSON translation located at `lang/.json`. + +Example output: +``` ++-----------------------------+---------------+-------------+ +| key | en | nl | ++-----------------------------+---------------+-------------+ +| What is in a name? | MISSING | MISSING | +| Do you need more proof? | MISSING | MISSING | ++-----------------------------+---------------+-------------+ +``` ``` php artisan langman:show users ``` +Shows all keys in the `lang//users.php` translation file. If no such file exists, langman +assumes that you are searching in the list of JSON strings. -You get: +Example output: ``` +---------+---------------+-------------+ @@ -62,7 +84,8 @@ You get: php artisan langman:show users.name ``` -Brings only the translation of the `name` key in all languages. +Shows only the translation of the `name` key in all languages as found in the `lang//users.php` +translation files. --- @@ -70,7 +93,7 @@ Brings only the translation of the `name` key in all languages. php artisan langman:show users.name.first ``` -Brings the translation of a nested key. +Shows the translation of a nested key. --- @@ -78,7 +101,7 @@ Brings the translation of a nested key. php artisan langman:show package::users.name ``` -Brings the translation of a vendor package language file. +Shows the translation of a vendor package language file. --- @@ -86,18 +109,43 @@ Brings the translation of a vendor package language file. php artisan langman:show users --lang=en,it ``` -Brings the translation of only the "en" and "it" languages. +Shows the translation of only the "en" and "it" languages. --- ``` php artisan langman:show users.nam -c +php artisan langman:show users.nam --close ``` -Brings only the translation lines with keys matching the given key via close match, so searching for `nam` brings values for -keys like (`name`, `username`, `branch_name_required`, etc...). +Shows only the translation lines with keys containing the given key via substring match, so searching for +`nam` brings values for keys like (`name`, `username`, `branch_name_required`, etc...). + +If the close option is specified, the file/key option is always interpreted as a key if it does not +contain a dot, or the resulting file does not exist. E.g., if the `lang//users.php` file does not exist, +then: +``` +php artisan langman:show users --close +``` +would show all keys containing the 'users' string in the JSON translation files. + +Similarly: +``` +php artisan langman:show "I don't know. Perhaps" --close +``` +will not look for a file called `"I don't know.php"`, but interpret the whole provided element as a key to search for. + +--- + +``` +php artisan langman:show users -u +php artisan langman:show users --unused +``` + +Scans all the view templates and application files (see Sync command) and outputs only the keys found in the +specified file that were not used in any template or file. This helps identifying legacy elements and keep +your translation files tight and orderly. -In the table returned by this command, if a translation is missing it'll be marked in red. ### Finding a translation line @@ -105,7 +153,8 @@ In the table returned by this command, if a translation is missing it'll be mark php artisan langman:find 'log in first' ``` -You get a table of language lines where any of the values matches the given phrase by close match. +You get a table of language lines where any of the values contains the given phrase. Strings from the JSON +translation files are capped at 40 characters to keep the output tidy. ### Searching view files for missing translations @@ -113,8 +162,10 @@ You get a table of language lines where any of the values matches the given phra php artisan langman:sync ``` -This command will look into all files in `resources/views` and `app` and find all translation keys that are not covered in your translation files, after -that it appends those keys to the files with a value equal to an empty string. +This command will look into all files in `resources/views` and `app` and find all translation keys that are not +covered in your translation files. After that it appends those keys to the files with an empty value. Then it +synchronises all translation files and adds all missing entries in each file, ensuring that all translation files +for all locales contain an entry for the same set of keys. ### Filling missing translations @@ -122,8 +173,8 @@ that it appends those keys to the files with a value equal to an empty string. php artisan langman:missing ``` -It'll collect all the keys that are missing in any of the languages or has values equals to an empty string, prompt -asking you to give a translation for each, and finally save the given values to the files. +This command collects all the keys that are missing or empty in any of the languages, prompt you for a +translation for each and finally saves the given values to the proper files. ### Translating a key @@ -132,34 +183,46 @@ php artisan langman:trans users.name php artisan langman:trans users.name.first php artisan langman:trans users.name --lang=en php artisan langman:trans package::users.name +php artisan langman:trans 'Translate Me' ``` -Using this command you may set a language key (plain or nested) for a given group, you may also specify which language you wish to set leaving the other languages as is. +Using this command you may set a language key (plain or nested) for a given group. You may also specify +which language you wish to set, leaving the other languages as is. -This command will add a new key if not existing, and updates the key if it is already there. +This command will add a new key if it did not exist yet and updates the key if it is already there. ### Removing a key ``` php artisan langman:remove users.name php artisan langman:remove package::users.name +php artisan langman:remove 'Random JSON string' ``` -It'll remove that key from all language files. +Removes the specified key from all relevant language files. ### Renaming a key ``` php artisan langman:rename users.name full_name ``` +This will rename `users.name` to `users.full_name`. The console will output a list of files where +the key used to exist. -This will rename `users.name` to be `users.full_name`, the console will output a list of files where the key used to exist. +``` +php artisan langman:rename 'Json Search String' 'New Json Search String' +``` +This will rename the JSON translatable string `'Json Search String'` to `'New Json Search String'` in all +relevant JSON translation files. ## Notes -`langman:sync`, `langman:missing`, `langman:trans`, and `langman:remove` will update your language files by writing them completely, meaning that any comments or special styling will be removed, so I recommend you backup your files. +`langman:sync`, `langman:missing`, `langman:trans`, and `langman:remove` will update your language files +by rewriting them completely, meaning that any comments or special styling will be removed. I recommend +that you backup your files if this is the first time you are running the tool. Langman sorts all keys in +files alphabetically by key name. ## Web interface -If you want a web interface to manage your language files instead, I recommend [Laravel 5 Translation Manager](https://github.com/barryvdh/laravel-translation-manager) -by [Barry vd. Heuvel](https://github.com/barryvdh). +If you want a web interface to manage your language files instead, I recommend +[Laravel Translation Manager](https://github.com/barryvdh/laravel-translation-manager) by [Barry vd. Heuvel](https://github.com/barryvdh). diff --git a/composer.json b/composer.json index 580cfe6..b9586e8 100644 --- a/composer.json +++ b/composer.json @@ -13,9 +13,9 @@ ], "require": { "php": "^5.5.9 || ^7.0", - "illuminate/support": "~5.1", - "illuminate/console": "~5.1", - "illuminate/filesystem": "~5.1" + "illuminate/support": "~5.1 || ^6.0 || ^7.0", + "illuminate/console": "~5.1 || ^6.0 || ^7.0", + "illuminate/filesystem": "~5.1 || ^6.0 || ^7.0" }, "require-dev": { "phpunit/phpunit" : "^4.8 || ^5.0", diff --git a/src/Commands/FindCommand.php b/src/Commands/FindCommand.php index 73cad05..025d771 100644 --- a/src/Commands/FindCommand.php +++ b/src/Commands/FindCommand.php @@ -118,7 +118,11 @@ private function tableRows() // Sort the language values based on language name ksort($original); - $output[$fullKey] = array_merge(['key' => "$fullKey"], $original); + $okey = $fullKey; + if ($fileName == "-json") { + $okey = strlen($key) > 40 ? substr($key, 0, 36)." ..." : $key; + } + $output[$fullKey] = array_merge(['key' => "$okey"], $original); } return array_values($output); diff --git a/src/Commands/MissingCommand.php b/src/Commands/MissingCommand.php index a1c9818..e78ab23 100644 --- a/src/Commands/MissingCommand.php +++ b/src/Commands/MissingCommand.php @@ -95,8 +95,12 @@ private function collectValues(array $missing) $values = []; foreach ($missing as $missingKey) { + $key = $missingKey; + if (substr($key, 0, 6) == "-json.") { + $key = substr($key, 6); + } $values[$missingKey] = $this->ask( - "{$missingKey} translation", $this->getDefaultValue($missingKey) + "{$key} translation", $this->getDefaultValue($missingKey) ); } diff --git a/src/Commands/RemoveCommand.php b/src/Commands/RemoveCommand.php index 24e9817..d9aacd9 100644 --- a/src/Commands/RemoveCommand.php +++ b/src/Commands/RemoveCommand.php @@ -59,12 +59,15 @@ public function handle() try { list($file, $key) = explode('.', $this->argument('key'), 2); } catch (\ErrorException $e) { - $this->error('Could not recognize the key you want to remove.'); - - return; + $file = "-json"; + $key = $this->argument('key'); } - if ($this->confirm("Are you sure you want to remove \"{$file}.{$key}\"?")) { + $fileName=$file."."; + if ($file === "-json") { + $fileName=""; + } + if ($this->confirm("Are you sure you want to remove \"{$fileName}{$key}\"?")) { if (Str::contains($file, '::')) { try { $parts = explode('::', $file); @@ -81,7 +84,7 @@ public function handle() $this->manager->removeKey($file, $key); - $this->info("{$file}.{$key} was removed successfully."); + $this->info("{$fileName}{$key} was removed successfully."); } } } diff --git a/src/Commands/RenameCommand.php b/src/Commands/RenameCommand.php index 38db80b..50476b4 100644 --- a/src/Commands/RenameCommand.php +++ b/src/Commands/RenameCommand.php @@ -61,7 +61,7 @@ public function handle() $this->listFilesContainingOldKey(); - $this->info('The key at '.$this->argument('oldKey').' was renamed to '.$this->argument('newKey').' successfully!'); + $this->info('The key at "'.$this->argument('oldKey').'" was renamed to "'.$this->argument('newKey').'" successfully!'); } /** @@ -74,18 +74,19 @@ private function renameKey() try { list($file, $key) = explode('.', $this->argument('oldKey'), 2); } catch (\ErrorException $e) { - $this->error('Could not recognize the key you want to rename.'); - - return; + $file = "-json"; + $key = $this->argument('oldKey'); } - if (Str::contains($this->argument('newKey'), '.')) { - $this->error('Please provide the new key must not contain a dot.'); - + if (Str::contains($this->argument('newKey'), '.') && $file !== "-json") { + $this->error('The provided new key must not contain a dot.'); return; } - $newKey = preg_replace('/(\w+)$/i', $this->argument('newKey'), $key); + $newKey = $this->argument('newKey'); + if ($file !== "-json") { + $newKey = preg_replace('/(\w+)$/i', $newKey, $key); + } $files = $this->manager->files()[$file]; diff --git a/src/Commands/ShowCommand.php b/src/Commands/ShowCommand.php index d2b79bc..bdd52e7 100644 --- a/src/Commands/ShowCommand.php +++ b/src/Commands/ShowCommand.php @@ -15,7 +15,7 @@ class ShowCommand extends Command * * @var string */ - protected $signature = 'langman:show {key} {--c|close} {--lang=}'; + protected $signature = 'langman:show {key?} {--c|close} {--lang=} {--u|unused}'; /** * The name and signature of the console command. @@ -82,6 +82,15 @@ public function handle() $this->files = $this->filesFromKey(); + $excluded = null; + if ($this->option('unused')) { + $allKeysInFiles = $this->manager->collectFromFiles(); + $keyfile = $this->file ?: "-json"; + if (isset($allKeysInFiles[$keyfile])) { + $excluded = array_values($allKeysInFiles[$keyfile]); + } + } + try { $this->languages = $this->getLanguages(); } catch (InvalidArgumentException $e) { @@ -90,18 +99,25 @@ public function handle() return; } + $displayFile = $this->file ?: "JSON strings"; + $closematch = $this->key === null ? "" : ($this->option('close') ? 'using substring match' : 'using equality match'); + $unused = $this->option('unused') ? 'unused keys' : 'keys'; + $what = $this->key === null ? "all $unused" : "specific $unused matching '$this->key'"; + $this->info("Displaying $what from $displayFile $closematch"); + $this->table( array_merge(['key'], $this->languages), - $this->tableRows() + $this->tableRows($excluded) ); } /** * The output of the table rows. * + * @param $allKeysInFiles Array? if set, a list of all keys we should filter out * @return array */ - private function tableRows() + private function tableRows($excluded) { $output = []; @@ -109,11 +125,12 @@ private function tableRows() foreach ($this->files as $languageKey => $file) { foreach ($filesContent[$languageKey] = Arr::dot($this->manager->getFileContent($file)) as $key => $value) { - if (! $this->shouldShowKey($key)) { + if (! $this->shouldShowKey($key, $excluded)) { continue; } - $output[$key]['key'] = $key; + $okey = strlen($key) > 40 ? substr($key, 0, 36)." ..." : $key; + $output[$key]['key'] = $okey; $output[$key][$languageKey] = $value ?: ''; } } @@ -133,7 +150,8 @@ private function tableRows() // Sort the language values based on language name ksort($original); - $output[$key] = array_merge(['key' => "$key"], $original); + $okey = strlen($key) > 40 ? substr($key, 0, 36)." ..." : $key; + $output[$key] = array_merge(['key' => "$okey"], $original); } return array_values($output); @@ -147,9 +165,13 @@ private function tableRows() private function filesFromKey() { try { - return $this->manager->files()[$this->file]; + return $this->manager->files()[$this->file ?? "-json"]; } catch (\ErrorException $e) { - $this->error(sprintf('Language file %s.php not found!', $this->file)); + if ($this->file === null) { + $this->error(sprintf('JSON language strings not found!', $this->file)); + } else { + $this->error(sprintf('Language file %s.php not found!', $this->file)); + } return []; } @@ -162,22 +184,39 @@ private function filesFromKey() */ private function parseKey() { - $parts = explode('.', $this->argument('key'), 2); + if (strlen($this->argument('key'))) { + $parts = explode('.', $this->argument('key'), 2); - $this->file = $parts[0]; + $this->file = $parts[0]; - $this->key = isset($parts[1]) ? $parts[1] : null; + $this->key = isset($parts[1]) ? $parts[1] : null; - if (Str::contains($this->file, '::')) { - try { - $parts = explode('::', $this->file); + if (Str::contains($this->file, '::')) { + try { + $parts = explode('::', $this->file); + $this->manager->setPathToVendorPackage($parts[0]); + } catch (\ErrorException $e) { + $this->error('Could not recognize the package.'); - $this->manager->setPathToVendorPackage($parts[0]); - } catch (\ErrorException $e) { - $this->error('Could not recognize the package.'); + return; + } + } else { + if ($this->key === null && !isset($this->manager->files()[$this->file])) { + // fallback on a key search in the JSON strings + $this->key = $this->file; + $this->file = null; + } + } - return; + // if we want to use --close on the JSON strings, the key is _always_ a key, even + // if it is also a file + if ($this->key === null && $this->option('close')) { + $this->key = $this->file; + $this->file = null; } + } else { + $this->file = null; + $this->key = null; } } @@ -188,8 +227,12 @@ private function parseKey() * * @return bool */ - private function shouldShowKey($key) + private function shouldShowKey($key, $exclude) { + if ($exclude != null && in_array($key, $exclude)) { + return false; + } + if ($this->key) { if (Str::contains($key, '.') && Str::startsWith($key, $this->key)) { return true; diff --git a/src/Commands/SyncCommand.php b/src/Commands/SyncCommand.php index b39a15e..b85846f 100644 --- a/src/Commands/SyncCommand.php +++ b/src/Commands/SyncCommand.php @@ -53,6 +53,9 @@ public function handle() $this->syncKeysFromFiles($translationFiles); + // reread the files in case we created new ones while syncing + $translationFiles = $this->manager->files(); + $this->syncKeysBetweenLanguages($translationFiles); $this->info('Done!'); @@ -71,22 +74,41 @@ private function syncKeysFromFiles($translationFiles) // An array of all translation keys as found in project files. $allKeysInFiles = $this->manager->collectFromFiles(); + $languages = $this->manager->languages(); + + foreach ($allKeysInFiles as $file=>$labels) { + foreach ($languages as $lang) { + if (!isset($translationFiles[$file])) { + if ($file === "-json") { + $this->info('Found json translation keys'); + } else { + $this->info('Found translation keys for new file '.$file); + } + $translationFiles[$file]=[]; + } + if (!isset($translationFiles[$file][$lang])) { + if ($file === "-json") { + $this->info('Creating new JSON translation file for locale '.$lang); + } else { + $this->info('Creating new translation key file '.$file.' for locale '.$lang); + } + $this->manager->createFile($file, $lang); + $translationFiles[$file][$lang]=$this->manager->createFileName($file, $lang); + } - foreach ($translationFiles as $fileName => $languages) { - foreach ($languages as $languageKey => $path) { + $path = $translationFiles[$file][$lang]; $fileContent = $this->manager->getFileContent($path); - if (isset($allKeysInFiles[$fileName])) { - $missingKeys = array_diff($allKeysInFiles[$fileName], array_keys(array_dot($fileContent))); + $missingKeys = array_diff($labels, array_keys(Arr::dot($fileContent))); - foreach ($missingKeys as $i => $missingKey) { - if (Arr::has($fileContent, $missingKey)) { - unset($missingKeys[$i]); - } + // remove all keys that are in the translation, but not in any view + foreach ($missingKeys as $i => $missingKey) { + if (Arr::has($fileContent, $missingKey)) { + unset($missingKeys[$i]); } - - $this->fillMissingKeys($fileName, $missingKeys, $languageKey); } + + $this->fillMissingKeys($file, $missingKeys, $lang); } } } @@ -106,7 +128,11 @@ private function fillMissingKeys($fileName, array $foundMissingKeys, $languageKe foreach ($foundMissingKeys as $missingKey) { $missingKeys[$missingKey] = [$languageKey => '']; - $this->output->writeln("\"{$fileName}.{$missingKey}.{$languageKey}\" was added."); + if ($fileName == "-json") { + $this->output->writeln("\"JSON {$languageKey}:{$missingKey}\" was added."); + } else { + $this->output->writeln("\"{$languageKey}.{$fileName}.{$missingKey}\" was added."); + } } $this->manager->fillKeys( diff --git a/src/Commands/TransCommand.php b/src/Commands/TransCommand.php index 1368eb4..76c3dd6 100644 --- a/src/Commands/TransCommand.php +++ b/src/Commands/TransCommand.php @@ -110,11 +110,8 @@ private function parseKey() $this->fileName = $matches[1]; $this->key = $matches[2]; } catch (\ErrorException $e) { - if (! $this->key) { - $this->error('Could not recognize the key you want to translate.'); - - return false; - } + $this->fileName = "-json"; + $this->key = $this->argument('key'); } if (Str::contains($this->fileName, '::')) { @@ -144,8 +141,14 @@ private function filesFromKey() try { return $this->manager->files()[$this->fileName]; } catch (\ErrorException $e) { - if ($this->confirm(sprintf('Language file %s.php not found, would you like to create it?', $this->fileName))) { - $this->manager->createFile(str_replace($this->packageName.'::', '', $this->fileName)); + if ($this->fileName === "-json") { + if ($this->confirm(sprintf('JSON language file not found, would you like to create it?'))) { + $this->manager->createFile($this->fileName); + } + } else { + if ($this->confirm(sprintf('Language file %s.php not found, would you like to create it?', $this->fileName))) { + $this->manager->createFile(str_replace($this->packageName.'::', '', $this->fileName)); + } } return []; @@ -180,7 +183,11 @@ private function fillKey() ); foreach ($values as $languageKey => $value) { - $this->line("{$this->fileName}.{$this->key}:{$languageKey} was set to \"{$value}\" successfully."); + if ($this->fileName == "-json") { + $this->line("JSON {$this->key}:{$languageKey} was set to \"{$value}\" successfully."); + } else { + $this->line("{$this->fileName}.{$this->key}:{$languageKey} was set to \"{$value}\" successfully."); + } } } @@ -197,10 +204,14 @@ private function collectValues($languages) foreach ($languages as $languageKey) { $languageContent = $this->manager->getFileContent($this->files[$languageKey]); + $promptFile = $this->fileName."."; + if ($this->fileName == "-json") { + $promptFile = ""; + } $values[$languageKey] = $this->ask( sprintf( - '%s.%s:%s translation', - $this->fileName, + '%s%s:%s translation', + $promptFile, $this->key, $languageKey ), diff --git a/src/Manager.php b/src/Manager.php index 4ff5824..9dca11b 100644 --- a/src/Manager.php +++ b/src/Manager.php @@ -54,23 +54,28 @@ public function __construct(Filesystem $disk, $path, array $syncPaths) public function files() { $files = Collection::make($this->disk->allFiles($this->path))->filter(function ($file) { - return $this->disk->extension($file) == 'php'; + return $this->disk->extension($file) == 'php' || $this->disk->extension($file) == 'json'; }); $filesByFile = $files->groupBy(function ($file) { $fileName = $file->getBasename('.'.$file->getExtension()); if (Str::contains($file->getPath(), 'vendor')) { - $fileName = str_replace('.php', '', $file->getFileName()); - $packageName = basename(dirname($file->getPath())); - return "{$packageName}::{$fileName}"; } else { - return $fileName; + // for our locale-general JSON files, group them as '-json' + if ($file->getExtension() == "json") { + return "-json"; + } else { + return $fileName; + } } })->map(function ($files) { return $files->keyBy(function ($file) { + if ($file->getExtension() == "json") { + return $file->getBasename('.json'); + } return basename($file->getPath()); })->map(function ($file) { return $file->getRealPath(); @@ -132,18 +137,50 @@ public function languages() * Create a file for all languages if does not exist already. * * @param $fileName + * @param $lang string|Array locale or array of locales to create files for * @return void + * + * If $lang is not passed, create files in all locales + * @return file path of the created file */ - public function createFile($fileName) + public function createFile($fileName, $lang=null) { - foreach ($this->languages() as $languageKey) { - $file = $this->path."/{$languageKey}/{$fileName}.php"; + if ($lang === null) { + $lang=$this->languages(); + } elseif (!is_array($lang)) { + $lang=[$lang]; + } + + $file=null; + foreach ($lang as $languageKey) { + $file = $this->createFileName($fileName, $languageKey); if (! $this->disk->exists($file)) { - file_put_contents($file, "path . "/{$languageKey}.json"; + } else { + return $this->path."/{$languageKey}/{$file}.php"; + } + } + /** * Fills translation lines for given keys in different languages. * @@ -159,8 +196,7 @@ public function fillKeys($fileName, array $keys) foreach ($keys as $key => $values) { foreach ($values as $languageKey => $value) { - $filePath = $this->path."/{$languageKey}/{$fileName}.php"; - + $filePath = $this->createFileName($fileName, $languageKey); Arr::set($appends[$filePath], $key, $value); } } @@ -184,7 +220,7 @@ public function fillKeys($fileName, array $keys) public function removeKey($fileName, $key) { foreach ($this->languages() as $language) { - $filePath = $this->path."/{$language}/{$fileName}.php"; + $filePath = $this->createFileName($fileName, $language); $fileContent = $this->getFileContent($filePath); @@ -203,11 +239,16 @@ public function removeKey($fileName, $key) */ public function writeFile($filePath, array $translations) { - $content = "stringLineMaker($translations); - - $content .= "\n];"; + $ext = substr(strrchr($filePath, '.'), 1); + ksort($translations); + + if ($ext == "php") { + $content = "stringLineMaker($translations); + $content .= "\n];"; + } else { + $content = json_encode($translations, JSON_PRETTY_PRINT); + } file_put_contents($filePath, $content); } @@ -247,18 +288,32 @@ private function stringLineMaker($array, $prepend = '') */ public function getFileContent($filePath, $createIfNotExists = false) { + $info = pathinfo($filePath); if ($createIfNotExists && ! $this->disk->exists($filePath)) { if (! $this->disk->exists($directory = dirname($filePath))) { mkdir($directory, 0777, true); } - file_put_contents($filePath, " ['city', 'name', 'phone']] * + * @param Array $files ['user' => ['en' => 'user.php', 'nl' => 'user.php'], 'password' => ...] * @return array */ public function collectFromFiles() @@ -277,20 +333,30 @@ public function collectFromFiles() foreach ($this->getAllViewFilesWithTranslations() as $file => $matches) { foreach ($matches as $key) { - try { + $fileName = "-json"; + $keyName = $key; + + if (strpos($key, '.') !== false) { list($fileName, $keyName) = explode('.', $key, 2); - } catch (\ErrorException $e) { - continue; - } + if (!isset($translationKeys[$fileName])) { + // only create a new file if the fileName contains only alnum characters + if (preg_match("/^[a-zA-Z][a-zA-Z0-9]*\$/", $fileName)) { + $translationKeys[$fileName] = []; + } else { + $fileName = "-json"; + $keyName = $key; + } + } + } if (isset($translationKeys[$fileName]) && in_array($keyName, $translationKeys[$fileName])) { + // translation already found continue; } $translationKeys[$fileName][] = $keyName; } } - return $translationKeys; } @@ -312,17 +378,15 @@ public function getAllViewFilesWithTranslations() $pattern = // See https://regex101.com/r/jS5fX0/4 - '[^\w]'. // Must not start with any alphanum or _ - '(?)'. // Must not start with -> + // https://regex101.com/r/L9maxG/3 + '[^\w]'. // Must not start with any alphanum or _ + '(?)'. // Must not start with -> '('.implode('|', $functions).')'.// Must start with one of the functions - "\(".// Match opening parentheses - "[\'\"]".// Match " or ' - '('.// Start a new group to match: - '[a-zA-Z0-9_-]+'.// Must start with group - "([.][^\1)$]+)+".// Be followed by one or more items/keys - ')'.// Close group - "[\'\"]".// Closing quote - "[\),]" // Close parentheses or new parameter + '\s*\(\s*'. // match opening parentheses using any number of whitespace + "((?disk->allFiles($this->syncPaths) as $file) { if (preg_match_all("/$pattern/siU", $file->getContents(), $matches)) { - $allMatches[$file->getRelativePathname()] = $matches[2]; + $allMatches[$file->getRelativePathname()] = $matches[3]; } } @@ -371,15 +435,20 @@ public function getKeysExistingInALanguageButNotTheOther($values) // other languages. Those keys combined with ones with values = '' // will be sent to the console user to fill and save in disk. foreach ($values as $key => $value) { - list($fileName, $languageKey, $key) = explode('.', $key, 3); + $parts = explode('.', $key, 3); + $fileName = $parts[0]; + $languageKey = $parts[1]; + if (sizeof($parts) > 2) { + $key = $parts[2]; - if (in_array("{$fileName}.{$key}", $searched)) { - continue; - } + if (in_array("{$fileName}.{$key}", $searched)) { + continue; + } - foreach ($this->languages() as $languageName) { - if (! Arr::has($values, "{$fileName}.{$languageName}.{$key}") && ! array_key_exists("{$fileName}.{$languageName}.{$key}", $values)) { - $missing[] = "{$fileName}.{$key}:{$languageName}"; + foreach ($this->languages() as $languageName) { + if (! Arr::has($values, "{$fileName}.{$languageName}.{$key}") && ! array_key_exists("{$fileName}.{$languageName}.{$key}", $values)) { + $missing[] = "{$fileName}.{$key}:{$languageName}"; + } } } diff --git a/tests/FindCommandTest.php b/tests/FindCommandTest.php index a33d342..f20217a 100644 --- a/tests/FindCommandTest.php +++ b/tests/FindCommandTest.php @@ -10,8 +10,7 @@ public function testCommandErrorOnFilesNotFound() $this->createTempFiles(); $this->artisan('langman:find', ['keyword' => 'ragnar']); - - $this->assertContains('No language files were found!', $this->consoleOutput()); + $this->assertStringContainsString('No language files were found!', $this->consoleOutput()); } public function testCommandOutputForFile() @@ -26,8 +25,8 @@ public function testCommandOutputForFile() $this->assertRegExp('/key(?:.*)en(?:.*)nl/', $this->consoleOutput()); $this->assertRegExp('/user\.not_found(?:.*)User NoT fOunD(?:.*)something/', $this->consoleOutput()); - $this->assertNotContains('age', $this->consoleOutput()); - $this->assertNotContains('else', $this->consoleOutput()); + $this->assertStringNotContainsString('age', $this->consoleOutput()); + $this->assertStringNotContainsString('else', $this->consoleOutput()); } public function testCommandOutputForFileWithNestedKeys() @@ -41,7 +40,7 @@ public function testCommandOutputForFileWithNestedKeys() $this->assertRegExp('/key(?:.*)en(?:.*)sp/', $this->consoleOutput()); $this->assertRegExp('/user\.missing\.not_found(?:.*)user not found(?:.*)sp/', $this->consoleOutput()); - $this->assertNotContains('jarl_borg', $this->consoleOutput()); + $this->assertStringNotContainsString('jarl_borg', $this->consoleOutput()); } public function testCommandOutputForPackage() @@ -56,6 +55,21 @@ public function testCommandOutputForPackage() $this->assertRegExp('/key(?:.*)en(?:.*)sp/', $this->consoleOutput()); $this->assertRegExp('/package::file\.not_found(?:.*)file not found here(?:.*)something/', $this->consoleOutput()); - $this->assertNotContains('weight', $this->consoleOutput()); + $this->assertStringNotContainsString('weight', $this->consoleOutput()); + } + + public function testCommandOutputForJson() + { + $this->createTempFiles([ + 'en' => ['-json' => ['String1' => 'Usor','String2'=>'Admin'] ], + 'nl' => ['-json' => ['String1' => 'Giraffe','String2'=>'Tiger'] ], + ]); + + $this->artisan('langman:find', ['keyword' => 'admin']); + + $this->assertRegExp('/key(?:.*)en(?:.*)nl/', $this->consoleOutput()); + $this->assertRegExp('/^| String2 (?:.*)Admin(?:.*)Tiger/', $this->consoleOutput()); + $this->assertStringNotContainsString('User', $this->consoleOutput()); + $this->assertStringNotContainsString('Giraffe', $this->consoleOutput()); } } diff --git a/tests/Kernel.php b/tests/Kernel.php index 1c189e5..dd67a0e 100644 --- a/tests/Kernel.php +++ b/tests/Kernel.php @@ -25,7 +25,7 @@ class Kernel extends \Illuminate\Foundation\Console\Kernel * * @throws \Exception */ - protected function reportException(Exception $e) + protected function reportException(Throwable $e) { throw $e; } diff --git a/tests/ManagerTest.php b/tests/ManagerTest.php index deac505..28a90d3 100644 --- a/tests/ManagerTest.php +++ b/tests/ManagerTest.php @@ -10,8 +10,8 @@ public function testFilesMethod() $manager = $this->app[\Themsaid\Langman\Manager::class]; $this->createTempFiles([ - 'en' => ['user' => '', 'category' => ''], - 'nl' => ['user' => '', 'category' => ''], + 'en' => ['user' => '', 'category' => '', "-json"=>[]], + 'nl' => ['user' => '', 'category' => '', "-json"=>[]], 'vendor' => ['package' => ['en' => ['user' => '', 'product' => ''], 'sp' => ['user' => '', 'product' => '']]], ]); @@ -24,6 +24,10 @@ public function testFilesMethod() 'en' => __DIR__.DIRECTORY_SEPARATOR.'temp'.DIRECTORY_SEPARATOR.'en'.DIRECTORY_SEPARATOR.'category.php', 'nl' => __DIR__.DIRECTORY_SEPARATOR.'temp'.DIRECTORY_SEPARATOR.'nl'.DIRECTORY_SEPARATOR.'category.php', ], + "-json" => [ + 'en' => __DIR__.DIRECTORY_SEPARATOR.'temp'.DIRECTORY_SEPARATOR.'en.json', + 'nl' => __DIR__.DIRECTORY_SEPARATOR.'temp'.DIRECTORY_SEPARATOR.'nl.json', + ] // Uncomment when starting to support vendor language files // 'package::product' => [ // 'en' => __DIR__.'/temp/vendor/package/en/product.php', @@ -56,7 +60,7 @@ public function testCreateFileIfNotExisting() $manager = $this->app[\Themsaid\Langman\Manager::class]; $this->createTempFiles([ - 'en' => [], + 'en' => ["-json"=>[]], 'sp' => [], 'nl' => ['user' => '__UN_TOUCHED__'], ]); @@ -65,8 +69,19 @@ public function testCreateFileIfNotExisting() $this->assertFileExists($this->app['config']['langman.path'].'/en/user.php'); $this->assertFileExists($this->app['config']['langman.path'].'/sp/user.php'); + $this->assertFileNotExists($this->app['config']['langman.path'].'/sp.json'); + $this->assertFileNotExists($this->app['config']['langman.path'].'/nl.json'); $this->assertEquals('__UN_TOUCHED__', file_get_contents($this->app['config']['langman.path'].'/nl/user.php')); $this->assertEquals([], (array) include $this->app['config']['langman.path'].'/en/user.php'); + $this->assertEquals([], (array) include $this->app['config']['langman.path'].'/sp/user.php'); + + $manager->createFile('-json', "sp"); + + $this->assertFileExists($this->app['config']['langman.path'].'/en.json'); + $this->assertFileExists($this->app['config']['langman.path'].'/sp.json'); + $this->assertFileNotExists($this->app['config']['langman.path'].'/nl.json'); + $this->assertEquals([], (array) json_decode(file_get_contents($this->app['config']['langman.path'].'/en.json'), true)); + $this->assertEquals([], (array) json_decode(file_get_contents($this->app['config']['langman.path'].'/sp.json'), true)); } public function testWriteFile() @@ -92,22 +107,44 @@ public function testWriteFile() $this->assertEquals($values, (array) include $filePath); } + public function testWriteJSONFile() + { + $manager = $this->app[\Themsaid\Langman\Manager::class]; + + $this->createTempFiles([ + 'en' => ["-json" => []], + 'nl' => ["-json" => []], + ]); + + $filePath = $this->app['config']['langman.path'].'/en.json'; + + $values = [ + 'name' => ['first' => 'first', 'last' => ['last1' => '1', 'last2' => 2]], + 'age' => 'age', + 'double_quotes' => '"with quotes"', + 'quotes' => "With some ' quotes", + ]; + + $manager->writeFile($filePath, $values); + + $this->assertEquals($values, $manager->getFileContent($filePath)); + } + public function testGetFileContentReadsContent() { $manager = $this->app[\Themsaid\Langman\Manager::class]; + $values = ["Test1"=>"Value1", 'Test2'=>'Value2']; $this->createTempFiles([ - 'en' => ['users' => " ['users' => "$values], ]); $filePath = $this->app['config']['langman.path'].'/en/users.php'; $this->assertContains('_content_', $manager->getFileContent($filePath)); + $this->assertEquals($values, $manager->getFileContent($this->app['config']['langman.path'].'/en.json')); } - /** - * @expectedException Illuminate\Contracts\Filesystem\FileNotFoundException - */ public function testGetFileContentThrowsExceptionIfNotFound() { $manager = $this->app[\Themsaid\Langman\Manager::class]; @@ -116,6 +153,7 @@ public function testGetFileContentThrowsExceptionIfNotFound() $filePath = $this->app['config']['langman.path'].'/en/users.php'; + $this->expectException('\Illuminate\Contracts\Filesystem\FileNotFoundException'); $manager->getFileContent($filePath); } @@ -152,6 +190,26 @@ public function testRemoveTranslationLineFromAllFiles() $this->assertArrayHasKey('age', $nlFile); } + public function testRemoveTranslationLineFromJSONFiles() + { + $manager = $this->app[\Themsaid\Langman\Manager::class]; + + $this->createTempFiles([ + 'en' => ['-json' => ['String 1'=>'Trans 1', 'String 2'=>'Trans 2']], + 'nl' => ['-json' => ['String 1'=>'Hola 1', 'String 2'=>'Hola 2']], + ]); + + $manager->removeKey('-json', 'String 1'); + + $enFile = $manager->getFileContent($this->app['config']['langman.path'].'/en.json'); + $nlFile = $manager->getFileContent($this->app['config']['langman.path'].'/nl.json'); + + $this->assertArrayNotHasKey('String 1', $enFile); + $this->assertArrayHasKey('String 2', $enFile); + $this->assertArrayNotHasKey('String 1', $nlFile); + $this->assertArrayHasKey('String 2', $nlFile); + } + public function testRemoveNestedTranslationLineFromAllFiles() { $manager = $this->app[\Themsaid\Langman\Manager::class]; @@ -192,6 +250,24 @@ public function testFillTranslationLinesThatDoesNotExistYet() $this->assertEquals('naam', $nlFile['name']); } + public function testFillJSONTranslationLinesThatDoesNotExistYet() + { + $manager = $this->app[\Themsaid\Langman\Manager::class]; + + $this->createTempFiles([ + 'en' => ['-json' => []], + 'nl' => ['-json' => []], + ]); + + $manager->fillKeys('-json', ['name' => ['en' => 'name', 'nl' => 'naam']]); + + $enFile = $manager->getFileContent($this->app['config']['langman.path'].'/en.json'); + $nlFile = $manager->getFileContent($this->app['config']['langman.path'].'/nl.json'); + + $this->assertEquals('name', $enFile['name']); + $this->assertEquals('naam', $nlFile['name']); + } + public function testUpdatesTranslationLineThatExists() { $manager = $this->app[\Themsaid\Langman\Manager::class]; @@ -207,6 +283,27 @@ public function testUpdatesTranslationLineThatExists() $this->assertEquals('name', $enFile['name']); } + public function testUpdatesJSONTranslationLineThatExists() + { + $manager = $this->app[\Themsaid\Langman\Manager::class]; + + $enval = ['String 1'=>'Test 1', 'String 2'=>'Test 2']; + $nlval = ['String 1'=>'Hola 1', 'String 2'=>'Hola 2']; + $this->createTempFiles([ + 'en' => ['-json' => $enval], + 'nl' => ['-json' => $nlval], + ]); + + $manager->fillKeys('-json', ['String 1' => ['en' => 'name']]); + + $enFile = $manager->getFileContent($this->app['config']['langman.path'].'/en.json'); + $nlFile = $manager->getFileContent($this->app['config']['langman.path'].'/nl.json'); + + $enval['String 1']='name'; + $this->assertEquals($enval, $enFile); + $this->assertEquals($nlval, $nlFile); + } + public function testFillNestedTranslationLines() { $manager = $this->app[\Themsaid\Langman\Manager::class]; @@ -234,9 +331,37 @@ public function testFindTranslationsInProjectFiles() array_map('rmdir', glob(__DIR__.'/views_temp/users')); array_map('unlink', glob(__DIR__.'/views_temp/users.blade.php')); - file_put_contents(__DIR__.'/views_temp/users.blade.php', '{{ trans(\'users.name\') }} {{ trans(\'users.age\') }}'); mkdir(__DIR__.'/views_temp/users'); file_put_contents(__DIR__.'/views_temp/users/index.blade.php', "{{ trans('users.city') }}"); + file_put_contents(__DIR__.'/views_temp/user.blade.php', <<trans('not1') +___('not2') +@ lang('not3') +@ choice('not4') +trans(\$var) +trans() +anytrans('user.not5') +trans ( 'user.not6' . \$additional ) + +HEREDOC +); $results = $manager->collectFromFiles(); @@ -244,10 +369,28 @@ public function testFindTranslationsInProjectFiles() array_map('rmdir', glob(__DIR__.'/views_temp/users')); array_map('unlink', glob(__DIR__.'/views_temp/users.blade.php')); - $this->assertArrayHasKey('users', $results); - $this->assertContains('name', $results['users']); - $this->assertContains('age', $results['users']); - $this->assertContains('city', $results['users']); + $expected = [ + "-json" => [ + "JSON string check", + "random json string", + "whatever", + "do something", + ], + "user" => [ + "name", + "age", + "choice1", + "choice2", + "choice3", + "choice4", + "ws" + ], + "users" => [ + "city" + ] + ]; + + $this->assertEquals($expected, $results); } public function testGetKeysExistingInALanguageButNotTheOther() @@ -261,11 +404,17 @@ public function testGetKeysExistingInALanguageButNotTheOther() 'user.nl.phone' => 'a', 'user.en.address' => 'a', 'user.nl.address' => 'a', + "-json.en.city" => 'city', + "-json.nl.handy" => 'phone' ]); $this->assertContains('user.name:nl', $results); $this->assertContains('user.phone:en', $results); + $this->assertContains('-json.city:nl', $results); + $this->assertContains('-json.handy:en', $results); $this->assertNotContains('user.address:en', $results); $this->assertNotContains('user.address:nl', $results); + $this->assertNotContains('-json.city:en', $results); + $this->assertNotContains('-json.handy:nl', $results); } } diff --git a/tests/MissingCommandTest.php b/tests/MissingCommandTest.php index 389d62f..ca49afa 100644 --- a/tests/MissingCommandTest.php +++ b/tests/MissingCommandTest.php @@ -22,13 +22,13 @@ public function testCommandOutput() ]); $command = m::mock('\Themsaid\Langman\Commands\MissingCommand[ask]', [$manager]); - $command->shouldReceive('ask')->once()->with('/user\.age:nl/', null)->andReturn('fill_age'); - $command->shouldReceive('ask')->once()->with('/product\.name:en/', null)->andReturn('fill_name'); - $command->shouldReceive('ask')->once()->with('/product\.color:nl/', null)->andReturn('fill_color'); - $command->shouldReceive('ask')->once()->with('/product\.size:nl/', null)->andReturn('fill_size'); - $command->shouldReceive('ask')->once()->with('/missing\.missing\.id:nl/', null)->andReturn('fill_missing_id'); - $command->shouldReceive('ask')->once()->with('/missing\.missing\.price:en/', null)->andReturn('fill_missing_price'); - $command->shouldReceive('ask')->once()->with('/missing\.missing\.price:nl/', null)->andReturn('fill_missing_price'); + $command->shouldReceive('ask')->once()->with(m::pattern('/user\.age:nl/'), null)->andReturn('fill_age'); + $command->shouldReceive('ask')->once()->with(m::pattern('/product\.name:en/'), null)->andReturn('fill_name'); + $command->shouldReceive('ask')->once()->with(m::pattern('/product\.color:nl/'), null)->andReturn('fill_color'); + $command->shouldReceive('ask')->once()->with(m::pattern('/product\.size:nl/'), null)->andReturn('fill_size'); + $command->shouldReceive('ask')->once()->with(m::pattern('/missing\.missing\.id:nl/'), null)->andReturn('fill_missing_id'); + $command->shouldReceive('ask')->once()->with(m::pattern('/missing\.missing\.price:en/'), null)->andReturn('fill_missing_price'); + $command->shouldReceive('ask')->once()->with(m::pattern('/missing\.missing\.price:nl/'), null)->andReturn('fill_missing_price'); $this->app['artisan']->add($command); $this->artisan('langman:missing'); @@ -48,6 +48,31 @@ public function testCommandOutput() $this->assertEquals('fill_missing_price', $missingENFile['missing']['price']); } + public function testCommandOutputJSON() + { + $manager = $this->app[Manager::class]; + + $this->createTempFiles([ + 'en' => [ '-json' => ['String 1'=>'String 1', 'String 2'=>'String 2']], + 'nl' => [ '-json' => ['String 3'=>'Tiero']], + ]); + + $command = m::mock('\Themsaid\Langman\Commands\MissingCommand[ask]', [$manager]); + $command->shouldReceive('ask')->once()->with(m::pattern('/String 1:nl/'), null)->andReturn('fill_age'); + $command->shouldReceive('ask')->once()->with(m::pattern('/String 2:nl/'), null)->andReturn('fill_name'); + $command->shouldReceive('ask')->once()->with(m::pattern('/String 3:en/'), null)->andReturn('fill_color'); + + $this->app['artisan']->add($command); + $this->artisan('langman:missing'); + + $ENFile = (array) json_decode(file_get_contents($this->app['config']['langman.path'].'/en.json'), true); + $NLFile = (array) json_decode(file_get_contents($this->app['config']['langman.path'].'/nl.json'), true); + + $this->assertEquals('fill_age', $NLFile['String 1']); + $this->assertEquals('fill_name', $NLFile['String 2']); + $this->assertEquals('fill_color', $ENFile['String 3']); + } + public function testAllowSeeTranslationInDefaultLanguage() { $manager = $this->app[Manager::class]; @@ -64,7 +89,7 @@ public function testAllowSeeTranslationInDefaultLanguage() ]); $command = m::mock('\Themsaid\Langman\Commands\MissingCommand[ask]', [$manager]); - $command->shouldReceive('ask')->once()->with('/user\.age:nl<\/> translation/', '/en:Age/'); + $command->shouldReceive('ask')->once()->with(m::pattern('/user\.age:nl<\/> translation/'), 'en:Age'); $this->app['artisan']->add($command); @@ -87,7 +112,7 @@ public function testShowsNoDefaultWhenDefaultLanguageFileIsNotFound() ]); $command = m::mock('\Themsaid\Langman\Commands\MissingCommand[ask]', [$manager]); - $command->shouldReceive('ask')->once()->with('/user\.age:nl<\/> translation/', null); + $command->shouldReceive('ask')->once()->with(m::pattern('/user\.age:nl<\/> translation/'), null); $this->app['artisan']->add($command); diff --git a/tests/RemoveCommandTest.php b/tests/RemoveCommandTest.php index 1f845a5..14f5c27 100644 --- a/tests/RemoveCommandTest.php +++ b/tests/RemoveCommandTest.php @@ -31,6 +31,28 @@ public function testCommandOutput() $this->assertArrayNotHasKey('name', $userNLFile); } + public function testCommandOutputJSON() + { + $manager = $this->app[Manager::class]; + + $this->createTempFiles([ + 'en' => [ '-json' => ['String 1'=>'String 1', 'String 2'=>'String 2']], + 'nl' => [ '-json' => ['String 1'=>'Primo', 'String 2'=>'Secondo']], + ]); + + $command = m::mock('\Themsaid\Langman\Commands\RemoveCommand[confirm]', [$manager]); + $command->shouldReceive('confirm')->once()->with('Are you sure you want to remove "String 1"?')->andReturn(true); + + $this->app['artisan']->add($command); + $this->artisan('langman:remove', ['key' => 'String 1']); + + $ENFile = (array) json_decode(file_get_contents($this->app['config']['langman.path'].'/en.json'), true); + $NLFile = (array) json_decode(file_get_contents($this->app['config']['langman.path'].'/nl.json'), true); + + $this->assertArrayNotHasKey('String 1', $ENFile); + $this->assertArrayNotHasKey('String 1', $NLFile); + } + public function testRemovesNestedKeys() { $manager = $this->app[Manager::class]; diff --git a/tests/RenameCommandTest.php b/tests/RenameCommandTest.php index 2e2fd32..11d05d5 100644 --- a/tests/RenameCommandTest.php +++ b/tests/RenameCommandTest.php @@ -33,6 +33,24 @@ public function testRenameAKeyValueForAllLanguages() $this->assertEquals($expectedValueES, $newValueES); } + public function testRenameAKeyValueForAllLanguagesJSON() + { + $this->createTempFiles([ + 'en' => [ '-json' => ['String 1'=>'String 1', 'String 2'=>'String 2']], + 'nl' => [ '-json' => ['String 1'=>'Primo', 'String 2'=>'Secondo']], + ]); + + $this->artisan('langman:rename', ['oldKey' => 'String 1', 'newKey' => 'contact']); + + $ENFile = (array) json_decode(file_get_contents($this->app['config']['langman.path'].'/en.json'), true); + $NLFile = (array) json_decode(file_get_contents($this->app['config']['langman.path'].'/nl.json'), true); + + $this->assertArrayNotHasKey('String 1', $ENFile); + $this->assertArrayNotHasKey('String 1', $NLFile); + $this->assertArrayHasKey('contact', $ENFile); + $this->assertArrayHasKey('contact', $NLFile); + } + public function testRenameANestedKeyValueForAllLanguages() { $this->createTempFiles([ @@ -91,7 +109,7 @@ public function testRenameCommandShowViewFilesAffectedForTheChange() array_map('rmdir', glob(__DIR__.'/views_temp/users')); array_map('unlink', glob(__DIR__.'/views_temp/users.blade.php')); - $this->assertContains("Renamed key was found in 2 file(s).", $this->consoleOutput()); + $this->assertStringContainsString("Renamed key was found in 2 file(s).", $this->consoleOutput()); $this->assertRegExp('/Encounters(?:.*)File/', $this->consoleOutput()); $this->assertRegExp('/1(?:.*)users\.blade\.php/', $this->consoleOutput()); $this->assertRegExp('/2(?:.*)users(\\\|\/)index\.blade\.php/', $this->consoleOutput()); diff --git a/tests/ShowCommandTest.php b/tests/ShowCommandTest.php index 904c7c7..8314677 100644 --- a/tests/ShowCommandTest.php +++ b/tests/ShowCommandTest.php @@ -8,9 +8,142 @@ public function testCommandErrorOnFileNotFound() $this->artisan('langman:show', ['key' => 'user']); - $this->assertContains('Language file user.php not found!', $this->consoleOutput()); + $this->assertStringContainsString('JSON language strings not found!', $this->consoleOutput()); } + public function testOptionCombination1() + { + $this->createTempFiles([ + "en" => ["-json" => []] + ]); + + $this->artisan('langman:show', ['key' => 'user']); + + $this->assertStringContainsString("Displaying specific keys matching 'user' from JSON strings using equality match", $this->consoleOutput()); + } + + public function testOptionCombination2() + { + $this->createTempFiles([ + "en" => ["-json" => []] + ]); + + $this->artisan('langman:show', ['key' => 'user', "--close"=>true]); + + $this->assertStringContainsString("Displaying specific keys matching 'user' from JSON strings using substring match", $this->consoleOutput()); + } + + public function testOptionCombination3() + { + $this->createTempFiles([ + "en" => ["-json" => []] + ]); + + $this->artisan('langman:show', ['key' => 'user', "--close"=>true, "--unused"=>true]); + + $this->assertStringContainsString("Displaying specific unused keys matching 'user' from JSON strings using substring match", $this->consoleOutput()); + } + + public function testOptionCombination3b() + { + $this->createTempFiles([ + "en" => ["-json" => []] + ]); + + $this->artisan('langman:show', ['key' => 'user', "--unused"=>true]); + + $this->assertStringContainsString("Displaying specific unused keys matching 'user' from JSON strings using equality match", $this->consoleOutput()); + } + + public function testOptionCombination4() + { + $this->createTempFiles([ + "en" => ["-json" => []] + ]); + + $this->artisan('langman:show', ['key' => 'user.name']); + + $this->assertStringContainsString("Displaying specific keys matching 'name' from user using equality match", $this->consoleOutput()); + } + + public function testOptionCombination5() + { + $this->createTempFiles([ + "en" => ["-json" => []] + ]); + + $this->artisan('langman:show', ['key' => 'user.name', "--close"=>true]); + + $this->assertStringContainsString("Displaying specific keys matching 'name' from user using substring match", $this->consoleOutput()); + } + + public function testOptionCombination6() + { + $this->createTempFiles([ + "en" => ["-json" => []] + ]); + + $this->artisan('langman:show', ['key' => 'user.name', "--close"=>true, "--unused"=>true]); + + $this->assertStringContainsString("Displaying specific unused keys matching 'name' from user using substring match", $this->consoleOutput()); + } + + public function testOptionCombination6b() + { + $this->createTempFiles([ + "en" => ["-json" => []] + ]); + + $this->artisan('langman:show', ['key' => 'user.name', "--unused"=>true]); + + $this->assertStringContainsString("Displaying specific unused keys matching 'name' from user using equality match", $this->consoleOutput()); + } + + public function testOptionCombination7() + { + $this->createTempFiles([ + "en" => ["-json" => [], 'user' => 'artisan('langman:show', ['key' => 'user']); + + $this->assertStringContainsString("Displaying all keys from user", $this->consoleOutput()); + } + + public function testOptionCombination8() + { + $this->createTempFiles([ + "en" => ["-json" => [], 'user' => 'artisan('langman:show', ['key' => 'user', '--close' => true]); + + $this->assertStringContainsString("Displaying specific keys matching 'user' from JSON strings using substring match", $this->consoleOutput()); + } + + public function testOptionCombination9() + { + $this->createTempFiles([ + "en" => ["-json" => [], 'user' => 'artisan('langman:show', ['key' => 'user', '--close' => true, '--unused' => true]); + + $this->assertStringContainsString("Displaying specific unused keys matching 'user' from JSON strings using substring match", $this->consoleOutput()); + } + + public function testOptionCombination9b() + { + $this->createTempFiles([ + "en" => ["-json" => [], 'user' => 'artisan('langman:show', ['key' => 'user', '--unused' => true]); + + $this->assertStringContainsString("Displaying all unused keys from user", $this->consoleOutput()); + } + + public function testCommandOutputForFile() { $this->createTempFiles([ @@ -25,6 +158,20 @@ public function testCommandOutputForFile() $this->assertRegExp('/age(?:.*)Age(?:.*)|(?: *)|/', $this->consoleOutput()); } + public function testCommandOutputForJSON() + { + $this->createTempFiles([ + 'en' => [ '-json' => ['String 1'=>'String 1', 'String 2'=>'String 2']], + 'nl' => [ '-json' => ['String 1'=>'Primo', 'String 3 with a very long text that is cut off at some point' => 'Tiero']], + ]); + + $this->artisan('langman:show'); + + $this->assertRegExp('/key(?:.*)en(?:.*)nl/', $this->consoleOutput()); + $this->assertRegExp('/String 1(?:.*)String 1(?:.*)Primo/', $this->consoleOutput()); + $this->assertRegExp('/String 2(?:.*)String 2(?:.*)|(?: *)|/', $this->consoleOutput()); + $this->assertRegExp('/String 3 with a very long text that is cut off at some point(?:.*)|(?: *)|(?:.*)Tiero/', $this->consoleOutput()); + } public function testCommandOutputForFileAndSpecificLanguages() { $this->createTempFiles([ @@ -37,8 +184,8 @@ public function testCommandOutputForFileAndSpecificLanguages() $this->assertRegExp('/key(?:.*)en(?:.*)nl/', $this->consoleOutput()); $this->assertRegExp('/name(?:.*)Name(?:.*)Naam/', $this->consoleOutput()); - $this->assertNotContains('Nome', $this->consoleOutput()); - $this->assertNotContains('it_lang', $this->consoleOutput()); + $this->assertStringNotContainsString('Nome', $this->consoleOutput()); + $this->assertStringNotContainsString('it_lang', $this->consoleOutput()); } public function testCommandOutputForPackageFile() @@ -80,8 +227,8 @@ public function testCommandOutputForKey() $this->assertRegExp('/key(?:.*)en(?:.*)nl/', $this->consoleOutput()); $this->assertRegExp('/name(?:.*)Name(?:.*)Naam/', $this->consoleOutput()); - $this->assertNotContains('age', $this->consoleOutput()); - $this->assertNotContains('uname', $this->consoleOutput()); + $this->assertStringNotContainsString('age', $this->consoleOutput()); + $this->assertStringNotContainsString('uname', $this->consoleOutput()); } public function testCommandOutputForNestedKey() @@ -95,8 +242,8 @@ public function testCommandOutputForNestedKey() $this->assertRegExp('/key(?:.*)en(?:.*)nl/', $this->consoleOutput()); $this->assertRegExp('/name.first(?:.*)first(?:.*)firstnl/', $this->consoleOutput()); - $this->assertNotContains('name.last', $this->consoleOutput()); - $this->assertNotContains('age', $this->consoleOutput()); + $this->assertStringNotContainsString('name.last', $this->consoleOutput()); + $this->assertStringNotContainsString('age', $this->consoleOutput()); } public function testCommandOutputForSearchingParentKey() @@ -111,7 +258,7 @@ public function testCommandOutputForSearchingParentKey() $this->assertRegExp('/key(?:.*)en(?:.*)nl/', $this->consoleOutput()); $this->assertRegExp('/name.first(?:.*)first(?:.*)firstnl/', $this->consoleOutput()); $this->assertRegExp('/name.last(?:.*)last(?:.*)lastnl/', $this->consoleOutput()); - $this->assertNotContains('age', $this->consoleOutput()); + $this->assertStringNotContainsString('age', $this->consoleOutput()); } public function testCommandOutputForKeyOnCloseMatch() @@ -126,7 +273,7 @@ public function testCommandOutputForKeyOnCloseMatch() $this->assertRegExp('/key(?:.*)en(?:.*)nl/', $this->consoleOutput()); $this->assertRegExp('/name(?:.*)Name(?:.*)Naam/', $this->consoleOutput()); $this->assertRegExp('/username(?:.*)uname(?:.*)|(?: *)|/', $this->consoleOutput()); - $this->assertNotContains('age', $this->consoleOutput()); + $this->assertStringNotContainsString('age', $this->consoleOutput()); } public function test_ignore_attributes_and_keys_with_empty_arrays() diff --git a/tests/SyncCommandTest.php b/tests/SyncCommandTest.php index 1a23e4d..6378bbb 100644 --- a/tests/SyncCommandTest.php +++ b/tests/SyncCommandTest.php @@ -8,30 +8,104 @@ public function testCommandOutputForFile() array_map('rmdir', glob(__DIR__.'/views_temp/user')); array_map('unlink', glob(__DIR__.'/views_temp/user.blade.php')); - file_put_contents(__DIR__.'/views_temp/user.blade.php', '{{ trans(\'user.name\') }} {{ trans(\'user.age\') }}'); + file_put_contents(__DIR__.'/views_temp/user.blade.php', <<trans('not1') +___('not2') +@ lang('not3') +@ choice('not4') +trans(\$var) +trans() +anytrans('user.not5') +trans ( 'user.not6' . \$additional ) + +HEREDOC +); mkdir(__DIR__.'/views_temp/user'); file_put_contents(__DIR__.'/views_temp/user/index.blade.php', "{{ trans('user.city') }} {{ trans('user.code.initial') }}"); $this->createTempFiles([ - 'en' => ['user' => " 'Name', 'not_in_files' => 'a'];"], - 'nl' => ['user' => " [ + 'user' => " 'Name', 'not_in_files' => 'a'];", + '-json' => ['whatever'=>'Sailor', 'json_en'=>'JSON' ] + ], + 'nl' => [ + 'user' => "'ja'];", + '-json' => ['whatever'=>'Matroos', 'json_nl'=>'my_json' ] + ], ]); $this->artisan('langman:sync'); $userENFile = (array) include $this->app['config']['langman.path'].'/en/user.php'; $userNlFile = (array) include $this->app['config']['langman.path'].'/nl/user.php'; - - $this->assertArrayHasKey('name', $userENFile); - $this->assertArrayHasKey('not_in_files', $userENFile); - $this->assertArrayHasKey('initial', $userENFile['code']); - $this->assertArrayHasKey('age', $userENFile); - $this->assertArrayHasKey('city', $userENFile); - $this->assertArrayHasKey('name', $userNlFile); - $this->assertArrayHasKey('not_in_files', $userNlFile); - $this->assertArrayHasKey('initial', $userNlFile['code']); - $this->assertArrayHasKey('age', $userNlFile); - $this->assertArrayHasKey('city', $userNlFile); + $userJSONFileEN = (array) json_decode(file_get_contents($this->app['config']['langman.path'].'/en.json'), true); + $userJSONFileNL = (array) json_decode(file_get_contents($this->app['config']['langman.path'].'/nl.json'), true); + + $expectedEN=[ + 'name' => 'Name', + 'not_in_files' => 'a', + 'code' => ['initial' => ''], + 'age' => '', + 'city' => '', + 'choice1' => '', + 'choice2' => '', + 'choice3' => '', + 'choice4' => '', + 'ws' => '', + 'only_in_nl' => '' + ]; + $expectedENJson = [ + 'JSON string check' => '', + 'random json string' => '', + 'whatever' => 'Sailor', + 'do something' => '', + 'json_nl' => '', + 'json_en' => 'JSON' + ]; + + $expectedNL=[ + 'name' => '', + 'not_in_files' => '', + 'code' => ['initial' => ''], + 'age' => '', + 'city' => '', + 'choice1' => '', + 'choice2' => '', + 'choice3' => '', + 'choice4' => '', + 'ws' => '', + 'only_in_nl'=> 'ja' + ]; + $expectedNLJson = [ + 'JSON string check' => '', + 'random json string' => '', + 'whatever' => 'Matroos', + 'do something' => '', + 'json_nl' => 'my_json', + 'json_en' => '' + ]; + + $this->assertEquals($expectedEN, $userENFile); + $this->assertEquals($expectedENJson, $userJSONFileEN); + $this->assertEquals($expectedNL, $userNlFile); + $this->assertEquals($expectedNLJson, $userJSONFileNL); array_map('unlink', glob(__DIR__.'/views_temp/user/index.blade.php')); array_map('rmdir', glob(__DIR__.'/views_temp/user')); @@ -50,7 +124,7 @@ public function testCommandOutputForMissingSubKey() $this->createTempFiles([ 'en' => ['user' => " ['middle' => 'middle', 'first' => 'old_value','not_in_files' => 'a']];"], - 'nl' => ['user' => " ['middle' => 'middle', 'first' => 'old_value']];"], + 'nl' => ['user' => " ['middle' => 'middle2', 'first' => 'old_value2']];"], ]); $this->artisan('langman:sync'); @@ -58,12 +132,14 @@ public function testCommandOutputForMissingSubKey() $userENFile = (array) include $this->app['config']['langman.path'].'/en/user.php'; $userNLFile = (array) include $this->app['config']['langman.path'].'/nl/user.php'; - $this->assertArrayHasKey('not_in_files', $userNLFile['name']); - $this->assertArrayHasKey('name', $userENFile); - $this->assertArrayHasKey('first', $userENFile['name']); - $this->assertEquals('old_value', $userENFile['name']['first']); - $this->assertArrayHasKey('last', $userENFile['name']); - $this->assertArrayHasKey('middle', $userENFile['name']); + $expectEN = [ + 'name' => ['middle' => 'middle', 'first' => 'old_value', 'last' => '', 'not_in_files' => 'a' ] + ]; + $expectNL = [ + 'name' => ['middle' => 'middle2', 'first' => 'old_value2', 'last'=> '', 'not_in_files' => '' ] + ]; + $this->assertEquals($expectEN, $userENFile); + $this->assertEquals($expectNL, $userNLFile); array_map('unlink', glob(__DIR__.'/views_temp/user/index.blade.php')); array_map('rmdir', glob(__DIR__.'/views_temp/user')); diff --git a/tests/TestCase.php b/tests/TestCase.php index 9e94e0b..14562d4 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -15,18 +15,20 @@ protected function getEnvironmentSetUp($app) $app['config']->set('view.paths', [__DIR__.'/views_temp']); } - public function setUp() + public function setUp() : void { parent::setUp(); + $this->withoutMockingConsoleOutput(); exec('rm -rf '.__DIR__.'/temp/*'); + exec('rm -rf '.__DIR__.'/views_temp/*'); } - public function tearDown() + public function tearDown() : void { parent::tearDown(); - exec('rm -rf '.__DIR__.'/temp/*'); + exec('rm -rf '.__DIR__.'/views_temp/*'); $this->consoleOutput = ''; } @@ -37,7 +39,7 @@ public function createTempFiles($files = []) mkdir(__DIR__.'/temp/'.$dir); foreach ($dirFiles as $file => $content) { - if (is_array($content)) { + if (is_array($content) && $file!== "-json") { mkdir(__DIR__.'/temp/'.$dir.'/'.$file); foreach ($content as $subDir => $subContent) { @@ -47,7 +49,11 @@ public function createTempFiles($files = []) } } } else { - file_put_contents(__DIR__.'/temp/'.$dir.'/'.$file.'.php', $content); + if ($file == "-json") { + file_put_contents(__DIR__.'/temp/'.$dir.'.json', json_encode($content, JSON_PRETTY_PRINT)); + } else { + file_put_contents(__DIR__.'/temp/'.$dir.'/'.$file.'.php', $content); + } } } } diff --git a/tests/TransCommandTest.php b/tests/TransCommandTest.php index 0704dfb..cef574b 100644 --- a/tests/TransCommandTest.php +++ b/tests/TransCommandTest.php @@ -5,13 +5,25 @@ class TransCommandTest extends TestCase { - public function testCommandErrorOutputOnMissingKey() + public function testCommandOutputOnJSONKey() { - $this->createTempFiles(); + $this->createTempFiles([ + 'en' => [ "-json" => ["admin" => 'Admin'] ], + 'nl' => [ "-json" => ["admin" => 'Admini'] ], + ]); + $manager = $this->app[Manager::class]; + $command = m::mock('\Themsaid\Langman\Commands\TransCommand[ask]', [$manager]); + $command->shouldReceive('ask')->once()->with(m::pattern('/users:en/'), null)->andReturn('test'); + $command->shouldReceive('ask')->once()->with(m::pattern('/users:nl/'), null)->andReturn('otherwise'); + + $this->app['artisan']->add($command); $this->artisan('langman:trans', ['key' => 'users']); - $this->assertContains('Could not recognize the key you want to translate.', $this->consoleOutput()); + $ENFile = json_decode(file_get_contents($this->app['config']['langman.path'].'/en.json'), true); + $NLFile = json_decode(file_get_contents($this->app['config']['langman.path'].'/nl.json'), true); + $this->assertEquals(["admin" => "Admini","users" => "otherwise"], $NLFile); + $this->assertEquals(["admin" => "Admin","users" => "test"], $ENFile); } public function testCommandErrorOutputOnLanguageNotFound() @@ -20,7 +32,7 @@ public function testCommandErrorOutputOnLanguageNotFound() $this->artisan('langman:trans', ['key' => 'users.name', '--lang' => 'sd']); - $this->assertContains('Language (sd) could not be found!', $this->consoleOutput()); + $this->assertStringContainsString('Language (sd) could not be found!', $this->consoleOutput()); } public function testCommandAsksForConfirmationToCreateFileIfNotFound() @@ -88,8 +100,8 @@ public function testCommandAsksForValuePerLanguageAndWriteToFile() $manager = $this->app[Manager::class]; $command = m::mock('\Themsaid\Langman\Commands\TransCommand[ask]', [$manager]); $command->shouldReceive('confirm')->never(); - $command->shouldReceive('ask')->once()->with('/users\.name:en/', null)->andReturn('name'); - $command->shouldReceive('ask')->once()->with('/users\.name:nl/', null)->andReturn('naam'); + $command->shouldReceive('ask')->once()->with(m::pattern('/users\.name:en/'), null)->andReturn('name'); + $command->shouldReceive('ask')->once()->with(m::pattern('/users\.name:nl/'), null)->andReturn('naam'); $this->app['artisan']->add($command); $this->artisan('langman:trans', ['key' => 'users.name']); @@ -108,8 +120,8 @@ public function testCommandAsksForValuePerLanguageForPackageAndWriteToFile() $manager = $this->app[Manager::class]; $command = m::mock('\Themsaid\Langman\Commands\TransCommand[ask]', [$manager]); - $command->shouldReceive('ask')->once()->with('/users\.name:en/', null)->andReturn('name'); - $command->shouldReceive('ask')->once()->with('/users\.name:sp/', null)->andReturn('naam'); + $command->shouldReceive('ask')->once()->with(m::pattern('/users\.name:en/'), null)->andReturn('name'); + $command->shouldReceive('ask')->once()->with(m::pattern('/users\.name:sp/'), null)->andReturn('naam'); $this->app['artisan']->add($command); $this->artisan('langman:trans', ['key' => 'package::users.name']); @@ -130,8 +142,8 @@ public function testCommandAsksForValuePerLanguageAndUpdatingExistingInFile() $manager = $this->app[Manager::class]; $command = m::mock('\Themsaid\Langman\Commands\TransCommand[ask]', [$manager]); $command->shouldReceive('confirm')->never(); - $command->shouldReceive('ask')->once()->with('/users\.name:en/', 'nil')->andReturn('name'); - $command->shouldReceive('ask')->once()->with('/users\.name:nl/', '')->andReturn('naam'); + $command->shouldReceive('ask')->once()->with(m::pattern('/users\.name:en/'), 'nil')->andReturn('name'); + $command->shouldReceive('ask')->once()->with(m::pattern('/users\.name:nl/'), '')->andReturn('naam'); $this->app['artisan']->add($command); $this->artisan('langman:trans', ['key' => 'users.name']); @@ -152,7 +164,7 @@ public function testCommandAsksForValueForOnlyProvidedLanguage() $manager = $this->app[Manager::class]; $command = m::mock('\Themsaid\Langman\Commands\TransCommand[ask]', [$manager]); $command->shouldReceive('confirm')->never(); - $command->shouldReceive('ask')->once()->with('/users\.name:en/', null)->andReturn('name'); + $command->shouldReceive('ask')->once()->with(m::pattern('/users\.name:en/'), null)->andReturn('name'); $this->app['artisan']->add($command); $this->artisan('langman:trans', ['key' => 'users.name', '--lang' => 'en']); @@ -171,8 +183,8 @@ public function testCommandAsksForValuePerLanguageForNestedKeysAndWriteFile() $manager = $this->app[Manager::class]; $command = m::mock('\Themsaid\Langman\Commands\TransCommand[ask]', [$manager]); $command->shouldReceive('confirm')->never(); - $command->shouldReceive('ask')->once()->with('/users\.name\.first:en/', null)->andReturn('name'); - $command->shouldReceive('ask')->once()->with('/users\.name\.first:nl/', null)->andReturn('naam'); + $command->shouldReceive('ask')->once()->with(m::pattern('/users\.name\.first:en/'), null)->andReturn('name'); + $command->shouldReceive('ask')->once()->with(m::pattern('/users\.name\.first:nl/'), null)->andReturn('naam'); $this->app['artisan']->add($command); $this->artisan('langman:trans', ['key' => 'users.name.first']); @@ -193,7 +205,7 @@ public function testCommandAsksForLanguageForNestedKeysAndWriteFile() $manager = $this->app[Manager::class]; $command = m::mock('\Themsaid\Langman\Commands\TransCommand[ask]', [$manager]); $command->shouldReceive('confirm')->never(); - $command->shouldReceive('ask')->once()->with('/users\.name\.first:en/', null)->andReturn('name'); + $command->shouldReceive('ask')->once()->with(m::pattern('/users\.name\.first:en/'), null)->andReturn('name'); $this->app['artisan']->add($command); $this->artisan('langman:trans', ['key' => 'users.name.first', '--lang' => 'en']);