From 7a4bd49bb61cc24817d3ab1c7bfe496d9029583f Mon Sep 17 00:00:00 2001 From: Vladimir Schneider Date: Sat, 18 Jul 2015 19:23:44 -0400 Subject: [PATCH] add handling of nested directories under lang and soft delete/undelete of translations --- composer.json | 15 +- public/css/translations.css | 191 +++++++++++++ readme.md | 21 +- .../Console/CleanCommand.php | 6 +- .../Console/ExportCommand.php | 6 +- .../Console/FindCommand.php | 6 +- .../Console/ImportCommand.php | 6 +- .../Console/ResetCommand.php | 6 +- .../TranslationManager/Controller.php | 103 +++++-- .../TranslationManager/Manager.php | 83 ++++-- src/Vsch/TranslationManager/ManagerNested.php | 264 ++++++++++++++++++ .../ManagerServiceProvider.php | 8 +- .../TranslationManager/Models/Translation.php | 2 +- .../TranslationServiceProvider.php | 4 +- .../TranslationManager/Translator.php | 17 +- src/lang/en/messages.php | 140 +++++++--- src/lang/ru/messages.php | 140 +++++++--- ...72305_add_deleted_flag_to_translations.php | 37 +++ src/views/index.php | 12 +- src/views/localized.index.blade.php | 26 +- src/views/localized.search.php | 6 +- src/views/search.php | 2 +- 22 files changed, 915 insertions(+), 186 deletions(-) create mode 100644 public/css/translations.css rename src/{Barryvdh => Vsch}/TranslationManager/Console/CleanCommand.php (82%) rename src/{Barryvdh => Vsch}/TranslationManager/Console/ExportCommand.php (88%) rename src/{Barryvdh => Vsch}/TranslationManager/Console/FindCommand.php (84%) rename src/{Barryvdh => Vsch}/TranslationManager/Console/ImportCommand.php (88%) rename src/{Barryvdh => Vsch}/TranslationManager/Console/ResetCommand.php (83%) rename src/{Barryvdh => Vsch}/TranslationManager/Controller.php (87%) rename src/{Barryvdh => Vsch}/TranslationManager/Manager.php (88%) create mode 100644 src/Vsch/TranslationManager/ManagerNested.php rename src/{Barryvdh => Vsch}/TranslationManager/ManagerServiceProvider.php (90%) rename src/{Barryvdh => Vsch}/TranslationManager/Models/Translation.php (91%) rename src/{Barryvdh => Vsch}/TranslationManager/TranslationServiceProvider.php (88%) rename src/{Barryvdh => Vsch}/TranslationManager/Translator.php (93%) mode change 100644 => 100755 src/lang/en/messages.php mode change 100644 => 100755 src/lang/ru/messages.php create mode 100644 src/migrations/2015_07_17_172305_add_deleted_flag_to_translations.php diff --git a/composer.json b/composer.json index 4fb4c33..0cee07f 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,13 @@ { - "name": "barryvdh/laravel-translation-manager", + "name": "vsch/laravel-translation-manager", "description": "Manage Laravel Translations", "keywords": ["laravel", "translations", "translator"], "license": "MIT", "authors": [ + { + "name": "Vladimir Schneider", + "email": "vladimir.schneider@gmail.com" + }, { "name": "Barry vd. Heuvel", "email": "barryvdh@gmail.com" @@ -16,12 +20,13 @@ "symfony/finder": "~2.3" }, "autoload": { + "psr-0": { + "Vsch\\TranslationManager\\": "gorhill/p-hp-fine-diff/src", + "Vsch\\TranslationManager\\": "src/" + }, "classmap": [ "src/migrations" - ], - "psr-0": { - "Barryvdh\\TranslationManager\\": "src/" - } + ] }, "minimum-stability": "stable" } diff --git a/public/css/translations.css b/public/css/translations.css new file mode 100644 index 0000000..28966d0 --- /dev/null +++ b/public/css/translations.css @@ -0,0 +1,191 @@ +.editable-click, +a.editable-click, +a.editable-click:hover { + text-decoration: none; + border-bottom: dashed 1px #0088cc; +} + +.translation-manager .editable-click, +.translation-manager a.editable-click, +.translation-manager a.editable-click:hover { + text-decoration: none; + border-bottom: none; +} + +h1 .editable-click, h1 a.editable-click, h1 a.editable-click:hover { + text-decoration: none; + border-bottom: none; + border-bottom: dashed 1px #0088cc; +} + +h2 .editable-click, h2 a.editable-click, h2 a.editable-click:hover { + text-decoration: none; + border-bottom: none; + border-bottom: dashed 1px #0088cc; +} + +h3 .editable-click, h3 a.editable-click, h3 a.editable-click:hover { + text-decoration: none; + border-bottom: none; + border-bottom: dashed 1px #0088cc; +} + +h4 .editable-click, h4 a.editable-click, h4 a.editable-click:hover { + text-decoration: none; + border-bottom: none; + border-bottom: dashed 1px #0088cc; +} + +h5 .editable-click, h5 a.editable-click, h5 a.editable-click:hover { + text-decoration: none; + border-bottom: none; + border-bottom: dashed 1px #0088cc; +} + +h6 .editable-click, h6 a.editable-click, h6 a.editable-click:hover { + text-decoration: none; + border-bottom: none; + border-bottom: dashed 1px #0088cc; +} + +.translation-manager h1 .editable-click, h1 a.editable-click, h1 a.editable-click:hover { + text-decoration: none; + border-bottom: none; + border-bottom: dashed 1px #FF00cc !important; +} + +.translation-manager h2 .editable-click, h2 a.editable-click, h2 a.editable-click:hover { + text-decoration: none; + border-bottom: none; + border-bottom: dashed 1px #FF00cc !important; +} + +.translation-manager h3 .editable-click, h3 a.editable-click, h3 a.editable-click:hover { + text-decoration: none; + border-bottom: none; + border-bottom: dashed 1px #FF00cc !important; +} + +.translation-manager h4 .editable-click, h4 a.editable-click, h4 a.editable-click:hover { + text-decoration: none; + border-bottom: none; + border-bottom: dashed 1px #FF00cc !important; +} + +.translation-manager h5 .editable-click, h5 a.editable-click, h5 a.editable-click:hover { + text-decoration: none; + border-bottom: none; + border-bottom: dashed 1px #FF00cc !important; +} + +.translation-manager h6 .editable-click, h6 a.editable-click, h6 a.editable-click:hover { + text-decoration: none; + border-bottom: none; + border-bottom: dashed 1px #FF00cc !important; +} + +.table.translation-stats > thead > tr > th, +.table.translation-stats > tbody > tr > td { + margin: 0 0 0 !important; + padding: 2px 6px 2px 6px; +} + +.table.translation-stats > thead > tr > th.missing +.table.translation-stats > tbody > tr > td.changed { + text-align: right; + padding-right: 8px; +} + +.table.translation-stats > tbody > tr > td.group { + text-align: left; + font-weight: bold; + padding-left: 8px; +} + +.table.translation-stats > thead > tr > th.deleted, +.table.translation-stats > tbody > tr > td.group.deleted > a +{ + color: #3399f3; +} + +.table.translation-stats > thead > tr > th.missing, +.table.translation-stats > tbody > tr > td.group.missing > a { + color: #d00030; +} + +.table.translation-stats > thead > tr > th.changed, +.table.translation-stats > tbody > tr > td.group.changed > a { + color: #00A030; +} + +textarea.editable-input { + width: 600px !important; + display: block !important; +} + +.editable-container.editable-inline { + display: block !important; + width: 400px !important; +} + +.editable-container .editable-error-block { + display: block !important; + max-width: 600px !important; +} + +.editable-buttons .editable-translate { + margin-left: 1px; +} + +.table-translations tr.editing { + backround-color: rgba(255, 255, 100, .3); +} + +.table-striped > tbody > tr.editing:nth-of-type(odd) { + background-color: #f7f9da; +} + +.table-striped > tbody > tr.editing:nth-of-type(even) { + background-color: #fffdd9; +} + +/*.table-translations tr.deleted-translation {*/ +/*text-decoration: line-through;*/ +/*}*/ + +.table-translations tr.deleted-translation { + backround-color: rgba(255, 130, 147, 0.31); +} + +.table-striped > tbody > tr.deleted-translation:nth-of-type(odd) { + background-color: #f5e1e3; +} + +.table-striped > tbody > tr.deleted-translation:nth-of-type(even) { + background-color: #ffebec; +} + +.table-translations tr.editing.deleted-translation { + backround-color: rgba(255, 136, 77, 0.41); +} + +.table-striped > tbody > tr.editing.deleted-translation:nth-of-type(odd) { + background-color: #f5d3c4; +} + +.table-striped > tbody > tr.editing.deleted-translation:nth-of-type(even) { + background-color: #ffdccc; +} + +a.status-1 { + font-weight: bold; +} + +del { + color: #C80010; +} + +ins { + color: #108030; +} + diff --git a/readme.md b/readme.md index 62512c6..82f64a3 100644 --- a/readme.md +++ b/readme.md @@ -16,26 +16,26 @@ This way, translations can be saved in git history and no overhead is introduced ## Installation -Require this package in your composer.json and run composer update (or run `composer require barryvdh/laravel-translation-manager:*` directly): +Require this package in your composer.json and run composer update (or run `composer require vsch/laravel-translation-manager:*` directly): - "barryvdh/laravel-translation-manager": "0.1.x" + "vsch/laravel-translation-manager": "0.1.x" After updating composer, add the ServiceProvider to the providers array in app/config/app.php - 'Barryvdh\TranslationManager\ManagerServiceProvider', + 'Vsch\TranslationManager\ManagerServiceProvider', You need to run the migrations for this package - $ php artisan migrate --package="barryvdh/laravel-translation-manager" + $ php artisan migrate --package="vsch/laravel-translation-manager" -You need to publish the config file for this package. This will add the file `app/config/packages/barryvdh/laravel-translation-manager/config.php`, where you can configure this package. +You need to publish the config file for this package. This will add the file `app/config/packages/vsch/laravel-translation-manager/config.php`, where you can configure this package. - $ php artisan config:publish barryvdh/laravel-translation-manager + $ php artisan config:publish vsch/laravel-translation-manager You have to add the Controller to your routes.php, so you can set your own url/filters. Route::group(array('before' => 'auth_admin'), function() { - Route::controller('translations', 'Barryvdh\TranslationManager\Controller'); + Route::controller('translations', 'vsch\TranslationManager\Controller'); }); This example will make the translation manager available at `http://yourdomain.com/translations` @@ -91,16 +91,15 @@ The reset command simply clears all translation in the database, so you can star $ php artisan translations:reset - - -### Detect missing translations +### Detect missing translations and enable in-place translation editing Most translations can be found by using the Find command (see above), but in case you have dynamic keys (variables/automatic forms etc), it can be helpful to 'listen' to the missing translations. To detect missing translations, we can swap the Laravel TranslationServicepProvider with a custom provider. In your config/app.php, comment out the original TranslationServiceProvider and add the one from this package: //'Illuminate\Translation\TranslationServiceProvider', - 'Barryvdh\TranslationManager\TranslationServiceProvider', + 'Vsch\TranslationManager\TranslationServiceProvider', + 'Vsch\TranslationManager\ManagerServiceProvider', This will extend the Translator and will create a new database entry, whenever a key is not found, so you have to visit the pages that use them. This way it shows up in the webinterface and can be edited and later exported. diff --git a/src/Barryvdh/TranslationManager/Console/CleanCommand.php b/src/Vsch/TranslationManager/Console/CleanCommand.php similarity index 82% rename from src/Barryvdh/TranslationManager/Console/CleanCommand.php rename to src/Vsch/TranslationManager/Console/CleanCommand.php index 9102f23..ff8652e 100644 --- a/src/Barryvdh/TranslationManager/Console/CleanCommand.php +++ b/src/Vsch/TranslationManager/Console/CleanCommand.php @@ -1,6 +1,6 @@ - noEditTrans('laravel-translation-manager::messages.choose-group')) + $groups->lists('group', 'group'); $numChanged = Translation::where('group', $group)->where('status', Translation::STATUS_CHANGED)->count(); - $allTranslations = Translation::where('group', $group)->orderBy('key', 'asc')->get(); + // to allow proper handling of nested directory structure we need to copy the keys for the group for all missing + // translations, otherwise we don't know what the group and key looks like. + //$allTranslations = Translation::where('group', $group)->orderBy('key', 'asc')->get(); + $allTranslations = Translation::hydrateRaw(<< 0 +WHERE lcs.total < mx.total_keys OR lcs.changed > 0 OR lcs.deleted > 0 SQL ); @@ -198,11 +222,14 @@ function getIndex($group = null) $item = $summary[$stat->group] = new \stdClass(); $item->missing = ''; $item->changed = ''; + $item->deleted = ''; $item->group = $stat->group; } + $item = $summary[$stat->group]; if ($stat->missing) $item->missing .= $stat->locale . ":" . $stat->missing . " "; if ($stat->changed) $item->changed .= $stat->locale . ":" . $stat->changed . " "; + if ($stat->deleted) $item->deleted .= $stat->locale . ":" . $stat->deleted . " "; } // get mismatches @@ -340,7 +367,7 @@ function getSearch() $translations = DB::select(<<where('key', $key . trim($suffix))->delete(); + //Translation::where('group', $group)->where('key', $key . trim($suffix))->delete(); + $result = DB::update(<<where('key', $key)->delete(); + //Translation::where('group', $group)->where('key', $key)->delete(); + $result = DB::update(<<manager->getConfig('exclude_groups')) && $this->manager->getConfig('delete_enabled')) { - Translation::where('group', $group)->where('key', $key)->delete(); + //Translation::where('group', $group)->where('key', $key)->delete(); + $result = DB::update(<< 'ok'); + } + + public + function postUndelete($group, $key) + { + if (!in_array($group, $this->manager->getConfig('exclude_groups')) && $this->manager->getConfig('delete_enabled')) + { + //Translation::where('group', $group)->where('key', $key)->delete(); + $result = DB::update(<< 'ok'); } @@ -699,7 +752,11 @@ function keyOp($group, $op = 'preview') if (!empty($to_delete)) { $to_delete = $to_delete[0]->ids; - if ($to_delete) DB::delete("DELETE FROM ltm_translations WHERE id IN ($to_delete)"); + if ($to_delete) + { + //DB::delete("DELETE FROM ltm_translations WHERE id IN ($to_delete)"); + DB::update("UPDATE ltm_translations SET is_deleted = 1 WHERE id IN ($to_delete)"); + } } DB::update("UPDATE ltm_translations SET `group` = ?, `key` = ?, status = 1 WHERE id = ?" @@ -708,7 +765,8 @@ function keyOp($group, $op = 'preview') } elseif ($op === 'delete') { - DB::delete("DELETE FROM ltm_translations WHERE id IN ($rowids)"); + //DB::delete("DELETE FROM ltm_translations WHERE id IN ($rowids)"); + DB::update("UPDATE ltm_translations SET is_deleted = 1 WHERE is_deleted = 0 AND id IN ($rowids)"); } elseif ($op === 'copy') { @@ -725,7 +783,11 @@ function keyOp($group, $op = 'preview') if (!empty($to_delete)) { $to_delete = $to_delete[0]->ids; - if ($to_delete) DB::delete("DELETE FROM ltm_translations WHERE id IN ($to_delete)"); + if ($to_delete) + { + //DB::delete("DELETE FROM ltm_translations WHERE id IN ($to_delete)"); + DB::update("UPDATE ltm_translations SET is_deleted = 1 WHERE id IN ($to_delete)"); + } } DB::insert($sql = <<manager->truncateTranslations($group); $counter = $this->manager->importTranslations($group !== '*' ? true : $replace, false, $group === '*' ? null : [$group]); return Response::json(array('status' => 'ok', 'counter' => $counter)); diff --git a/src/Barryvdh/TranslationManager/Manager.php b/src/Vsch/TranslationManager/Manager.php similarity index 88% rename from src/Barryvdh/TranslationManager/Manager.php rename to src/Vsch/TranslationManager/Manager.php index f9e1196..d856a8a 100644 --- a/src/Barryvdh/TranslationManager/Manager.php +++ b/src/Vsch/TranslationManager/Manager.php @@ -1,19 +1,19 @@ -files->files($langPath); $package = $namespace ? $namespace . '::' : ''; + + // handle nested language definition directories + $directories = $this->files->directories($langPath); + foreach ($directories as $dir) + { + $this->importTranslationLocale($replace, $locale, $dir, $namespace, $groups); + } + + $files = $this->files->files($langPath); foreach ($files as $file) { $info = pathinfo($file); - $group = $info['filename']; + $group = self::calculateGroup($info, $namespace, $locale); if (in_array($package . $group, $this->config()['exclude_groups']) || ($groups && !in_array($package . $group, $groups))) { continue; } - $translations = array_dot(\Lang::getLoader()->load($locale, $group, $namespace)); + $translations = array_dot(\Lang::getLoader()->load($locale, str_replace(".", "/", $group), $namespace)); $dbTranslations = $this->translation->hydrateRaw(<<value = $value; } + $translation->is_deleted = 0; $translation->saved_value = $value; $newStatus = ($translation->value === $translation->saved_value ? Translation::STATUS_SAVED @@ -460,6 +491,23 @@ function formatForExport($trans, $indent = 0) return $text; } + public + function makeDirPath($path) + { + $directories = explode("/", $path); + $filename = array_pop($directories); + $dirpath = "/"; + // Build path and create dirrectories if needed + foreach ($directories as $directory) + { + $dirpath .= $directory . "/"; + if (!$this->files->exists($dirpath)) + { + $this->files->makeDirectory($dirpath); + } + } + } + public function exportTranslations($group, $recursing = 0) { @@ -477,7 +525,6 @@ function exportTranslations($group, $recursing = 0) ->where('status', '<>', Translation::STATUS_SAVED) ->where('group', '=', $group) ->get(['group', 'key', 'locale', 'saved_value']); - } else { @@ -485,11 +532,9 @@ function exportTranslations($group, $recursing = 0) UPDATE ltm_translations SET saved_value = value, status = ? WHERE (saved_value <> value || status <> ?) SQL , [Translation::STATUS_SAVED_CACHED, Translation::STATUS_SAVED]); - $translations = $this->translation->query() ->where('status', '<>', Translation::STATUS_SAVED) ->get(['group', 'key', 'locale', 'saved_value']); - } /* @var $translations Collection */ @@ -507,6 +552,9 @@ function exportTranslations($group, $recursing = 0) if ($group == '*') $this->exportAllTranslations(1); + $this->translation->getConnection()->affectingStatement("DELETE FROM ltm_translations WHERE is_deleted = 1 AND `group` = ?" + , [$group]); + $this->clearCache($group); $tree = $this->makeTree(Translation::where('group', $group)->whereNotNull('value')->orderby('key')->get()); @@ -516,28 +564,31 @@ function exportTranslations($group, $recursing = 0) if (isset($groups[$group])) { $translations = $groups[$group]; - if (strpos($group, '::') !== false) { // package group $packgroup = explode('::', $group, 2); $package = array_shift($packgroup); $packgroup = array_shift($packgroup); - $path = $this->app->make('path') . '/lang/packages/' . $locale . '/' . $package . '/' . $packgroup . '.php'; + $path = $this->app->make('path') . '/lang/packages/' . $locale . '/' . $package . '/' . str_replace(".","/", $packgroup) . '.php'; } else { - $path = $this->app->make('path') . '/lang/' . $locale . '/' . $group . '.php'; + $path = $this->app->make('path') . '/lang/' . $locale . '/' . str_replace(".","/", $group) . '.php'; } $output = "formatForExport($translations) . ";\n"; + $this->makeDirPath($path); $this->files->put($path, $output); } } if (!$inDatabasePublishing) { - Translation::where('group', $group)->update(array('status' => Translation::STATUS_SAVED, 'saved_value' => (new Expression('value')))); + Translation::where('group', $group)->update(array( + 'status' => Translation::STATUS_SAVED, + 'saved_value' => (new Expression('value')) + )); } } } @@ -570,7 +621,7 @@ function truncateTranslations($group = '*') } else { - DB::statement("DELETE FROM ltm_translations WHERE `group` = ?", [$group]); + $this->translation->getConnection()->affectingStatement("DELETE FROM ltm_translations WHERE `group` = ?", [$group]); } } diff --git a/src/Vsch/TranslationManager/ManagerNested.php b/src/Vsch/TranslationManager/ManagerNested.php new file mode 100644 index 0000000..ce5c590 --- /dev/null +++ b/src/Vsch/TranslationManager/ManagerNested.php @@ -0,0 +1,264 @@ +app = $app; + $this->files = $files; + $this->events = $events; + $this->config = $app['config']['laravel-translation-manager::config']; + } + + public + function missingKey($namespace, $group, $key) + { + if (!in_array($group, $this->config['exclude_groups'])) + { + Translation::firstOrCreate(array( + 'locale' => $this->app['config']['app.locale'], + 'group' => $group, + 'key' => $key, + )); + } + } + + public + function importTranslations($replace = false) + { + $counter = 0; + foreach ($this->files->directories($this->app->make('path') . '/lang') as $langPath) + { + $locale = basename($langPath); + $counter += $this->importDirectory($langPath, $replace, $locale, $counter); + } + return $counter; + } + + /** + * @param $replace + * @param $files + * @param $locale + * @param $counter + * + * @return mixed + */ + public + function importDirectory($directory, $replace, $locale) + { + $counter = 0; + $directories = $this->files->directories($directory); + foreach ($directories as $dir) + { + $counter += $this->importDirectory($dir, $replace, $locale); + } + $files = $this->files->files($directory); + foreach ($files as $file) + { + $info = pathinfo($file); + $group = $this->calculateGroup($info, $locale); + if (in_array($group, $this->config['exclude_groups'])) + { + continue; + } + $translations = array_dot(\Lang::getLoader()->load($locale, str_replace(".", "/", $group))); + foreach ($translations as $key => $value) + { + $value = (string)$value; + $translation = Translation::firstOrNew(array( + 'locale' => $locale, + 'group' => $group, + 'key' => $key, + )); + // Check if the database is different then the files + $newStatus = $translation->value === $value ? Translation::STATUS_SAVED : Translation::STATUS_CHANGED; + if ($newStatus !== (int)$translation->status) + { + $translation->status = $newStatus; + } + // Only replace when empty, or explicitly told so + if ($replace || !$translation->value) + { + $translation->value = $value; + } + $translation->save(); + $counter++; + } + } + return $counter; + } + + private + function calculateGroup($info, $locale) + { + $dirname = $info["dirname"]; + $filename = $info["filename"]; + if ($pos = strpos($dirname, "/app/lang/$locale/")) + { + $base = substr($dirname, $pos + strlen("/app/lang/$locale/")); + $base = str_replace("/", ".", $base); + } + else + { + return $filename; + } + return "$base.$filename"; + } + + public + function findTranslations($path = null) + { + $path = $path ?: $this->app['path']; + $keys = array(); + $functions = array( + 'trans', + 'trans_choice', + 'Lang::get', + 'Lang::choice', + 'Lang::trans', + 'Lang::transChoice', + '@lang', + '@choice' + ); + $pattern = // See http://regexr.com/392hu + "(" . implode('|', $functions) . ")" . // Must start with one of the functions + "\(" . // Match opening parenthese + "[\'\"]" . // Match " or ' + "(" . // Start a new group to match: + "[a-zA-Z0-9_-]+" . // Must start with group + "([.][^\1)]+)+" . // Be followed by one or more items/keys + ")" . // Close group + "[\'\"]" . // Closing quote + "[\),]"; // Close parentheses or new parameter + // Find all PHP + Twig files in the app folder, except for storage + $finder = new Finder(); + $finder->in($path)->exclude('storage')->name('*.php')->name('*.twig')->files(); + /** @var \Symfony\Component\Finder\SplFileInfo $file */ + foreach ($finder as $file) + { + // Search the current file for the pattern + if (preg_match_all("/$pattern/siU", $file->getContents(), $matches)) + { + // Get all matches + foreach ($matches[2] as $key) + { + $keys[] = $key; + } + } + } + // Remove duplicates + $keys = array_unique($keys); + // Add the translations to the database, if not existing. + foreach ($keys as $key) + { + // Split the group and item + list($group, $item) = explode('.', $key, 2); + $this->missingKey('', $group, $item); + } + // Return the number of found translations + return count($keys); + } + + public + function exportTranslations($group) + { + if (!in_array($group, $this->config['exclude_groups'])) + { + if ($group == '*') + return $this->exportAllTranslations(); + $tree = $this->makeTree(Translation::where('group', $group)->whereNotNull('value')->get()); + foreach ($tree as $locale => $groups) + { + if (isset($groups[$group])) + { + $translations = $groups[$group]; + $output = "saveGroupFile($group, $locale, $output); + } + } + Translation::where('group', $group)->whereNotNull('value')->update(array('status' => Translation::STATUS_SAVED)); + } + } + + public + function saveGroupFile($group, $locale, $output) + { + $group = str_replace(".", "/", $group); + $directories = explode("/", $group); + $path = $this->app->make('path') . '/lang/' . $locale . '/'; + // Build path and create dirrectories if needed + for ($i = 0; $i < (count($directories) - 1); $i++) + { + $path .= $directories[$i] . "/"; + if (!$this->files->exists($path)) + { + $this->files->makeDirectory($path); + } + } + $path .= $directories[count($directories) - 1] . '.php'; + $this->files->put($path, $output); + } + + public + function exportAllTranslations() + { + $groups = Translation::whereNotNull('value')->select(DB::raw('DISTINCT `group`'))->get('group'); + foreach ($groups as $group) + { + $this->exportTranslations($group->group); + } + } + + public + function cleanTranslations() + { + Translation::whereNull('value')->delete(); + } + + public + function truncateTranslations() + { + Translation::truncate(); + } + + protected + function makeTree($translations) + { + $array = array(); + foreach ($translations as $translation) + { + array_set($array[$translation->locale][$translation->group], $translation->key, $translation->value); + } + return $array; + } + + public + function getConfig($key = null) + { + if ($key == null) + { + return $this->config; + } + else + { + return $this->config[$key]; + } + } +} diff --git a/src/Barryvdh/TranslationManager/ManagerServiceProvider.php b/src/Vsch/TranslationManager/ManagerServiceProvider.php similarity index 90% rename from src/Barryvdh/TranslationManager/ManagerServiceProvider.php rename to src/Vsch/TranslationManager/ManagerServiceProvider.php index 3eda390..c9de1c5 100644 --- a/src/Barryvdh/TranslationManager/ManagerServiceProvider.php +++ b/src/Vsch/TranslationManager/ManagerServiceProvider.php @@ -1,4 +1,4 @@ -package('barryvdh/laravel-translation-manager'); + $this->package('vsch/laravel-translation-manager'); } /** @@ -33,8 +33,8 @@ function register() { $this->app['translation-manager'] = $this->app->share(function ($app) { - /* @var $manager \Barryvdh\TranslationManager\Manager */ - $manager = $app->make('Barryvdh\TranslationManager\Manager'); + /* @var $manager \Vsch\TranslationManager\Manager */ + $manager = $app->make('Vsch\TranslationManager\Manager'); return $manager; }); diff --git a/src/Barryvdh/TranslationManager/Models/Translation.php b/src/Vsch/TranslationManager/Models/Translation.php similarity index 91% rename from src/Barryvdh/TranslationManager/Models/Translation.php rename to src/Vsch/TranslationManager/Models/Translation.php index e825b1c..edce2fc 100644 --- a/src/Barryvdh/TranslationManager/Models/Translation.php +++ b/src/Vsch/TranslationManager/Models/Translation.php @@ -1,4 +1,4 @@ -setFallback($app['config']['app.fallback_locale']); diff --git a/src/Barryvdh/TranslationManager/Translator.php b/src/Vsch/TranslationManager/Translator.php similarity index 93% rename from src/Barryvdh/TranslationManager/Translator.php rename to src/Vsch/TranslationManager/Translator.php index 13f0ba6..6db6b78 100644 --- a/src/Barryvdh/TranslationManager/Translator.php +++ b/src/Vsch/TranslationManager/Translator.php @@ -1,4 +1,4 @@ -useDB; - list($namespace, $group, $item) = $this->parseKey($key); + list($namespace, $parsed_group, $item) = $this->parseKey($key); + if ($group === null) $group = $parsed_group; + else + { + $item = substr($key, strlen("$group.")); + if ($namespace && $namespace !== '*') $group = substr($group, strlen("$namespace::")); + } + if ($this->manager && $group && $item && !$this->manager->excludedPageEditGroup($group)) { $t = $this->manager->missingKey($namespace, $group, $item, $locale, false, true); @@ -96,14 +103,14 @@ function inPlaceEditLink($t, $withDiff = false, $key = null, $locale = null, $us { if ($withDiff && $diff === '') { - $diff = ($t->saved_value == $t->value ? '' : ($t->saved_value === $t->value ? '' : ' [' . \Barryvdh\TranslationManager\Controller::mb_renderDiffHtml($t->saved_value, $t->value) . ']')); + $diff = ($t->saved_value == $t->value ? '' : ($t->saved_value === $t->value ? '' : ' [' . \Vsch\TranslationManager\Controller::mb_renderDiffHtml($t->saved_value, $t->value) . ']')); } $title = parent::get('laravel-translation-manager::messages.enter-translation'); if ($t->value === null) $t->value = ''; //$t->value = parent::get($key, $replace, $locale); $result = 'key) . '" data-type="textarea" data-pk="' . ($t->id ?: 0) . '" ' - . 'data-url="' . URL::action('Barryvdh\TranslationManager\Controller@postEdit', array($t->group)) . '" ' + . 'data-url="' . URL::action('Vsch\TranslationManager\Controller@postEdit', array($t->group)) . '" ' . 'data-inputclass="editable-input" data-saved_value="' . htmlentities($t->saved_value, ENT_QUOTES, 'UTF-8', false) . '" ' . 'data-title="' . $title . ': [' . $t->locale . '] ' . $t->group . '.' . $t->key . '">' . ($t ? htmlentities($t->value, ENT_QUOTES, 'UTF-8', false) : '') . ' ' diff --git a/src/lang/en/messages.php b/src/lang/en/messages.php old mode 100644 new mode 100755 index 8b5e76c..c380923 --- a/src/lang/en/messages.php +++ b/src/lang/en/messages.php @@ -1,46 +1,102 @@ 'Add Keys', - 'addkeys-placeholder' => 'Add 1 key per line, without the group prefix', - 'changed' => 'Changed', - 'choose-group' => 'Choose a translation group', - 'choose-group-text' => 'Choose a group to display the group translations. If no groups are visible, contact your web-admin.', - 'close' => 'Close', - 'confirm-delete-all' => 'Are you sure you want delete all the translations from the database? Any changes that have not been published to translation files, will be lost.', - 'confirm-find' => 'Are you sure you want to scan your app folder? All found translation keys will be added to the database.', - 'delete-all' => 'Delete All', - 'deleting' => 'Deleting...', - 'done-publishing' => 'Done publishing the translations for group :group.', - 'en' => 'English', - 'en-ru' => 'Yandex en➟ru', - 'enter-translation' => 'Enter translation', - 'export-warning-text' => 'Warning, translations are not visible until they are processed by your web-admin.', - 'find-in-files' => 'Add References', - 'group' => 'Group', - 'import-add' => 'Only add new translations', - 'import-done-head' => 'Done importing, processed', - 'import-done-tail' => 'items. Reload this page to refresh the groups.', - 'import-groups' => 'Import groups', - 'import-replace' => 'Replace existing translations', - 'key' => 'Key', - 'loading' => 'Importing...', - 'locale' => 'Locale', - 'mismatches' => 'Mismatched Translations', - 'missing' => 'Missing', - 'publish' => 'Publish Group', - 'publish-all' => 'Publish All Groups', - 'publishing' => 'Publishing...', - 'ru' => 'Russian', - 'ru-en' => 'Yandex ru➟en', - 'search' => 'Search', - 'search-done-head' => 'Done searching for translations, found', - 'search-done-tail' => 'items.', - 'search-translations' => 'Search Translations', - 'searching' => 'Searching...', - 'stats' => 'Dashboard View', - 'total' => 'Total', - 'translation' => 'Translation', - 'translation-manager' => 'Translation Manager', - 'translations' => 'Translations', + 'addkeys' => 'Add Keys', + 'addkeys-placeholder' => 'Add 1 key per line, without the group prefix', + 'addsuffixes' => 'Set Suffixes', + 'addsuffixes-placeholder' => 'add each key suffixed by lines entered here', + 'changed' => 'Changed', + 'choose-group' => 'Choose a translation group', + 'choose-group-text' => 'Choose a group to display the group translations. If no groups are visible, contact your web-admin.', + 'cleardstkeys' => 'Clear Keys', + 'clearkeys' => 'Clear Keys', + 'clearsrckeys' => 'Clear Keys', + 'clearsuffixes' => 'Clear Suffixes', + 'close' => 'Close', + 'confirm-delete' => <<<'TEXT' +Are you sure you want delete: + +:group + +the translations from the database? Any changes that have not been published to translation files, will be lost. +TEXT +, + 'confirm-delete-all' => <<<'TEXT' +Are you sure you want delete all the translations from the database? + +Any changes that have not been published to translation files, will be lost. +TEXT +, + 'confirm-find' => 'Are you sure you want to scan your app folder? All found translation keys will be added to the database.', + 'copykeys' => 'Copy Keys', + 'delete' => 'Delete', + 'delete-all' => 'Delete All', + 'deleted' => 'Deleted', + 'deletekeys' => 'Delete Keys', + 'deleting' => 'Deleting...', + 'done-publishing' => 'Done publishing the translations for group :group.', + 'done-publishing-all' => 'Done publishing the translations for all groups.', + 'dst-preview' => 'To', + 'dstkey' => 'To', + 'dstkeys' => 'To Keys', + 'dstkeys-placeholder' => 'Add 1 key per line, with or without the group prefix', + 'en' => 'English', + 'en-ru' => 'Yandex en➟ru', + 'enter-translation' => 'Enter translation', + 'export-warning-text' => 'Warning, translations are not visible until they are processed by your web-admin.', + 'find-in-files' => 'Add References', + 'group' => 'Group', + 'import-add' => 'Only add new translations', + 'import-all-done' => 'Done importing, processed :count items. Reload this page to refresh the groups.', + 'import-done-head' => 'Done importing, processed', + 'import-done-tail' => 'items. Reload this page to refresh the groups.', + 'import-group' => 'Import', + 'import-group-done' => 'Done importing group :group, processed :count items. Reload this page to refresh translations.', + 'import-groups' => 'Import all', + 'import-replace' => 'Replace existing translations', + 'key' => 'Key', + 'keyop-count-mustmatch' => 'Number of keys for source and destination must match', + 'keyop-header' => 'Key Operation Results', + 'keyop-header-copy' => 'Copy key operation for :group group:', + 'keyop-header-delete' => 'Delete keys operation from :group group:', + 'keyop-header-move' => 'Move key operation for :group group:', + 'keyop-header-preview' => 'Preview key operation for :group group:', + 'keyop-need-group' => 'Key operations require a group', + 'keyop-need-keys' => 'No keys provided for key operation', + 'keyop-wildcard-mustmatch' => 'Wildcard * character must be the first or last character, and if present must be used on both source and destination keys in the same position.', + 'keyop-wildcard-once' => 'Wildcard * character can only appear once in a key.', + 'keyops-not-authorized' => 'Key operations are not authorized on this server. Contact your web-admin to change this setting.', + 'keys' => 'Keys', + 'loading' => 'Importing...', + 'locale' => 'Locale', + 'mismatches' => 'Mismatched Translations', + 'missing' => 'Missing', + 'missmatched-quotes' => 'mismatched or missing quotes in :string attribute', + 'movekeys' => 'Move Keys', + 'preview' => 'Preview', + 'publish' => 'Publish Group', + 'publish-all' => 'Publish All', + 'publishing' => 'Publishing...', + 'ru' => 'Русский', + 'ru-en' => 'Yandex ru➟en', + 'search' => 'Search', + 'search-done' => 'Done searching for translations, found :count items.', + 'search-done-head' => 'Done searching for translations, found', + 'search-done-tail' => 'items.', + 'search-header' => 'Results found: :count', + 'search-translations' => 'Search Translations', + 'searching' => 'Searching...', + 'src-preview' => 'From', + 'srckey' => 'From', + 'srckeys' => 'From Keys', + 'srckeys-placeholder' => 'Add 1 key per line, with or without the group prefix', + 'stats' => 'Dashboard View', + 'suffixed-keyops' => 'Suffixed Key Operations & Search', + 'suffixes' => 'Suffixes', + 'total' => 'Total', + 'translation' => 'Translation', + 'translation-manager' => 'Translation Manager', + 'translation-ops' => 'Translation Helpers', + 'translations' => 'Translations', + 'wildcard-keyops' => 'Wildcard Key Operations', ); diff --git a/src/lang/ru/messages.php b/src/lang/ru/messages.php old mode 100644 new mode 100755 index fbfca74..c3a0961 --- a/src/lang/ru/messages.php +++ b/src/lang/ru/messages.php @@ -1,46 +1,102 @@ 'Добавить новые ключивые', - 'addkeys-placeholder' => 'Добавляйте по одному ключу на линию беэ префикса гпуппы', - 'changed' => 'Измененны', - 'choose-group' => 'Выберите группу переводов', - 'choose-group-text' => 'Выберите группу переводов для редакции. Если нет групп в списке, контактируйте вашего веб-админа.', - 'close' => 'Закрыть', - 'confirm-delete-all' => 'Вы уверены, что хотите удалить все переводы из базы данных? Любые изменения, которые не были опубликованы в файлы переводов будут потеряны.', - 'confirm-find' => 'Вы уверены, что хотите сканировать папки приложения? Все найденные ключи перевода будут добавлены в базу данных.', - 'delete-all' => 'Удалить Все', - 'deleting' => 'Удаляю...', - 'done-publishing' => 'Сделал публикацию переводов для группы :group.', - 'en' => 'Английский', - 'en-ru' => 'Яндекс en➟ru', - 'enter-translation' => 'Редактируйте перевод', - 'export-warning-text' => 'Предупреждение, переводы не видны, пока они обработанны вашем веб-админом.', - 'find-in-files' => 'Добавить Ссылки', - 'group' => 'Группа', - 'import-add' => 'Только добавлять новые переводы', - 'import-done-head' => 'Сделал импорт, обработанно', - 'import-done-tail' => 'переводов. Перезагрузите страницу, чтобы обновить группы.', - 'import-groups' => 'Импорт групп', - 'import-replace' => 'Заменить существующие переводы', - 'key' => 'Ключ', - 'loading' => 'Импорт...', - 'locale' => 'Язык', - 'mismatches' => 'Не Совподающие Переводы', - 'missing' => 'Отсутствует', - 'publish' => 'Опубликовать Группу', - 'publish-all' => 'Опубликовать Все Группы', - 'publishing' => 'Публикую...', - 'ru' => 'Русский', - 'ru-en' => 'Яндекс ru➟en', - 'search' => 'Поиск', - 'search-done-head' => 'Сделан поиск переводов, найдено', - 'search-done-tail' => 'переводов.', - 'search-translations' => 'Поиск Переводов', - 'searching' => 'Поиск...', - 'stats' => 'Панель Мониторинга', - 'total' => 'В общем', - 'translation' => 'Перевод', - 'translation-manager' => 'Контроль Переводов', - 'translations' => 'Переводы', + 'addkeys' => 'Добавить ключивые', + 'addkeys-placeholder' => 'Добавляйте по одному ключу на строке без префикса группы', + 'addsuffixes' => 'Набор Суффиксов', + 'addsuffixes-placeholder' => 'добавляй каждый ключ с суффиксом строк, указанных здесь', + 'changed' => 'Измененны', + 'choose-group' => 'Выберите группу переводов', + 'choose-group-text' => 'Выберите группу переводов для редакции. Если нет групп в списке, контактируйте вашего веб-админа.', + 'cleardstkeys' => 'Стереть Ключи', + 'clearkeys' => 'Стереть Ключи', + 'clearsrckeys' => 'Стереть Ключи', + 'clearsuffixes' => 'Стереть Суфф.', + 'close' => 'Закрыть', + 'confirm-delete' => <<<'TEXT' +Вы уверены, что хотите удалить переводы + +:group + +из базы данных? Любые изменения, которые не были опубликованы в файлы переводов будут потеряны. +TEXT +, + 'confirm-delete-all' => <<<'TEXT' +Вы уверены, что хотите удалить все переводы из базы данных? + +Любые изменения, которые не были опубликованы в файлы переводов будут потеряны. +TEXT +, + 'confirm-find' => 'Вы уверены, что хотите сканировать папки приложения? Все найденные ключи перевода будут добавлены в базу данных.', + 'copykeys' => 'Скопируй', + 'delete' => 'Удалить', + 'delete-all' => 'Удалить Все', + 'deleted' => 'Удаленные', + 'deletekeys' => 'Удалить Ключи', + 'deleting' => 'Удаляю...', + 'done-publishing' => 'Сделал публикацию переводов для группы :group.', + 'done-publishing-all' => 'Публикация переводов для всех групп завершена.', + 'dst-preview' => 'На', + 'dstkey' => 'На', + 'dstkeys' => 'На Ключи', + 'dstkeys-placeholder' => 'Добавляйте по одному ключу на строке с префиксом или без префикса группы', + 'en' => 'English', + 'en-ru' => 'Яндекс en➟ru', + 'enter-translation' => 'Редактируйте перевод', + 'export-warning-text' => 'Предупреждение, переводы не видны, пока они обработанны вашем веб-админом.', + 'find-in-files' => 'Добавить Ссылки', + 'group' => 'Группа', + 'import-add' => 'Только добавлять новые переводы', + 'import-all-done' => 'Сделан импорт, обработанно :count переводов. Перезагрузите страницу, чтобы обновить группы.', + 'import-done-head' => 'Сделал импорт, обработанно', + 'import-done-tail' => 'переводов. Перезагрузите страницу, чтобы обновить группы.', + 'import-group' => 'Импорт', + 'import-group-done' => 'Сделан импорт группы :group, обработанно :count переводов. Перезагрузите страницу, чтобы обновить переводы.', + 'import-groups' => 'Импорт все', + 'import-replace' => 'Заменить существующие переводы', + 'key' => 'Ключ', + 'keyop-count-mustmatch' => 'Количество ключей для источника и получателя должны совпадать', + 'keyop-header' => 'Результаты Ключевых Операций', + 'keyop-header-copy' => 'Копирования ключей группы :group:', + 'keyop-header-delete' => 'Удаления ключей в группе :group:', + 'keyop-header-move' => 'Перемещения ключей в группе :group:', + 'keyop-header-preview' => 'Пересмотр операции по ключам для группы :group:', + 'keyop-need-group' => 'Ключевые операции требуют группу', + 'keyop-need-keys' => 'Не указали ключей для операции', + 'keyop-wildcard-mustmatch' => 'Подстановочный символ * должен быть первым или последним символом, и если они присутствуют, должны быть использованы на оба источника и в том же положении.', + 'keyop-wildcard-once' => 'Подстановочный символ * может появиться в ключе только один раз.', + 'keyops-not-authorized' => 'Ключевые операции не авторизованы на этом сервере. Обратитесь к веб-админу чтобы изменить этот параметр.', + 'keys' => 'Ключи', + 'loading' => 'Импорт...', + 'locale' => 'Язык', + 'mismatches' => 'Не Совпадающие Переводы', + 'missing' => 'Отсутствует', + 'missmatched-quotes' => 'несоответствующие или отсутствующие кавычки в строковом атрибуте :string', + 'movekeys' => 'Переместить Ключи', + 'preview' => 'Превью', + 'publish' => 'Опубликовать Группу', + 'publish-all' => 'Опубликовать Все', + 'publishing' => 'Публикую...', + 'ru' => 'Русский', + 'ru-en' => 'Яндекс ru➟en', + 'search' => 'Поиск', + 'search-done' => 'Сделан поиск переводов, найдено :count переводов.', + 'search-done-head' => 'Сделан поиск переводов, найдено', + 'search-done-tail' => 'переводов.', + 'search-header' => 'Найдено результатов: :count', + 'search-translations' => 'Поиск Переводов', + 'searching' => 'Поиск...', + 'src-preview' => 'От', + 'srckey' => 'От', + 'srckeys' => 'От Ключей', + 'srckeys-placeholder' => 'Добавляйте по одному ключу на строке с префиксом или без префикса группы', + 'stats' => 'Панель Мониторинга', + 'suffixed-keyops' => 'Ключевых Операций с Суффиксами и Поиск', + 'suffixes' => 'Суффиксы', + 'total' => 'В общем', + 'translation' => 'Перевод', + 'translation-manager' => 'Контроль Переводов', + 'translation-ops' => 'Помощники Перевода', + 'translations' => 'Переводы', + 'wildcard-keyops' => 'Подстановочные Операции С Ключами', ); diff --git a/src/migrations/2015_07_17_172305_add_deleted_flag_to_translations.php b/src/migrations/2015_07_17_172305_add_deleted_flag_to_translations.php new file mode 100644 index 0000000..cc97568 --- /dev/null +++ b/src/migrations/2015_07_17_172305_add_deleted_flag_to_translations.php @@ -0,0 +1,37 @@ +tinyInteger('is_deleted')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public + function down() + { + Schema::table('ltm_translations', function (Blueprint $table) + { + $table->dropColumn('is_deleted'); + }); + } + +} diff --git a/src/views/index.php b/src/views/index.php index 3d6d924..25ed5f7 100644 --- a/src/views/index.php +++ b/src/views/index.php @@ -33,7 +33,7 @@ }); $('.group-select').on('change', function(){ - window.location.href = '/'+$(this).val(); + window.location.href = '/'+$(this).val(); }); @@ -81,19 +81,19 @@ Search -
+
-
+
-
+
@@ -104,7 +104,7 @@ -
+
@@ -136,7 +136,7 @@ - + diff --git a/src/views/localized.index.blade.php b/src/views/localized.index.blade.php index 0e5b316..bc70f20 100644 --- a/src/views/localized.index.blade.php +++ b/src/views/localized.index.blade.php @@ -34,7 +34,7 @@
-