Skip to content

Commit

Permalink
Merge pull request #3 from Afting/master
Browse files Browse the repository at this point in the history
Added export support for xls files, importing from multiple files and…
  • Loading branch information
AidasK authored Jul 2, 2020
2 parents cedf510 + 254735f commit 2afb9a3
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 123 deletions.
133 changes: 40 additions & 93 deletions src/Console/ExportToCsvCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 - \'"\').} ';

Expand All @@ -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')) {
Expand Down Expand Up @@ -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;
}
}
86 changes: 57 additions & 29 deletions src/Console/ImportFromCsvCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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).}
Expand All @@ -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);
}
}

Expand All @@ -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]];
}
Expand Down
3 changes: 2 additions & 1 deletion src/config/lang_import_export.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
];

0 comments on commit 2afb9a3

Please sign in to comment.