Skip to content

Commit 54be38f

Browse files
committed
feature: validate files on import
1 parent a16e4be commit 54be38f

File tree

6 files changed

+83
-177
lines changed

6 files changed

+83
-177
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ php artisan lang:export --locale en -g paggination,validation # export only cre
7676
php artisan lang:import es.csv # localed autodetected from file name
7777
php artisan lang:import espaniol.csv -l es
7878
php artisan lang:import espaniol.csv -l es -g paggination,validation # import only cretain groups
79+
php artisan lang:import es.csv -p # validate imported translations for missing placeholders (see below)
7980
```
8081

8182
### Validate

src/Console/ExportToCsvCommand.php

Lines changed: 15 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ class ExportToCsvCommand extends Command
1616
* @var string
1717
*/
1818
protected $signature = 'lang:export
19-
{--l|locale= : The locales to be exported. Separated by comma (default - default lang of application).}
19+
{--l|locale= : The locales to be exported. Separated by comma (default - base locale from config).}
2020
{--t|target= : Target languages, only missing keys are exported. Separated by comma.}
21-
{--g|group= : The name of translation file to export (default all groups).}
22-
{--o|output= : Filename of exported translation, :locale, :target is replaced (optional, default - storage/:locale:target.csv).}
21+
{--g|group= : The name of translation file to export (default - base group from config).}
22+
{--o|output= : Filename of exported translation, :locale, :target is replaced (default - export_path from config).}
2323
{--z|zip= : Zip all files.}
2424
{--X|excel : Set file encoding for Excel (optional, default - UTF-8).}
2525
{--D|delimiter=, : Field delimiter (optional, default - ",").}
@@ -30,14 +30,7 @@ class ExportToCsvCommand extends Command
3030
*
3131
* @var string
3232
*/
33-
protected $description = "Exports the language files to CSV file";
34-
35-
/**
36-
* Parameters provided to command.
37-
*
38-
* @var array
39-
*/
40-
protected $parameters = [];
33+
protected $description = "Exports language files to CSV file";
4134

4235
/**
4336
* List of files created by the export
@@ -53,16 +46,14 @@ class ExportToCsvCommand extends Command
5346
*/
5447
public function handle()
5548
{
56-
$this->getParameters();
57-
58-
foreach ($this->strToArray($this->parameters['locale']) as $locale) {
59-
foreach ($this->strToArray($this->parameters['target'], [null]) as $target) {
49+
foreach ($this->strToArray($this->option('locale')) as $locale) {
50+
foreach ($this->strToArray($this->option('target'), [null]) as $target) {
6051
$translations = $this->getTranslations($locale, $target);
6152
$this->saveTranslations($locale, $target, $translations);
6253
$this->info(strtoupper($locale) . strtoupper($target ?: '') . ' Translations saved to: ' . $this->getOutputFileName($locale, $target));
6354
}
6455
}
65-
if ($zipName = $this->parameters['zip']) {
56+
if ($zipName = $this->option('zip')) {
6657
$this->info('Creating archive...');
6758
$zip = new \ZipArchive;
6859
if (!$zip->open($zipName, \ZipArchive::CREATE)) {
@@ -87,29 +78,6 @@ private function strToArray($string, $fallback = [])
8778
return array_filter(array_map('trim', explode(',', $string)));
8879
}
8980

90-
/**
91-
* Fetch command parameters (arguments and options) and analyze them.
92-
*
93-
* @return void
94-
*/
95-
private function getParameters()
96-
{
97-
$parameters = [
98-
'locale' => $this->option('locale'),
99-
'group' => $this->option('group'),
100-
'output' => $this->option('output'),
101-
'excel' => $this->option('excel'),
102-
'delimiter' => $this->option('delimiter'),
103-
'enclosure' => $this->option('enclosure'),
104-
'target' => $this->option('target'),
105-
'zip' => $this->option('zip'),
106-
];
107-
$parameters = array_filter($parameters, function ($var) {
108-
return !is_null($var);
109-
});
110-
$this->parameters = array_merge(config('lang_import_export.export', []), $parameters);
111-
}
112-
11381
/**
11482
* Get translations from localization files.
11583
*
@@ -119,9 +87,10 @@ private function getParameters()
11987
*/
12088
private function getTranslations($locale, $target = null)
12189
{
122-
$from = LangListService::loadLangList($locale, $this->parameters['group']);
90+
$group = $this->option('group') ?: config('lang_import_export.base_group');
91+
$from = LangListService::loadLangList($locale, $group);
12392
if ($target) {
124-
$targetList = LangListService::loadLangList($target, $this->parameters['group']);
93+
$targetList = LangListService::loadLangList($target, $group);
12594
foreach ($targetList as $group => $translations) {
12695
foreach ($translations as $key => $v) {
12796
unset($from[$group][$key]);
@@ -203,7 +172,7 @@ private function writeFile()
203172
{
204173
$data = func_get_args();
205174
$output = array_shift($data);
206-
fputcsv($output, $data, $this->parameters['delimiter'], $this->parameters['enclosure']);
175+
fputcsv($output, $data, $this->option('delimiter'), $this->option('enclosure'));
207176
}
208177

209178
/**
@@ -216,7 +185,7 @@ private function closeFile($output)
216185
{
217186
fclose($output);
218187

219-
if ($this->parameters['excel']) {
188+
if ($this->option('excel')) {
220189
$this->adjustToExcel();
221190
}
222191
}
@@ -229,8 +198,8 @@ private function closeFile($output)
229198
*/
230199
private function adjustToExcel()
231200
{
232-
$data = file_get_contents($this->parameters['output']);
233-
file_put_contents($this->parameters['output'],
201+
$data = file_get_contents($this->option('output'));
202+
file_put_contents($this->option('output'),
234203
chr(255) . chr(254) . mb_convert_encoding($data, 'UTF-16LE', 'UTF-8'));
235204
}
236205

@@ -241,7 +210,7 @@ private function adjustToExcel()
241210
*/
242211
private function getOutputFileName($locale, $target = null)
243212
{
244-
$fileName = $this->parameters['output'];
213+
$fileName = $this->option('output');
245214
$fileName = str_replace(':locale', $locale, $fileName);
246215
$fileName = str_replace(':target', $target, $fileName);
247216
return $fileName;

src/Console/ImportFromCsvCommand.php

Lines changed: 26 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ class ImportFromCsvCommand extends Command
1818
protected $signature = 'lang:import
1919
{input : Filename of file to be imported with translation files.}
2020
{--l|locale= : The locale to be imported (default - parsed from file name).}
21-
{--g|group= : The name of translation file to imported (default - all files).}
21+
{--g|group= : The name of translation file to imported (default - base group from config).}
22+
{--p|placeholders : Search for missing placeholders in imported keys.}
2223
{--D|delimiter=, : Field delimiter (optional, default - ",").}
2324
{--E|enclosure=" : Field enclosure (optional, default - \'"\').}
2425
{--escape=" : Field escape (optional, default - \'"\').}
@@ -31,64 +32,38 @@ class ImportFromCsvCommand extends Command
3132
*/
3233
protected $description = "Imports the CSV file and write content into language files.";
3334

34-
/**
35-
* Parameters provided to command.
36-
*
37-
* @var array
38-
*/
39-
protected $parameters = [];
40-
41-
42-
/**
43-
* Execute the console command.
44-
*
45-
* @return void
46-
*/
4735
public function handle()
4836
{
49-
$this->getParameters();
50-
$fileName = $this->parameters['input'];
51-
if (!$this->parameters['locale']) {
37+
$fileName = $this->argument('input');
38+
$locale = $this->option('locale');
39+
if (!$locale) {
5240
$locale = pathinfo($fileName, PATHINFO_FILENAME);
53-
$this->parameters['locale'] = $locale;
5441
if (file_exists(resource_path("lang/$locale"))) {
5542
$this->info("Detected locale $locale");
5643
} else {
57-
throw new \Exception("Could not detect locale of $fileName");
44+
$this->error("Could not detect locale of $fileName");
45+
return 1;
5846
}
5947
}
6048
$translations = $this->readTranslations($fileName);
61-
LangListService::writeLangList($this->parameters['locale'], $this->parameters['group'], $translations);
62-
}
63-
64-
/**
65-
* Fetch command parameters (arguments and options) and analyze them.
66-
*
67-
* @return void
68-
*/
69-
private function getParameters()
70-
{
71-
$parameters = [
72-
'input' => $this->argument('input'),
73-
'locale' => $this->option('locale'),
74-
'group' => $this->option('group'),
75-
'delimiter' => $this->option('delimiter'),
76-
'enclosure' => $this->option('enclosure'),
77-
'escape' => $this->option('escape'),
78-
'excel' => $this->option('excel'),
79-
];
80-
$parameters = array_filter($parameters, function ($var) {
81-
return !is_null($var);
82-
});
83-
$this->parameters = array_merge(config('lang_import_export.import', []), $parameters);
49+
$group = $this->option('group');
50+
LangListService::writeLangList($locale, $group, $translations);
51+
if ($this->option('placeholders')) {
52+
$baseTranslations = LangListService::loadLangList(config('lang_import_export.base_locale'), $group);
53+
foreach (LangListService::validatePlaceholders($translations, $baseTranslations) as $errors) {
54+
$this->warn($locale . "/{$errors['group']}.{$errors['key']} is missing \"{$errors['placeholder']}\".");
55+
$this->info($errors['translation'], 'v');
56+
$this->info($errors['baseTranslation'], 'vv');
57+
}
58+
}
8459
}
8560

8661

87-
8862
/**
8963
* Get translations from CSV file.
9064
*
9165
* @return array
66+
* @throws \Exception
9267
*/
9368
private function readTranslations($fileName)
9469
{
@@ -104,34 +79,31 @@ private function readTranslations($fileName)
10479
/**
10580
* Read content of file.
10681
*
107-
* @param FilePointer $input
82+
* @param resource $input
10883
* @return array
10984
* @throws \Exception
11085
*/
11186
private function readFile($input)
11287
{
113-
if ($this->parameters['excel']) {
88+
if ($this->option('excel')) {
11489
$this->adjustFromExcel();
11590
}
11691

11792
$translations = [];
11893
$confirmed = false;
119-
while (($data = fgetcsv($input, 0, $this->parameters['delimiter'], $this->parameters['enclosure'],
120-
$this->parameters['escape'])) !== false) {
94+
while (($data = fgetcsv(
95+
$input, 0, $this->option('delimiter'), $this->option('enclosure'), $this->option('escape'))
96+
) !== false) {
12197
if (isset($translations[$data[0]]) == false) {
12298
$translations[$data[0]] = [];
12399
}
124-
125100
$columns = sizeof($data);
126101
if ($columns < 3) {
127102
throw new \Exception("File has only $columns column/s");
128103
}
129-
if ($columns > 3 &&
130-
!($confirmed || $confirmed = $this->confirm("File contains $columns columns, continue?"))
131-
) {
104+
if ($columns > 3) {
132105
throw new \Exception('Canceled by user');
133106
}
134-
135107
$translations[$data[0]][$data[1]] = $data[2];
136108
}
137109

@@ -145,7 +117,7 @@ private function readFile($input)
145117
*/
146118
private function adjustFromExcel()
147119
{
148-
$data = file_get_contents($this->parameters['input']);
149-
file_put_contents($this->parameters['input'], mb_convert_encoding($data, 'UTF-8', 'UTF-16'));
120+
$data = file_get_contents($this->argument('input'));
121+
file_put_contents($this->argument('input'), mb_convert_encoding($data, 'UTF-8', 'UTF-16'));
150122
}
151123
}

src/Console/ValidationCommand.php

Lines changed: 16 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ class ValidationCommand extends Command
1717
*/
1818
protected $signature = 'lang:validate
1919
{target? : Locale to be checked.}
20-
{--l|locale= : The locales to be exported. Separated by comma (default - default lang of application).}
21-
{--g|group= : The name of translation file to export (default all groups).}
20+
{--l|locale= : Base locale (default - base locale from config).}
21+
{--g|group= : The name of translation file to export (default - base group from config).}
2222
{--m|missing : Show missing translations}
2323
';
2424

@@ -29,13 +29,6 @@ class ValidationCommand extends Command
2929
*/
3030
protected $description = "Check language files for missing or misspelled placeholders";
3131

32-
/**
33-
* Parameters provided to command.
34-
*
35-
* @var array
36-
*/
37-
protected $parameters = [];
38-
3932
/**
4033
* Execute the console command.
4134
*
@@ -44,16 +37,17 @@ class ValidationCommand extends Command
4437
*/
4538
public function handle()
4639
{
47-
$this->getParameters();
48-
49-
$baseTranslations = LangListService::loadLangList($this->parameters['locale'], $this->parameters['group']);
50-
if (empty($this->parameters['target'])) {
40+
$baseLocale = $this->option('locale') ?: config('lang_import_export.base_locale');
41+
$groups = $this->option('group') ?: config('lang_import_export.base_group');
42+
$baseTranslations = LangListService::loadLangList($baseLocale, $groups);
43+
$target = $this->argument('target');
44+
if (empty($target)) {
5145
$this->error('--target is required');
5246
}
53-
foreach ($this->strToArray($this->parameters['target']) as $locale) {
54-
$targetTranslations = LangListService::loadLangList($locale, $this->parameters['group']);
47+
foreach ($this->strToArray($target) as $locale) {
48+
$targetTranslations = LangListService::loadLangList($locale, $groups);
5549
$this->validatePlaceholders($targetTranslations, $baseTranslations, $locale);
56-
if ($this->parameters['missing']) {
50+
if ($this->option('missing')) {
5751
$this->showMissing($targetTranslations, $baseTranslations, $locale);
5852
}
5953
}
@@ -67,51 +61,18 @@ private function strToArray($string, $fallback = [])
6761
return array_filter(array_map('trim', explode(',', $string)));
6862
}
6963

70-
/**
71-
* Fetch command parameters (arguments and options) and analyze them.
72-
*
73-
* @return void
74-
*/
75-
private function getParameters()
76-
{
77-
$parameters = [
78-
'target' => $this->argument('target'),
79-
'locale' => $this->option('locale') ?: \App::getLocale(),
80-
'group' => $this->option('group'),
81-
'missing' => $this->option('missing'),
82-
];
83-
$parameters = array_filter($parameters, function ($var) {
84-
return !is_null($var);
85-
});
86-
$this->parameters = array_merge(config('lang_import_export.validate', []), $parameters);
87-
}
88-
89-
private function matchPlaceholders($translation)
90-
{
91-
preg_match_all('~(:[a-zA-Z0-9_]+)~', $translation, $m);
92-
return $m[1] ?? [];
93-
}
94-
9564
/**
9665
* @param $targetTranslations
9766
* @param $baseTranslations
9867
* @param $locale
9968
*/
100-
private function validatePlaceholders($targetTranslations, $baseTranslations, $locale): void
69+
private function validatePlaceholders($targetTranslations, $baseTranslations, $locale)
10170
{
10271
$this->info('Searching for missing placeholers...');
103-
foreach ($targetTranslations as $group => $translations) {
104-
foreach ($translations as $key => $translation) {
105-
if (isset($baseTranslations[$group][$key]) && is_string($baseTranslations[$group][$key])) {
106-
$placeholders = $this->matchPlaceholders($baseTranslations[$group][$key]);
107-
foreach ($placeholders as $placeholder) {
108-
if (strpos($translation, $placeholder) === false) {
109-
$this->warn("$locale/$group.$key is missing \"$placeholder\".");
110-
$this->info($translation, 'v');
111-
}
112-
}
113-
}
114-
}
72+
foreach (LangListService::validatePlaceholders($targetTranslations, $baseTranslations) as $errors) {
73+
$this->warn($locale . "/{$errors['group']}.{$errors['key']} is missing \"{$errors['placeholder']}\".");
74+
$this->info($errors['translation'], 'v');
75+
$this->info($errors['baseTranslation'], 'vv');
11576
}
11677
}
11778

@@ -124,7 +85,7 @@ private function showMissing($targetTranslations, $baseTranslations, $locale)
12485
continue;
12586
}
12687
foreach ($translations as $key => $translation) {
127-
if (!isset($targetTranslations[$group][$key])) {
88+
if (!empty($baseTranslations[$group][$key]) && !isset($targetTranslations[$group][$key])) {
12889
$this->warn("$locale/$group.$key is missing");
12990
}
13091
}

0 commit comments

Comments
 (0)