├── .gitignore ├── README.md ├── src ├── TranslationServiceProvider.php ├── TranslationModel.php ├── Console │ ├── ResetCommand.php │ ├── ExportCommand.php │ └── ImportCommand.php ├── ExportButton.php ├── Translation.php └── TranslationController.php ├── composer.json ├── LICENSE ├── database └── migrations │ └── 2017_07_17_040159_create_translations_table.php └── resources └── views └── edit.blade.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | phpunit.phar 3 | /vendor 4 | composer.phar 5 | composer.lock 6 | *.project 7 | .idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | laravel-admin-ext/translation 2 | ============================= 3 | 4 | License 5 | ------------ 6 | Licensed under [The MIT License (MIT)](LICENSE). 7 | -------------------------------------------------------------------------------- /src/TranslationServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadViewsFrom(__DIR__.'/../resources/views', 'laravel-admin-translations'); 15 | 16 | if ($this->app->runningInConsole()) { 17 | $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); 18 | 19 | $this->commands([ 20 | Console\ImportCommand::class, 21 | Console\ExportCommand::class, 22 | Console\ResetCommand::class, 23 | ]); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel-admin-ext/translation", 3 | "description": "Translation manager for laravel", 4 | "type": "library", 5 | "keywords": ["laravel-admin", "translation", "lang"], 6 | "homepage": "https://github.com/laravel-admin-extensions/translation", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "z-song", 11 | "email": "zosong@126.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=7.0.0", 16 | "laravel/framework": "~5.5", 17 | "encore/laravel-admin": "~1.5" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "~6.0", 21 | "laravel/laravel": "~5.5" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Encore\\Admin\\Translation\\": "src/" 26 | } 27 | }, 28 | "extra": { 29 | "laravel": { 30 | "providers": [ 31 | "Encore\\Admin\\Translation\\TranslationServiceProvider" 32 | ] 33 | 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/TranslationModel.php: -------------------------------------------------------------------------------- 1 | setConnection(config('admin.database.connection') ?: config('database.default')); 25 | 26 | $this->setTable(config('admin.extensions.translation.table', 'laravel_translations')); 27 | } 28 | 29 | public static function boot() 30 | { 31 | parent::boot(); 32 | 33 | static::updating(function (Model $model) { 34 | if ($model->attributes['value'] != $model->original['value']) { 35 | $model->status = static::STATUS_CHANGED; 36 | } 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jens Segers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/Console/ResetCommand.php: -------------------------------------------------------------------------------- 1 | manager = $manager; 37 | 38 | parent::__construct(); 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function handle() 45 | { 46 | $this->manager->resetTranslations($this->argument('locale')); 47 | 48 | $this->info('All translations are deleted'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Console/ExportCommand.php: -------------------------------------------------------------------------------- 1 | manager = $manager; 37 | 38 | parent::__construct(); 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function handle() 45 | { 46 | if ($group = $this->argument('group')) { 47 | $this->manager->exportTranslations($group); 48 | } else { 49 | $this->manager->exportAllTranslations(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Console/ImportCommand.php: -------------------------------------------------------------------------------- 1 | manager = $manager; 37 | 38 | parent::__construct(); 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function handle() 45 | { 46 | $counter = $this->manager->importTranslations($this->argument('locale'), $this->option('force')); 47 | 48 | $this->info('Done importing, processed '.$counter.' items!'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /database/migrations/2017_07_17_040159_create_translations_table.php: -------------------------------------------------------------------------------- 1 | create($table, function (Blueprint $table) { 24 | $table->increments('id'); 25 | $table->integer('status')->default(0); 26 | $table->string('locale'); 27 | $table->string('group'); 28 | $table->string('key'); 29 | $table->text('value')->nullable(); 30 | $table->timestamps(); 31 | }); 32 | } 33 | 34 | /** 35 | * Reverse the migrations. 36 | * 37 | * @return void 38 | */ 39 | public function down() 40 | { 41 | $connection = config('admin.database.connection') ?: config('database.default'); 42 | 43 | $table = config('admin.extensions.translations.table', 'laravel_translations'); 44 | 45 | Schema::connection($connection)->dropIfExists($table); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ExportButton.php: -------------------------------------------------------------------------------- 1 | get()->toarray(); 14 | 15 | return array_flatten($groups); 16 | } 17 | 18 | protected function setupScript() 19 | { 20 | $script = <<<'SCRIPT' 21 | 22 | $('._export').click(function () { 23 | $.ajax({ 24 | url: $(this).attr('href'), 25 | type: "GET", 26 | success: function (data) { 27 | $.pjax.reload('#pjax-container'); 28 | toastr.success(data.message); 29 | } 30 | }); 31 | 32 | return false; 33 | }); 34 | 35 | SCRIPT; 36 | 37 | Admin::script($script); 38 | } 39 | 40 | /** 41 | * Render Export button. 42 | * 43 | * @return string 44 | */ 45 | public function render() 46 | { 47 | $this->setupScript(); 48 | 49 | $export = trans('admin.export'); 50 | 51 | $links = ''; 52 | 53 | foreach ($this->getGroups() as $group) { 54 | $action = route('translations.index', ['export' => $group]); 55 | $links .= "
  • $group
  • "; 56 | } 57 | 58 | return << 61 | {$export} 62 | 66 | 69 | 70 |    71 | 72 | EOT; 73 | } 74 | 75 | public function __toString() 76 | { 77 | return $this->render(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Translation.php: -------------------------------------------------------------------------------- 1 | directories(resource_path('lang'))); 24 | 25 | return array_unique(array_merge($exists, static::config('locales', []))); 26 | } 27 | 28 | public static function getGroups() 29 | { 30 | $groups = TranslationModel::select(DB::raw('DISTINCT `group`'))->get()->toarray(); 31 | 32 | $groups = array_flatten($groups); 33 | 34 | return array_unique(array_merge($groups, static::config('groups', []))); 35 | } 36 | 37 | public function importTranslations($locale = null, $force = false) 38 | { 39 | if (is_null($locale)) { 40 | $locale = config('app.locale'); 41 | } 42 | 43 | $langPath = resource_path('lang').'/'.$locale; 44 | 45 | $counter = 0; 46 | 47 | foreach (app('files')->allfiles($langPath) as $file) { 48 | $info = pathinfo($file); 49 | $group = $info['filename']; 50 | 51 | $translations = \Lang::getLoader()->load($locale, $group); 52 | if ($translations && is_array($translations)) { 53 | foreach (array_dot($translations) as $key => $value) { 54 | $importedTranslation = $this->importTranslation($key, $value, $locale, $group, $force); 55 | $counter += $importedTranslation ? 1 : 0; 56 | } 57 | } 58 | } 59 | 60 | return $counter; 61 | } 62 | 63 | public function importTranslation($key, $value, $locale, $group, $force = false) 64 | { 65 | if (is_array($value)) { 66 | return false; 67 | } 68 | 69 | $translation = TranslationModel::firstOrNew([ 70 | 'locale' => $locale, 71 | 'group' => $group, 72 | 'key' => $key, 73 | ]); 74 | 75 | if ($force || !$translation->value) { 76 | $translation->value = (string) $value; 77 | $translation->status = TranslationModel::STATUS_SAVED; 78 | $translation->save(); 79 | 80 | return true; 81 | } 82 | 83 | return false; 84 | } 85 | 86 | /** 87 | * @param $locale 88 | */ 89 | public function resetTranslations($locale) 90 | { 91 | TranslationModel::where('locale', $locale)->delete(); 92 | } 93 | 94 | public function exportAllTranslations() 95 | { 96 | $groups = TranslationModel::select(DB::raw('DISTINCT `group`'))->get(); 97 | 98 | foreach ($groups as $group) { 99 | $this->exportTranslations($group->group); 100 | } 101 | } 102 | 103 | public function exportTranslations($group) 104 | { 105 | $translations = TranslationModel::where('group', $group)->get(); 106 | 107 | $tree = []; 108 | 109 | foreach ($translations as $translation) { 110 | array_set($tree[$translation->locale][$translation->group], $translation->key, $translation->value); 111 | } 112 | 113 | foreach ($tree as $locale => $groups) { 114 | if (isset($groups[$group])) { 115 | $translations = $groups[$group]; 116 | $path = resource_path('lang/'.$locale.'/'.$group.'.php'); 117 | $output = "put($path, $output); 119 | } 120 | } 121 | 122 | TranslationModel::where('group', $group)->update(['status' => TranslationModel::STATUS_SAVED]); 123 | } 124 | 125 | /** 126 | * Register routes for laravel-admin. 127 | * 128 | * @return void 129 | */ 130 | public static function registerRoutes() 131 | { 132 | /* @var \Illuminate\Routing\Router $router */ 133 | Route::group(['prefix' => config('admin.route.prefix')], function ($router) { 134 | $attributes = array_merge([ 135 | 'middleware' => config('admin.route.middleware'), 136 | ], static::config('route', [])); 137 | 138 | Route::group($attributes, function ($router) { 139 | 140 | /* @var \Illuminate\Routing\Router $router */ 141 | $router->resource('translations', 'Encore\Admin\Translation\TranslationController'); 142 | }); 143 | }); 144 | } 145 | 146 | /** 147 | * {@inheritdoc} 148 | */ 149 | public static function import() 150 | { 151 | parent::createMenu('Translations', 'translations', 'fa-lang'); 152 | 153 | parent::createPermission('Translations', 'ext.translations', 'translations*'); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/TranslationController.php: -------------------------------------------------------------------------------- 1 | get('export')) { 23 | $translation = new Translation(); 24 | 25 | $translation->exportTranslations($group); 26 | 27 | return response()->json([ 28 | 'status' => true, 29 | 'message' => trans('admin.export_success'), 30 | ]); 31 | } 32 | 33 | return Admin::content(function (Content $content) { 34 | $content->header('Translations'); 35 | $content->description('Translation list.'); 36 | 37 | $content->body($this->grid()); 38 | }); 39 | } 40 | 41 | /** 42 | * Edit interface. 43 | * 44 | * @param $id 45 | * 46 | * @return Content 47 | */ 48 | public function edit($id) 49 | { 50 | return Admin::content(function (Content $content) use ($id) { 51 | $content->header('Edit translations'); 52 | 53 | list($group, $key) = explode('.', $id); 54 | 55 | $locales = $this->getLocaleOptions(); 56 | 57 | $translations = TranslationModel::where(compact('group', 'key'))->get(); 58 | 59 | $content->body(view('laravel-admin-translations::edit', compact('translations', 'locales', 'id', 'group', 'key'))); 60 | }); 61 | } 62 | 63 | public function update($id, Request $request) 64 | { 65 | list($group, $key) = explode('.', $id); 66 | 67 | $translations = TranslationModel::where(compact('group', 'key'))->get(); 68 | 69 | foreach ($request->input('values') as $id => $item) { 70 | if (is_int($id)) { 71 | $model = $translations->find($id); 72 | } else { 73 | $model = new TranslationModel(); 74 | $model->group = $group; 75 | $model->key = $key; 76 | } 77 | 78 | if (empty($model)) { 79 | continue; 80 | } 81 | 82 | if ($item['_remove_'] == 1) { 83 | $model->delete(); 84 | continue; 85 | } 86 | 87 | $model->locale = $item['locale']; 88 | $model->value = $item['value']; 89 | 90 | $model->save(); 91 | } 92 | 93 | admin_toastr(trans('admin.update_succeeded')); 94 | 95 | return redirect(route('translations.index')); 96 | } 97 | 98 | /** 99 | * Create interface. 100 | * 101 | * @return Content 102 | */ 103 | public function create() 104 | { 105 | return Admin::content(function (Content $content) { 106 | $content->header('Create new translation'); 107 | 108 | $form = new \Encore\Admin\Widgets\Form(compact('group', 'key')); 109 | 110 | $form->select('group')->options($this->getGroupOptions()); 111 | $form->text('key'); 112 | 113 | $form->divider(); 114 | 115 | $form->hasMany('locales', function (Form\NestedForm $form) { 116 | $form->select('locale', 'Locale')->options($this->getLocaleOptions()); 117 | $form->textarea('value', 'Text'); 118 | }); 119 | 120 | $form->action(route('translations.store')); 121 | 122 | $content->body(new Box('', $form)); 123 | }); 124 | } 125 | 126 | public function store(Request $request) 127 | { 128 | $translation = new Translation(); 129 | 130 | foreach ($request->input('locales') as $item) { 131 | $translation->importTranslation( 132 | $request->input('key'), 133 | $item['value'], 134 | $item['locale'], 135 | $request->input('group') 136 | ); 137 | } 138 | 139 | admin_toastr(trans('admin.save_succeeded')); 140 | 141 | return redirect(route('translations.index')); 142 | } 143 | 144 | public function grid() 145 | { 146 | return Admin::grid(TranslationModel::class, function (Grid $grid) { 147 | $grid->model()->groupBy(['group', 'key'])->select(['id', 'group', 'key', DB::raw('GROUP_CONCAT(status SEPARATOR \',\') as status'), DB::raw('GROUP_CONCAT(DISTINCT CONCAT(locale,\'###\',value) ORDER BY locale ASC SEPARATOR \'|||\') as value'), 'created_at', 'updated_at']); 148 | 149 | $grid->column('usage')->display(function () { 150 | return "trans('{$this->group}.{$this->key}')"; 151 | }); 152 | 153 | $grid->value('Locale : Text')->display(function ($value) { 154 | $html = '
    '; 155 | 156 | foreach (explode('|||', $value) as $value) { 157 | list($locale, $value) = explode('###', $value); 158 | $html .= "
    $locale:
    $value
    "; 159 | } 160 | 161 | return $html.'
    '; 162 | }); 163 | 164 | $grid->group(); 165 | $grid->key(); 166 | 167 | $grid->status()->display(function ($status) { 168 | $status = explode(',', $status); 169 | 170 | $changed = in_array(TranslationModel::STATUS_CHANGED, $status); 171 | 172 | return $changed ? 'changed' 173 | : 'saved'; 174 | }); 175 | 176 | $grid->updated_at(); 177 | 178 | $grid->filter(function (Grid\Filter $filter) { 179 | $filter->disableIdFilter(); 180 | 181 | $filter->equal('group')->select($this->getGroupOptions()); 182 | $filter->equal('key'); 183 | $filter->equal('locale')->select($this->getLocaleOptions()); 184 | }); 185 | 186 | $grid->actions(function (Grid\Displayers\Actions $actions) { 187 | $actions->setKey($this->row->group.'.'.$this->row->key); 188 | }); 189 | 190 | $grid->tools(function (Grid\Tools $tools) { 191 | $tools->append(new ExportButton()); 192 | }); 193 | 194 | $grid->disableExport(); 195 | $grid->disableRowSelector(); 196 | }); 197 | } 198 | 199 | /** 200 | * Make a form builder. 201 | * 202 | * @return Form 203 | */ 204 | protected function form() 205 | { 206 | return Admin::form(TranslationModel::class, function (Form $form) { 207 | $form->display('id', 'ID'); 208 | 209 | $form->text('group'); 210 | $form->text('key'); 211 | 212 | $form->divider(); 213 | 214 | $form->select('locale')->options($this->getLocaleOptions())->default(config('app.locale')); 215 | $form->textarea('value'); 216 | 217 | $form->divider(); 218 | 219 | $form->select('locale')->options($this->getLocaleOptions())->default(config('app.locale')); 220 | $form->textarea('value'); 221 | 222 | $form->divider(); 223 | 224 | $form->display('created_at', 'Created At'); 225 | $form->display('updated_at', 'Updated At'); 226 | }); 227 | } 228 | 229 | public function destroy($id) 230 | { 231 | list($group, $key) = explode('.', $id); 232 | 233 | if (TranslationModel::where(compact('group', 'key'))->delete()) { 234 | return response()->json([ 235 | 'status' => true, 236 | 'message' => trans('admin.delete_succeeded'), 237 | ]); 238 | } else { 239 | return response()->json([ 240 | 'status' => false, 241 | 'message' => trans('admin.delete_failed'), 242 | ]); 243 | } 244 | } 245 | 246 | protected function getLocaleOptions() 247 | { 248 | $locales = Translation::getLocales(); 249 | 250 | return array_combine($locales, $locales); 251 | } 252 | 253 | protected function getGroupOptions() 254 | { 255 | $groups = Translation::getGroups(); 256 | 257 | return array_combine($groups, $groups); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /resources/views/edit.blade.php: -------------------------------------------------------------------------------- 1 | 27 |
    28 |
    29 |
    30 |
    31 |

    trans('{{$id}}')

    32 |
    33 | 36 |
    37 |
    38 |
    39 |
    40 |
    41 |
    42 | 43 |
    44 |
    45 | 46 | 47 |
    48 |
    49 |
    50 |
    51 | 52 |
    53 |
    54 | 55 | 56 |
    57 |
    58 |
    59 | 60 |
    61 |

    Values

    62 |
    63 |
    64 |
    65 | 66 |
    67 |
    68 | 69 | @foreach($translations as $translation) 70 |
    71 |
    72 | 73 |
    74 | 75 | 80 |
    81 |
    82 | 83 |
    84 | 85 |
    86 | 87 |
    88 |
    89 | 90 | 91 | 92 |
    93 | 94 |
    95 |
     {{ trans('admin.remove') }}
    96 |
    97 |
    98 |
    99 |
    100 | @endforeach 101 | 102 |
    103 | 104 |
    105 | 106 |
    107 |
     {{ trans('admin.new') }}
    108 |
    109 |
    110 | 111 |
    112 | 113 | 114 | 115 |
    116 | 117 | 118 | 134 |
    135 |
    136 |
    137 |
    138 |
    139 | 140 | --------------------------------------------------------------------------------