diff --git a/src/Console/ExportToCsvCommand.php b/src/Console/ExportToCsvCommand.php index d38dfe1..da470b5 100644 --- a/src/Console/ExportToCsvCommand.php +++ b/src/Console/ExportToCsvCommand.php @@ -3,6 +3,9 @@ namespace LangImportExport\Console; use Illuminate\Console\Command; +use PhpOffice\PhpSpreadsheet\IOFactory; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Xls; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use LangImportExport\Facades\LangListService; @@ -22,7 +25,7 @@ class ExportToCsvCommand extends Command {--exclude= : The names of translation files to exclude (default - group from config).} {--o|output= : Filename of exported translation, :locale, :target is replaced (default - export_path from config).} {--z|zip= : Zip all files.} - {--X|excel : Set file encoding for Excel (optional, default - UTF-8).} + {--e|ext= : Type of files extension (available extensions Xls, Xlsx, Ods, Csv, Html, Tcpdf, Mpdf, Dompdf).} {--D|delimiter=, : Field delimiter (optional, default - ",").} {--E|enclosure=" : Field enclosure (optional, default - \'"\').} '; @@ -49,11 +52,14 @@ public function handle() { $exportLocales = $this->option('locale') ?: config('lang_import_export.export_locale'); $targetLocales = $this->option('target') ?: config('lang_import_export.export_target'); + $fileExtensions = $this->option('ext') ?: config('lang_import_export.export_default_extension'); foreach ($this->strToArray($exportLocales) as $exportLocale) { foreach ($this->strToArray($targetLocales, [null]) as $targetLocale) { $translations = $this->getTranslations($exportLocale, $targetLocale); - $this->saveTranslations($exportLocale, $targetLocale, $translations); - $this->info(strtoupper($exportLocale) . strtoupper($targetLocale ?: '') . ' Translations saved to: ' . $this->getOutputFileName($exportLocale, $targetLocale)); + $wordCount = $this->getTranslatableWordCount($translations); + $fileName = $this->getOutputFileName($exportLocale, $wordCount, $fileExtensions, $targetLocale); + $this->saveTranslations($translations, $fileName, $fileExtensions); + $this->info(strtoupper($exportLocale) . strtoupper($targetLocale ?: '') . ' Translations saved to: ' . $this->getOutputFileName($exportLocale, $wordCount, $fileExtensions, $targetLocale)); } } if ($zipName = $this->option('zip')) { @@ -105,116 +111,57 @@ private function getTranslations($locale, $target = null) } /** - * Save fetched translations to file. - * * @param $locale - * @param $target - * @param $translations - * @return void - * @throws \Exception + * @param null $target + * @return mixed */ - private function saveTranslations($locale, $target, $translations) + private function getOutputFileName($locale, $wordCount, $fileExtension, $target = null) { - $output = $this->openFile($locale, $target); - - $this->saveTranslationsToFile($output, $translations); - - $this->closeFile($output); - - if ($this->option('excel')) { - $this->adjustToExcel($this->getOutputFileName($locale, $target)); - } + $fileName = $this->option('output') ?: config('lang_import_export.export_path'); + $fileName = str_replace(':locale', $locale, $fileName); + $fileName = str_replace(':target', $target, $fileName); + $fileName = str_replace(':wordcount', $wordCount, $fileName); + $fileName = str_replace(':ext', $fileExtension, $fileName); + return $fileName; } /** - * Open specified file (if not possible, open default one). - * - * @param $locale - * @param $target - * @throws \Exception - * @return resource + * @param $translations */ - private function openFile($locale, $target) + private function getTranslatableWordCount($translations) { - $fileName = $this->getOutputFileName($locale, $target); - - if (!($output = fopen($fileName, 'w'))) { - throw new \Exception("$fileName failed to open"); + $wordCount = 0; + foreach ($translations as $group => $files) { + foreach ($files as $key => $value) { + if (is_array($value)) { + continue; + } + $wordCount += str_word_count($value); + } } - $this->files[] = $fileName; - - fputs($output, "\xEF\xBB\xBF"); - - return $output; + return $wordCount; } /** - * Save content of translation files to specified file. - * - * @param resource $output - * @param array $translations - * @return void + * @param $translations + * @param $fileName + * @param $fileExtension */ - private function saveTranslationsToFile($output, $translations) + private function saveTranslations($translations, $fileName, $fileExtension) { + $data = []; foreach ($translations as $group => $files) { foreach ($files as $key => $value) { if (is_array($value)) { continue; } - $this->writeFile($output, $group, $key, $value); + $data[] = [$group, $key, $value]; } } - } - - /** - * Put content of file to specified file with CSV parameters. - * - * @return void - */ - private function writeFile() - { - $data = func_get_args(); - $output = array_shift($data); - fputcsv($output, $data, $this->option('delimiter'), $this->option('enclosure')); - } - - /** - * Close output file and check if adjust file to Excel format. - * - * @param resource $output - * @return void - */ - private function closeFile($output) - { - fclose($output); - } - - /** - * Adjust file to Excel format. - * - * @return void - * - */ - private function adjustToExcel($fileName) - { - $data = file_get_contents($fileName); - file_put_contents( - $fileName, - chr(255) . chr(254) . mb_convert_encoding($data, 'UTF-16LE', 'UTF-8') - ); - } - - /** - * @param $locale - * @param null $target - * @return mixed - */ - private function getOutputFileName($locale, $target = null) - { - $fileName = $this->option('output') ?: config('lang_import_export.export_path'); - $fileName = str_replace(':locale', $locale, $fileName); - $fileName = str_replace(':target', $target, $fileName); - return $fileName; + $spreadsheet = new Spreadsheet(); + $spreadsheet->getActiveSheet()->fromArray($data); + $writer = IOFactory::createWriter($spreadsheet, ucfirst(strtolower($fileExtension))); + $writer->save($fileName); + $this->files[] = $fileName; } } diff --git a/src/Console/ImportFromCsvCommand.php b/src/Console/ImportFromCsvCommand.php index 20fd2cc..ebdc699 100644 --- a/src/Console/ImportFromCsvCommand.php +++ b/src/Console/ImportFromCsvCommand.php @@ -2,6 +2,7 @@ namespace LangImportExport\Console; +use Composer\Util\Zip; use Illuminate\Console\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; @@ -16,8 +17,9 @@ class ImportFromCsvCommand extends Command * @var string */ protected $signature = 'lang:import - {input : Filename of file to be imported with translation files.} + {input?* : Filenames of files to be imported with translation files.} {--l|locale= : The locale to be imported (default - parsed from file name).} + {--z|zip= : The name of zip file to be imported from.} {--g|group= : The name of translation file to imported (default - base group from config).} {--p|placeholders : Search for missing placeholders in imported keys (see config file for default value).} {--html : Validate html in imported keys (see config file for default value).} @@ -33,35 +35,61 @@ class ImportFromCsvCommand extends Command public function handle() { - $fileName = $this->argument('input'); - $locale = $this->option('locale'); - if (!$locale) { - $locale = pathinfo($fileName, PATHINFO_FILENAME); - if (file_exists(resource_path("lang/$locale"))) { - $this->info("Detected locale $locale"); - } else { - $this->error("Could not detect locale of $fileName"); - return 1; - } + $files = $this->argument('input'); + if ($zipName = $this->option('zip')) { + $directory = 'extracted_language_files/'; + $zip = new \ZipArchive(); + $zip->open($zipName); + $zip->extractTo($directory); + $zip->close(); + $files = array_merge($files, array_map(function($val) use ($directory) { + return $directory.$val; + }, preg_grep('/^([^.])/', scandir($directory)))); } - $translations = $this->readTranslations($fileName); - $group = $this->option('group'); - LangListService::writeLangList($locale, $group, $translations); - if ($this->option('placeholders') || config('lang_import_export.import_validate_placeholders')) { - $baseTranslations = LangListService::loadLangList(config('lang_import_export.base_locale'), $group); - foreach (LangListService::validatePlaceholders($translations, $baseTranslations) as $errors) { - $this->warn("resources/lang/$locale/{$errors['group']}.php {$errors['key']} is missing \"{$errors['placeholder']}\"."); - $this->info($errors['translation'], 'v'); - $this->info($errors['baseTranslation'], 'vv'); + $locale = $this->option('locale'); + foreach ($files as $fileName) { + try { + if (!$locale) { + preg_match('#\((.*?)\)#', pathinfo($fileName, PATHINFO_FILENAME), $localeCode); + $locale = $localeCode[1] ?? pathinfo($fileName, PATHINFO_FILENAME); + if (file_exists(resource_path("lang/$locale"))) { + $this->info("Detected locale $locale"); + } else { + $this->error("Could not detect locale of $fileName"); + continue; + } + } + if (!file_exists($fileName)) { + $this->error("File $fileName does not exist"); + continue; + } + $translations = $this->readTranslations($fileName); + $group = $this->option('group'); + LangListService::writeLangList($locale, $group, $translations); + if ($this->option('placeholders') || config('lang_import_export.import_validate_placeholders')) { + $baseTranslations = LangListService::loadLangList(config('lang_import_export.base_locale'), $group); + foreach (LangListService::validatePlaceholders($translations, $baseTranslations) as $errors) { + $this->warn("resources/lang/$locale/{$errors['group']}.php {$errors['key']} is missing \"{$errors['placeholder']}\"."); + $this->info($errors['translation'], 'v'); + $this->info($errors['baseTranslation'], 'vv'); + } + } + if ($this->option('html') || config('lang_import_export.import_validate_html')) { + $baseTranslations = LangListService::loadLangList(config('lang_import_export.base_locale'), $group); + foreach (LangListService::validateHTML($translations, $baseTranslations) as $errors) { + $this->warn("resources/lang/$locale/{$errors['group']}.php {$errors['key']} is missing `{$errors['tag']}` html tag."); + $this->info($errors['translation'], 'v'); + $this->info($errors['baseTranslation'], 'vv'); + } + } + $locale = $this->option('locale'); + } catch (\Throwable $t) { + $this->error('Error occurred: '. $t->getMessage()); + continue; } } - if ($this->option('html') || config('lang_import_export.import_validate_html')) { - $baseTranslations = LangListService::loadLangList(config('lang_import_export.base_locale'), $group); - foreach (LangListService::validateHTML($translations, $baseTranslations) as $errors) { - $this->warn("resources/lang/$locale/{$errors['group']}.php {$errors['key']} is missing `{$errors['tag']}` html tag."); - $this->info($errors['translation'], 'v'); - $this->info($errors['baseTranslation'], 'vv'); - } + if (!empty($directory)) { + \File::deleteDirectory($directory); } } @@ -85,10 +113,10 @@ private function readTranslations($fileName) } $columns = count($data); if ($columns < 3) { - throw new \Exception("File has only $columns column/s"); + throw new \Exception("File $fileName has only $columns column/s"); } if ($columns > 3 && !$this->option('column-map')) { - throw new \Exception("File has $columns columns"); + $map = ['A', 'B', $spreadsheet->getActiveSheet()->getHighestDataColumn()]; } $translations[$data[$map[0]]][$data[$map[1]]] = $data[$map[2]]; } diff --git a/src/config/lang_import_export.php b/src/config/lang_import_export.php index f2c70d0..ddbea8d 100644 --- a/src/config/lang_import_export.php +++ b/src/config/lang_import_export.php @@ -8,7 +8,8 @@ 'exclude_groups' => null, // comma separated list 'export_locale' => 'en',// locale list separated by comma 'export_target' => null,// target locale list separated by comma - 'export_path' => storage_path(':locale:target.csv'), + 'export_path' => storage_path(':locale:target.:ext'), + 'export_default_extension' => 'csv', 'import_validate_placeholders' => false, // validate placeholders by default 'import_validate_html' => false, // validate html by default ];