├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── intro.jpg ├── phpunit.xml └── src ├── Console ├── ExportToCsvCommand.php └── ImportFromCsvCommand.php ├── Facades └── LangListService.php ├── LangImportExportServiceProvider.php ├── LangListService.php └── Support └── helpers.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | 7 | matrix: 8 | include: 9 | - php: 5.6 10 | env: 'COMPOSER_FLAGS="--prefer-stable --prefer-lowest"' 11 | 12 | before_script: 13 | - composer self-update 14 | - composer install --prefer-source --no-interaction --dev 15 | - composer dump-autoload 16 | 17 | script: phpunit 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 UFirst Group 4 | Copyright (c) 2017 HighSolutions 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Laravel-Lang-Import-Export by HighSolutions](https://raw.githubusercontent.com/highsolutions/laravel-lang-import-export/master/intro.jpg) 2 | 3 | Laravel-Lang-Import-Export 4 | ========================== 5 | 6 | This package provides artisan commands to import and export language files from and to CSV. This can be used to send translations to agencies that normally work with Excel-like files. 7 | 8 | It turns some navigation.php file... 9 | 10 | ```php 11 | 15 | array ( 16 | 'next' => 'Next', 17 | 'prev' => 'Previous', 18 | 'play' => 'Play', 19 | ), 20 | 'tips' => 21 | array ( 22 | 'next' => 'Navigate to the next item', 23 | 'prev' => 'Navigate to the previous item', 24 | 'play' => 'Autoplay the slide show', 25 | ), 26 | ); 27 | ``` 28 | ...to the following CSV... 29 | 30 | ```CSV 31 | navigation.commands.next,Next 32 | navigation.commands.prev,Previous 33 | navigation.commands.play,Play 34 | navigation.tips.next,"Navigate to the next item" 35 | navigation.tips.prev,"Navigate to the previous item" 36 | navigation.tips.play,"Autoplay the slide show" 37 | 38 | ``` 39 | ...and vice versa. 40 | 41 | Installation 42 | ------------ 43 | 44 | Add the following line to the `require` section of your Laravel webapp's `composer.json` file: 45 | 46 | ```javascript 47 | "require": { 48 | "HighSolutions/laravel-lang-import-export": "^6.0" 49 | } 50 | ``` 51 | 52 | Run `composer update` to install the package. 53 | 54 | This package uses Laravel 5.5 Package Auto-Discovery. 55 | For previous versions of Laravel, you need to update `config/app.php` by adding an entry for the service provider: 56 | 57 | ```php 58 | 'providers' => array( 59 | /* ... */ 60 | 'HighSolutions\LangImportExport\LangImportExportServiceProvider' 61 | ) 62 | ``` 63 | 64 | Usage 65 | ----- 66 | 67 | The package currently provides two commands, one for exporting the files and one for importing them back: 68 | 69 | ### Export 70 | 71 | ```bash 72 | php artisan lang:export 73 | php artisan lang:export en * path/to/export 74 | php artisan lang:export en auth -A -X 75 | ``` 76 | 77 | When you call command without parameters, export file will be generated for all localization files within default locale. But you can define **locale** explicitly. You can also export only one file (second parameter - **group**) and define where to store file (you can provide name with and without .csv extension). When you use **output** argument, default path is base_path() -> catalog of your whole project. 78 | But there is few more useful parameters: 79 | 80 | | name of parameter | description | is required? | default value | 81 | |-------------------|-----------------------------------------|--------------|------------------------------------| 82 | | locale | The locale to be exported | NO | default lang of application | 83 | | group | The name of translation file to export | NO | \* - all files | 84 | | output | Filename of exported translation files | NO | storage/app/lang-import-export.csv | 85 | | -A / --append | Append name of group to the name of file | NO | empty | 86 | | -X / --excel | Set file encoding (UTF-16) for Excel | NO | UTF-8 | 87 | | -D / --delimiter | Field delimiter | NO | , | 88 | | -E / --enclosure | Field enclosure | NO | " | 89 | 90 | ### Import 91 | 92 | ``` 93 | php artisan lang:import 94 | php artisan lang:import en * path/to/import 95 | php artisan lang:import en auth -X 96 | ``` 97 | 98 | When you call command without parameters - it will try to read default file of export command without parameters for default locale and all localization files. You can of course specify all parameters (**locale**, **group**, **input**) and there is few more options: 99 | 100 | | name of parameter | description | is required? | default value | 101 | |-------------------|----------------------------------------------|--------------|------------------------------------| 102 | | locale | The locale to be imported | NO | default lang of application | 103 | | group | The name of translation file to import | NO | * - all files | 104 | | output | Filename of translation files to be imported | NO | storage/app/lang-import-export.csv | 105 | | -X / --excel | Set file encoding from Excel | NO | UTF-8 | 106 | | -D / --delimiter | Field delimiter | NO | , | 107 | | -E / --enclosure | Field enclosure | NO | " | 108 | | -C / --escape | Field escape | NO | \ | 109 | 110 | Changelog 111 | ------------ 112 | 113 | 6.4.0 114 | * Support Laravel 12.x 115 | 116 | 6.3.0 117 | * Support Laravel 11.x 118 | 119 | 6.2.0 120 | * Support Laravel 9.x and 10.x 121 | 122 | 6.1.0 123 | * Support Laravel 7.x and 8.x 124 | 125 | 6.0.0 126 | * Support Laravel 6.0 127 | 128 | 5.4.10 129 | * Laravel 5.7 support 130 | 131 | 5.4.9 132 | * Create new directory, when not exists before 133 | 134 | 5.4.8 135 | * Fix UTF-8 encoding 136 | 137 | 5.4.7 138 | * Handling empty keys 139 | 140 | 5.4.6 141 | * Laravel 5.6 support 142 | 143 | 5.4.3 144 | - support Package Auto-Discovery 145 | 146 | 5.4.2 147 | - resolve problems with PSR-4 autoloading 148 | 149 | 5.4.1 150 | - improved import command 151 | - improved Excel support 152 | - support of [LaravelLocalization](https://github.com/mcamara/laravel-localization) routes files 153 | 154 | 5.4.0 155 | - refactor whole repository 156 | - add support for Excel 157 | - add support for export and import all localization files 158 | - any arguments are not required 159 | 160 | Roadmap 161 | ------------ 162 | 163 | * Removing tabs from text 164 | * Option for deleting export file after importing. 165 | * Option for excluding certain files (and system ones). 166 | * Unit tests! 167 | 168 | Credits 169 | ------------ 170 | 171 | This package was originally created by [UFirst](http://github.com/ufirstgroup) and is available here: [Laravel-lang-import-export](https://github.com/ufirstgroup/laravel-lang-import-export). 172 | 173 | Currently is developed by [HighSolutions](https://highsolutions.org), software house from Poland in love in Laravel. 174 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "highsolutions/laravel-lang-import-export", 3 | "description": "A Laravel package providing artisan commands to import and export language files from and to CSV.", 4 | "keywords": ["laravel", "localization", "translation", "messages", "import", "export", "CSV"], 5 | "authors": [ 6 | { 7 | "name": "Michael Ruoss", 8 | "email": "michael.ruoss@UFirstgroup.com" 9 | }, 10 | { 11 | "name": "HighSolutions", 12 | "email": "adam@highsolutions.pl" 13 | } 14 | ], 15 | "license": "MIT", 16 | "require": { 17 | "php": "^5.6.4|^7.3|^8.0.2", 18 | "illuminate/support": "5.*|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "HighSolutions\\LangImportExport\\": "src/" 23 | }, 24 | "files": [ 25 | "src/Support/helpers.php" 26 | ] 27 | }, 28 | "extra": { 29 | "component": "package", 30 | "frameworks": ["Laravel 5.7", "Laravel 5.8", "Laravel 6.x", "Laravel 7.x", "Laravel 8.x", "Laravel 9.x", "Laravel 10.x", "Laravel 11.x", "Laravel 12.x"], 31 | "laravel": { 32 | "providers": [ 33 | "HighSolutions\\LangImportExport\\LangImportExportServiceProvider" 34 | ] 35 | } 36 | }, 37 | "minimum-stability": "stable" 38 | } 39 | -------------------------------------------------------------------------------- /intro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/highsolutions/laravel-lang-import-export/d87523d71e5e5c608d11ee13acea8729b15cc72f/intro.jpg -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Console/ExportToCsvCommand.php: -------------------------------------------------------------------------------- 1 | defaultPath = storage_path('app'. DIRECTORY_SEPARATOR .'lang-import-export') . $this->ext; 64 | } 65 | 66 | /** 67 | * Execute the console command. 68 | * 69 | * @return void 70 | */ 71 | public function handle() 72 | { 73 | $this->getParameters(); 74 | 75 | $this->sayItsBeginning(); 76 | 77 | $translations = $this->getTranslations(); 78 | 79 | $this->saveTranslations($translations); 80 | 81 | $this->sayItsFinish(); 82 | } 83 | 84 | /** 85 | * Fetch command parameters (arguments and options) and analyze them. 86 | * 87 | * @return void 88 | */ 89 | private function getParameters() 90 | { 91 | $this->parameters = [ 92 | 'group' => $this->argument('group'), 93 | 'locale' => $this->argument('locale') === null ? config('app.locale') : $this->argument('locale'), 94 | 'output' => $this->argument('output') === null ? $this->defaultPath : base_path($this->argument('output')), 95 | 'append' => $this->option('append') !== false, 96 | 'excel' => $this->option('excel') !== false, 97 | 'delimiter' => $this->option('delimiter'), 98 | 'enclosure' => $this->option('enclosure'), 99 | ]; 100 | 101 | $this->setDefaultPath(); 102 | } 103 | 104 | /** 105 | * Set possible file names. 106 | * 107 | * @return void 108 | */ 109 | private function setDefaultPath() 110 | { 111 | if($this->parameters['append']) { 112 | $this->parameters['output'] .= '-'. $this->parameters['group']; 113 | $this->defaultPath .= '-'. $this->parameters['group']; 114 | } 115 | } 116 | 117 | /** 118 | * Display output that command has started and which groups are being exported. 119 | * 120 | * @return void 121 | */ 122 | private function sayItsBeginning() 123 | { 124 | $this->info(PHP_EOL 125 | . 'Translations export of '. ($this->parameters['group'] === null ? 'all groups' : $this->parameters['group'] .' group') .' - started.'); 126 | } 127 | 128 | /** 129 | * Get translations from localization files. 130 | * 131 | * @return array 132 | */ 133 | private function getTranslations() 134 | { 135 | return LangListService::loadLangList($this->parameters['locale'], $this->parameters['group']); 136 | } 137 | 138 | /** 139 | * Save fetched translations to file. 140 | * 141 | * @return void 142 | */ 143 | private function saveTranslations($translations) 144 | { 145 | $output = $this->openFile(); 146 | 147 | $this->saveTranslationsToFile($output, $translations); 148 | 149 | $this->closeFile($output); 150 | } 151 | 152 | /** 153 | * Open specified file (if not possible, open default one). 154 | * 155 | * @return FilePointerResource 156 | */ 157 | private function openFile() 158 | { 159 | if(substr($this->parameters['output'], -4) != $this->ext) 160 | $this->parameters['output'] .= $this->ext; 161 | 162 | if (!($output = fopen($this->parameters['output'], 'w'))) { 163 | $output = fopen($this->defaultPath . $this->ext, 'w'); 164 | } 165 | 166 | fputs($output, "\xEF\xBB\xBF"); 167 | 168 | return $output; 169 | } 170 | 171 | /** 172 | * Save content of translation files to specified file. 173 | * 174 | * @param FilePointerResource $output 175 | * @param array $translations 176 | * @return void 177 | */ 178 | private function saveTranslationsToFile($output, $translations) 179 | { 180 | foreach ($translations as $group => $files) { 181 | foreach($files as $key => $value) { 182 | if(is_array($value)) { 183 | continue; 184 | } 185 | $this->writeFile($output, $group, $key, $value); 186 | } 187 | } 188 | } 189 | 190 | /** 191 | * Put content of file to specified file with CSV parameters. 192 | * 193 | * @param FilePointerResource $output 194 | * @param string $group 195 | * @param string $key 196 | * @param string $value 197 | * @return void 198 | * 199 | */ 200 | private function writeFile() 201 | { 202 | $data = func_get_args(); 203 | $output = array_shift($data); 204 | fputcsv($output, $data, $this->parameters['delimiter'], $this->parameters['enclosure']); 205 | } 206 | 207 | /** 208 | * Close output file and check if adjust file to Excel format. 209 | * 210 | * @param FilePointerResource $output 211 | * @return void 212 | */ 213 | private function closeFile($output) 214 | { 215 | fclose($output); 216 | 217 | if($this->parameters['excel']) 218 | $this->adjustToExcel(); 219 | } 220 | 221 | /** 222 | * Adjust file to Excel format. 223 | * 224 | * @return void 225 | * 226 | */ 227 | private function adjustToExcel() 228 | { 229 | $data = file_get_contents($this->parameters['output']); 230 | file_put_contents($this->parameters['output'], chr(255) . chr(254) . mb_convert_encoding($data, 'UTF-16LE', 'UTF-8')); 231 | } 232 | 233 | /** 234 | * Display output that command is finished and where to find file. 235 | * 236 | * @return void 237 | */ 238 | private function sayItsFinish() 239 | { 240 | $this->info('Finished! Translations saved to: '. (substr($this->parameters['output'], strlen(base_path()) + 1)) 241 | . PHP_EOL); 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /src/Console/ImportFromCsvCommand.php: -------------------------------------------------------------------------------- 1 | defaultPath = storage_path('app'. DIRECTORY_SEPARATOR .'lang-import-export') . $this->ext; 64 | } 65 | 66 | /** 67 | * Execute the console command. 68 | * 69 | * @return void 70 | */ 71 | public function handle() 72 | { 73 | $this->getParameters(); 74 | 75 | $this->sayItsBeginning(); 76 | 77 | $translations = $this->getTranslations(); 78 | 79 | $this->saveTranslations($translations); 80 | 81 | $this->sayItsFinish(); 82 | } 83 | 84 | /** 85 | * Fetch command parameters (arguments and options) and analyze them. 86 | * 87 | * @return void 88 | */ 89 | private function getParameters() 90 | { 91 | $this->parameters = [ 92 | 'locale' => $this->argument('locale') === null ? config('app.locale') : $this->argument('locale'), 93 | 'group' => $this->argument('group'), 94 | 'input' => $this->argument('input') === null ? $this->defaultPath : base_path($this->argument('input')), 95 | 'delimiter' => $this->option('delimiter'), 96 | 'enclosure' => $this->option('enclosure'), 97 | 'escape' => $this->option('escape'), 98 | 'excel' => $this->option('excel') !== false, 99 | ]; 100 | 101 | if(substr($this->parameters['input'], -4) != $this->ext) 102 | $this->parameters['input'] .= $this->ext; 103 | } 104 | 105 | /** 106 | * Display output that command has started and which groups are being imported. 107 | * 108 | * @return void 109 | */ 110 | private function sayItsBeginning() 111 | { 112 | $this->info(PHP_EOL 113 | . 'Translations import of '. ($this->parameters['group'] === false ? 'all groups' : $this->parameters['group'] .' group') .' has started.'); 114 | } 115 | 116 | /** 117 | * Get translations from CSV file. 118 | * 119 | * @return array 120 | */ 121 | private function getTranslations() 122 | { 123 | $input = $this->openFile(); 124 | 125 | $translations = $this->readFile($input); 126 | 127 | $this->closeFile($input); 128 | 129 | return $translations; 130 | } 131 | 132 | /** 133 | * Opens file to read content. 134 | * 135 | * @return FileInputPointer 136 | */ 137 | private function openFile() 138 | { 139 | if (($input = fopen($this->parameters['input'], 'r')) === false) { 140 | $this->error('Can\'t open the input file!'); 141 | } 142 | 143 | return $input; 144 | } 145 | 146 | /** 147 | * Read content of file. 148 | * 149 | * @param FilePointer $input 150 | * @throws \Exception 151 | * @return array 152 | */ 153 | private function readFile($input) 154 | { 155 | if($this->parameters['excel']) 156 | $this->adjustFromExcel(); 157 | 158 | $translations = []; 159 | while (($data = fgetcsv($input, 0, $this->parameters['delimiter'], $this->parameters['enclosure'], $this->parameters['escape'])) !== false) { 160 | if(isset($translations[$data[0]]) == false) 161 | $translations[$data[0]] = []; 162 | 163 | if(sizeof($data) != 3) 164 | throw new \Exception("Wrong format of file. Try launch command with -X option if you use Excel for editing file."); 165 | 166 | $translations[$data[0]][$data[1]] = $data[2]; 167 | } 168 | 169 | return $translations; 170 | } 171 | 172 | /** 173 | * Adjust file to Excel format. 174 | * 175 | * @return void 176 | */ 177 | private function adjustFromExcel() 178 | { 179 | $data = file_get_contents($this->parameters['input']); 180 | file_put_contents($this->parameters['input'], mb_convert_encoding($data, 'UTF-8', 'UTF-16')); 181 | } 182 | 183 | /** 184 | * Close file. 185 | * 186 | * @return void 187 | */ 188 | private function closeFile($input) 189 | { 190 | fclose($input); 191 | } 192 | 193 | /** 194 | * Save fetched translations to file. 195 | * 196 | * @return void 197 | */ 198 | private function saveTranslations($translations) 199 | { 200 | LangListService::writeLangList($this->parameters['locale'], $this->parameters['group'], $translations); 201 | } 202 | 203 | /** 204 | * Display output that command is finished and where to find file. 205 | * 206 | * @return void 207 | */ 208 | private function sayItsFinish() 209 | { 210 | $this->info('Finished! Translations imported from: '. (substr($this->parameters['input'], strlen(base_path()) + 1)) 211 | . PHP_EOL); 212 | } 213 | 214 | } -------------------------------------------------------------------------------- /src/Facades/LangListService.php: -------------------------------------------------------------------------------- 1 | registerExportToCsvCommand(); 27 | $this->registerImportFromCsvCommand(); 28 | } 29 | 30 | /** 31 | * Register the service provider. 32 | * 33 | * @return void 34 | */ 35 | public function register() 36 | { 37 | $this->app->singleton('LangImportExportLangListService', function() { 38 | return new LangListService; 39 | }); 40 | } 41 | 42 | /** 43 | * Get the services provided by the provider. 44 | * 45 | * @return array 46 | */ 47 | public function provides() 48 | { 49 | return [ 50 | 'lang-export.csv', 'lang-import.csv' 51 | ]; 52 | } 53 | 54 | private function registerExportToCsvCommand() 55 | { 56 | $this->app->singleton('lang-export.csv', function($app) { 57 | return new ExportToCsvCommand(); 58 | }); 59 | 60 | $this->commands('lang-export.csv'); 61 | } 62 | 63 | private function registerImportFromCsvCommand() 64 | { 65 | $this->app->singleton('lang-import.csv', function($app) { 66 | return new ImportFromCsvCommand(); 67 | }); 68 | 69 | $this->commands('lang-import.csv'); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/LangListService.php: -------------------------------------------------------------------------------- 1 | isOneGroup($group)) { 25 | $result[$group] = $this->getGroup($locale, $group); 26 | return $result; 27 | } 28 | 29 | $path = resource_path('lang/'. $locale.'/'); 30 | $files = $this->getAllFiles($path); 31 | foreach($files as $file) { 32 | $file_path = substr($file->getRealPath(), strlen($path), -4); 33 | $result[$file_path] = $this->getGroup($locale, $file_path); 34 | } 35 | return $result; 36 | } 37 | 38 | /** 39 | * Check if $group is one file only. 40 | * 41 | * @param string $group 42 | * @return bool 43 | */ 44 | private function isOneGroup($group) 45 | { 46 | return $group != '*' && $group != ''; 47 | } 48 | 49 | /** 50 | * Fetch localization from file. 51 | * 52 | * @param string $locale 53 | * @param string $group 54 | * @return array 55 | */ 56 | private function getGroup($locale, $group) 57 | { 58 | $translations = Lang::getLoader()->load($locale, $group); 59 | return Arr::dot($translations); 60 | } 61 | 62 | /** 63 | * Get list of all files from $path. 64 | * 65 | * @param string $path 66 | * @return array 67 | */ 68 | private function getAllFiles($path) 69 | { 70 | return File::allFiles($path); 71 | } 72 | 73 | /** 74 | * Write translated content to localization file or files. 75 | * 76 | * @param string $locale 77 | * @param string $group 78 | * @param array $new_translations 79 | * @return void 80 | */ 81 | public function writeLangList($locale, $group, $new_translations) 82 | { 83 | if($this->isOneGroup($group)) { 84 | if(isset($new_translations[$group]) == false) 85 | return; 86 | 87 | return $this->writeLangFile($locale, $group, $new_translations[$group]); 88 | } 89 | 90 | foreach($new_translations as $group => $translations) 91 | $this->writeLangFile($locale, $group, $translations); 92 | } 93 | 94 | /** 95 | * Write translated content to one file. 96 | * 97 | * @param string $locale 98 | * @param string $group 99 | * @param array $new_translations 100 | * @throws \Exception 101 | * @return void 102 | */ 103 | private function writeLangFile($locale, $group, $new_translations) 104 | { 105 | $translations = $this->getTranslations($locale, $group, $new_translations); 106 | 107 | $header = "load($locale, $group); 137 | foreach($new_translations as $key => $value) { 138 | Arr::set($translations, $key, $value); 139 | } 140 | 141 | if(in_array($group, $this->dotFiles)) { 142 | $translations = Arr::dot($translations); 143 | } 144 | 145 | return $translations; 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/Support/helpers.php: -------------------------------------------------------------------------------- 1 | basePath() . DIRECTORY_SEPARATOR . 'resources' . ($path ? DIRECTORY_SEPARATOR . $path : $path); 14 | } 15 | } 16 | --------------------------------------------------------------------------------