From fc848138d24bbb43b7896299b3c955102a13038b Mon Sep 17 00:00:00 2001 From: Vladimir Schneider Date: Sun, 22 Mar 2015 16:13:23 -0400 Subject: [PATCH] add in page edit capability through translation manager and config for missing key logging using sparse lottery. Fix search to include missing key,locale combinations when the query matches a key. --- scripts/translations-manager.sql | 312 +++++++++--------- .../TranslationManager/Controller.php | 23 +- src/Barryvdh/TranslationManager/Manager.php | 48 ++- .../TranslationManager/Translator.php | 11 +- src/config/config.php | 51 ++- 5 files changed, 275 insertions(+), 170 deletions(-) diff --git a/scripts/translations-manager.sql b/scripts/translations-manager.sql index f9e6dad..4c19d3a 100644 --- a/scripts/translations-manager.sql +++ b/scripts/translations-manager.sql @@ -1,201 +1,215 @@ -DROP FUNCTION IF EXISTS CAPITAL; +DROP FUNCTION IF EXISTS CAPITAL +; + CREATE FUNCTION CAPITAL(input VARCHAR(1024)) RETURNS VARCHAR(1024) DETERMINISTIC BEGIN - DECLARE len INT; - DECLARE i INT; - - SET len = CHAR_LENGTH(input); - SET input = LOWER(input); - SET i = 0; - SET input = CONCAT(LEFT(input, i), UPPER(MID(input, i + 1, 1)), right(input, len - i - 1)); - SET i = i + 1; + DECLARE len INT + ; + + DECLARE i INT + ; + + SET len = CHAR_LENGTH(input) + ; + + SET input = LOWER(input) + ; + + SET i = 0 + ; + + SET input = CONCAT(LEFT(input, i), UPPER(MID(input, i + 1, 1)), right(input, len - i - 1)) + ; + + SET i = i + 1 + ; + WHILE (i < len) DO - SET i = LOCATE(' ', input, i); + SET i = LOCATE(' ', input, i) + ; + IF i = 0 OR i = len THEN - SET i = len; + SET i = len + ; ELSE - SET input = CONCAT(LEFT(input, i), UPPER(MID(input, i + 1, 1)), right(input, len - i - 1)); - SET i = i + 1; - END IF; - END WHILE; + SET input = CONCAT(LEFT(input, i), UPPER(MID(input, i + 1, 1)), right(input, len - i - 1)) + ; - RETURN input; - END; + SET i = i + 1 + ; + END IF + ; + + END WHILE + ; + + RETURN input + ; + + END +; CREATE OR REPLACE VIEW en_translations AS SELECT * FROM ltm_translations - WHERE locale = 'en'; + WHERE locale = 'en' +; CREATE OR REPLACE VIEW ru_translations AS SELECT * FROM ltm_translations - WHERE locale = 'ru'; + WHERE locale = 'ru' +; CREATE OR REPLACE VIEW missing_translations AS - SELECT - ru.id, - ru.group, - ru.key, - ru.value ru_value, - en.value en_value + SELECT ru.id, ru.group, ru.key, ru.value ru_value, en.value en_value FROM ru_translations ru JOIN en_translations en ON ru.`group` = en.`group` AND ru.`key` = en.`key` WHERE 1 = 1 - AND ru.value is null or ru.value = en.value - AND concat(en.`group`, '.', en.`key`) NOT IN ('messages.lang_en', 'messages.lang_ru', 'messages.use-site'); + AND ru.value IS NULL OR ru.value = en.value + AND concat(en.`group`, '.', en.`key`) NOT IN ('messages.lang_en', 'messages.lang_ru', 'messages.use-site') +; SELECT * -FROM missing_translations; +FROM missing_translations +; CREATE OR REPLACE VIEW have_translations AS - SELECT - ru.id, - ru.group, - ru.key, - ru.value ru_value, - en.value en_value + SELECT ru.id, ru.group, ru.key, ru.value ru_value, en.value en_value FROM ru_translations ru JOIN en_translations en ON ru.`group` = en.`group` AND ru.`key` = en.`key` WHERE 1 = 1 - AND ru.value is not null and ru.value <> en.value - AND concat(en.`group`, '.', en.`key`) NOT IN ('messages.lang_en', 'messages.lang_ru', 'messages.use-site'); + AND ru.value IS NOT NULL AND ru.value <> en.value + AND concat(en.`group`, '.', en.`key`) NOT IN ('messages.lang_en', 'messages.lang_ru', 'messages.use-site') +; SELECT * -FROM have_translations; +FROM have_translations +; + +DROP TEMPORARY TABLE IF EXISTS ru_missing_translations +; -DROP TEMPORARY TABLE IF EXISTS ru_missing_translations; CREATE TEMPORARY TABLE ru_missing_translations AS SELECT * - FROM missing_translations; + FROM missing_translations +; + +DROP TEMPORARY TABLE IF EXISTS ru_have_translations +; -DROP TEMPORARY TABLE IF EXISTS ru_have_translations; CREATE TEMPORARY TABLE ru_have_translations AS SELECT * - FROM have_translations; + FROM have_translations +; SELECT * -FROM ru_missing_translations; +FROM ru_missing_translations +; SELECT * -FROM ru_have_translations; +FROM ru_have_translations +; -select * - from ru_missing_translations rm JOIN ru_have_translations rh on rm.key = rh.key;# and rm.en_value = rh.en_value; - - -# SELECT * -# FROM ltm_translations lt JOIN on lt.id = st.id; - -/* select all missing translations that have a pretty good match somewhere else*/ -SELECT - st.id, - st.ru_value, - st.en_value, - ht.en_value, - ht.ru_value -FROM - ru_missing_translations st JOIN ru_have_translations ht - ON st.en_value LIKE BINARY ht.en_value -ORDER BY st.id; - -/* update all missing translations that have a pretty good match somewhere else*/ -UPDATE ltm_translations lt - JOIN ( - SELECT - st.id, - ht.ru_value - FROM - ru_missing_translations st JOIN ru_have_translations ht - ON st.en_value LIKE BINARY ht.en_value - ) tr ON tr.id = lt.id -SET lt.value = tr.ru_value, lt.status = 1; - -/* select all missing translations that have a descent match somewhere else*/ -SELECT - st.id, - st.ru_value, - st.en_value, - ht.en_value ht_en_value, - CAPITAL(ht.ru_value) ht_ru_value -FROM - ru_missing_translations st JOIN ru_have_translations ht - ON /*st.key = ht.key AND*/ st.en_value LIKE BINARY CAPITAL(ht.en_value) -ORDER BY st.id; - -/* update all missing translations that have a pretty good match somewhere else*/ -UPDATE ltm_translations lt - JOIN ( - SELECT - st.id, - ht.ru_value - FROM - ru_missing_translations st JOIN ru_have_translations ht - ON /*st.key = ht.key AND*/ st.en_value LIKE BINARY CAPITAL(ht.en_value) - ) tr ON tr.id = lt.id -SET lt.value = CAPITAL(tr.ru_value), lt.status = 1; - -SELECT - st.id, - st.ru_value, - st.en_value, - ht.en_value ht_en_value, - CAPITAL(ht.ru_value) ht_ru_value +SELECT * +FROM ru_missing_translations rm JOIN ru_have_translations rh ON rm.key = rh.key +; + +# and rm.en_value = rh.en_value; + + +SELECT mt.locale, mt.`group`, mt.`key`, mt.value, ht.value +FROM (SELECT ht.`group`, ht.locale, ht.`key`, ht.value + FROM + (SELECT DISTINCT ht.`key`, ht.locale + FROM ltm_translations ht WHERE `group` = 'page-titles') kt + JOIN ltm_translations ht + ON ht.`group` = 'messages' AND kt.`key` = ht.`key` AND kt.locale = ht.locale) ht + LEFT OUTER JOIN ltm_translations mt + ON mt.`group` = ht.`group` AND mt.`key` = ht.`key` AND mt.locale = ht.locale +; + +## +# get missing keys +SET @group = 'page-titles' +; + +#list missing translations that are in messages. +SELECT kt.`group`, kt.locale, kt.`key`, ht.value FROM - ru_missing_translations st JOIN ru_have_translations ht - ON st.key = ht.key AND st.en_value LIKE CONCAT('%',ht.en_value,'%') -ORDER BY st.id; - -/* update all missing translations that have a pretty good match somewhere else, ignoring case*/ -UPDATE ltm_translations lt - JOIN ( - SELECT - st.id, - ht.ru_value + (SELECT kt.`key`, lt.locale, @group `group` + FROM + (SELECT DISTINCT ht.`key` + FROM ltm_translations ht WHERE `group` LIKE BINARY @group) kt + CROSS JOIN (SELECT DISTINCT ht.locale + FROM ltm_translations ht WHERE `group` LIKE BINARY @group) lt) kt + LEFT JOIN ltm_translations ht + ON ht.`group` = 'messages' AND kt.`key` = ht.`key` AND kt.locale = ht.locale +WHERE NOT exists(SELECT * + FROM ltm_translations lt WHERE lt.locale = kt.locale AND lt.`group` LIKE BINARY kt.`group` AND lt.`key` = kt.`key`) +; + +# copy ones that exist +UPDATE + ltm_translations mt JOIN ltm_translations ht + ON mt.`group` = 'page-titles' AND ht.`group` = 'messages' AND mt.`key` = ht.`key` AND mt.locale = ht.locale +SET + mt.value = ht.value, + mt.status = 1 +WHERE mt.value IS NULL +; + +# now insert missing ones +INSERT INTO ltm_translations + ( + SELECT NULL, 1 status, ht.locale, @group `group`, ht.`key`, ht.value, ht.created_at, ht.updated_at, ht.source, NULL saved_value + FROM + (SELECT kt.`key`, lt.locale, @group `group` FROM - ru_missing_translations st JOIN ru_have_translations ht - ON st.key = ht.key AND st.en_value LIKE CONCAT('%',ht.en_value,'%') - ) tr ON tr.id = lt.id -SET lt.value = CAPITAL(tr.ru_value), lt.status = 1; - -DROP TEMPORARY TABLE IF EXISTS ru_missing_translations; -CREATE TEMPORARY TABLE ru_same_translations - AS SELECT * - FROM missing_translations; - -DROP TEMPORARY TABLE IF EXISTS ru_have_translations; -CREATE TEMPORARY TABLE ru_have_translations - AS SELECT * - FROM have_translations; - + (SELECT DISTINCT ht.`key` + FROM ltm_translations ht WHERE `group` LIKE BINARY @group) kt + CROSS JOIN (SELECT DISTINCT ht.locale + FROM ltm_translations ht WHERE `group` LIKE BINARY @group) lt) kt + LEFT JOIN ltm_translations ht + ON ht.`group` = 'messages' AND kt.`key` = ht.`key` AND kt.locale = ht.locale + WHERE ht.`key` IS NOT NULL AND ht.locale IS NOT NULL + AND NOT exists(SELECT * + FROM ltm_translations lt WHERE lt.locale = kt.locale AND lt.`group` LIKE BINARY kt.`group` AND lt.`key` = kt.`key`) + ) +; + + +# search query that fills in missing locale,key combinations for a group SELECT * -FROM ru_missing_translations; - -SELECT lt.* from ltm_translations lt - JOIN ru_missing_translations st ON st.id = lt.id; - -# finally, set the translations that are the same to NULL so they show up as missing -UPDATE ltm_translations lt - JOIN ru_missing_translations st ON st.id = lt.id -SET lt.value = NULL, lt.status = 1; - - -COMMIT; - - -#select * from ltm_translations WHERE value = 'Адрес Электронной Почты'; -#update ltm_translations set value = 'е-майл', status = 1 where value = 'Адрес Электронной Почты'; - -# select * from ltm_translations where value = 'е-майл' and `key` <> 'email'; -# update ltm_translations set value = NULL, status = 1 where value = 'е-майл' and `key` <> 'email'; +FROM ltm_translations rt +WHERE `key` LIKE '%for-beta%' or value like '%for-beta%' +UNION ALL +SELECT NULL id, 0 status, lt.locale, kt.`group`, kt.`key`, NULL value, NULL created_at, NULL updated_at, NULL source, NULL saved_value +FROM (SELECT DISTINCT locale + FROM ltm_translations) lt + CROSS JOIN (SELECT DISTINCT `key`, `group` + FROM ltm_translations) kt +WHERE NOT exists(SELECT * + FROM ltm_translations tr WHERE tr.`key` = kt.`key` AND tr.`group` = kt.`group` AND tr.locale = lt.locale) + AND `key` LIKE '%for-beta%' +; + +# insert missing keys for other locales to reduce hit on the database for missing keys +INSERT into ltm_translations +SELECT NULL id, 0 status, lt.locale, kt.`group`, kt.`key`, NULL value, NULL created_at, NULL updated_at, NULL source, NULL saved_value +FROM (SELECT DISTINCT locale + FROM ltm_translations) lt + CROSS JOIN (SELECT DISTINCT `key`, `group` + FROM ltm_translations) kt +WHERE NOT exists(SELECT * FROM ltm_translations tr WHERE tr.`key` = kt.`key` AND tr.`group` = kt.`group` AND tr.locale = lt.locale) +; -# TRUNCATE table ltm_translations; diff --git a/src/Barryvdh/TranslationManager/Controller.php b/src/Barryvdh/TranslationManager/Controller.php index 6b0f28f..0085145 100644 --- a/src/Barryvdh/TranslationManager/Controller.php +++ b/src/Barryvdh/TranslationManager/Controller.php @@ -301,7 +301,28 @@ function getIndex($group = null) function getSearch() { $q = \Input::get('q'); - $translations = Translation::where('key', 'like', "%$q%")->orWhere('value', 'like', "%$q%")->orderBy('group', 'asc')->orderBy('key', 'asc')->get(); + + if ($q === '') $translations = []; + else + { + if (strpos($q, '%') === false) $q = "%$q%"; + + //$translations = Translation::where('key', 'like', "%$q%")->orWhere('value', 'like', "%$q%")->orderBy('group', 'asc')->orderBy('key', 'asc')->get(); + + // need to fill-in missing locale's that match the key + $translations = DB::select(<<with('translations', $translations)->with('numTranslations', $numTranslations); diff --git a/src/Barryvdh/TranslationManager/Manager.php b/src/Barryvdh/TranslationManager/Manager.php index 92edc08..4741dc0 100644 --- a/src/Barryvdh/TranslationManager/Manager.php +++ b/src/Barryvdh/TranslationManager/Manager.php @@ -6,8 +6,13 @@ use Barryvdh\TranslationManager\Models\Translation; use Illuminate\Foundation\Application; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Session; use Symfony\Component\Finder\Finder; +/** + * Class Manager + * @package Barryvdh\TranslationManager + */ class Manager { @@ -31,24 +36,47 @@ function __construct(Application $app, Filesystem $files, Dispatcher $events) // causes a problem since none of the keys are defined. $this->config = null; } - + protected function config() { return $this->config ?: $this->config = $this->app['config']['laravel-translation-manager::config']; } + public + function excludedPageEditGroup($group) + { + return in_array($group, $this->config()['exclude_page_edit_groups']); + } + + /** + * @param $namespace string + * @param $group string + * @param $key string + * + * @return null|Translation + */ public function missingKey($namespace, $group, $key) { - if (!in_array($group, $this->config()['exclude_groups'])) + if (!in_array($group, $this->config()['exclude_groups']) && $this->config()['log_missing_keys']) { - $translation = Translation::firstOrCreate(array( - 'locale' => $this->app['config']['app.locale'], - 'group' => $group, - 'key' => $key, - )); - return $translation; + $lottery = Session::get('laravel_translation_manager.lottery', ''); + if ($lottery === '') + { + $lottery = rand(1, $this->config()['missing_keys_lottery']); + Session::put('laravel_translation_manager.lottery', $lottery); + } + + if ($lottery === 1) + { + $translation = Translation::firstOrCreate(array( + 'locale' => $this->app['config']['app.locale'], + 'group' => $group, + 'key' => $key, + )); + return $translation; + } } return null; } @@ -96,7 +124,7 @@ function importTranslations($replace = false) $translation->value = $value; } - $translation->saved_value = $value; + $translation->saved_value = $value; $translation->save(); @@ -180,7 +208,7 @@ function formatForExport($trans, $indent = 0) { if (!is_int($key) && strlen($key) > $max) $max = strlen($key); } - $max += (($max+2) & 3) ? 4-(($max+2) & 3): 0; + $max += (($max + 2) & 3) ? 4 - (($max + 2) & 3) : 0; foreach ($trans as $key => $val) { diff --git a/src/Barryvdh/TranslationManager/Translator.php b/src/Barryvdh/TranslationManager/Translator.php index 7ac60b8..e2c8760 100644 --- a/src/Barryvdh/TranslationManager/Translator.php +++ b/src/Barryvdh/TranslationManager/Translator.php @@ -11,6 +11,9 @@ class Translator extends LaravelTranslator /** @var Dispatcher */ protected $events; + /* @var $manager Manager */ + protected $manager; + /** * Translator constructor. */ @@ -38,16 +41,17 @@ function get($key, array $replace = array(), $locale = null) { list($namespace, $group, $item) = $this->parseKey($key); - // TODO: add config to translation manager to define exclude groups for in page edit - if ($this->manager && $namespace === '*' && $group && $group !== 'page-titles' && $item) + if ($this->manager && $namespace === '*' && $group && $item && !$this->manager->excludedPageEditGroup($group)) { + if (is_numeric($item)) xdebug_break(); + $t = $this->manager->missingKey($namespace, $group, $item); if ($t) { if (is_null($t->value)) $t->value = parent::get($key, $replace, $locale); $result = 'key . '" id="username" data-type="textarea" data-pk="' . ($t ? $t->id : 0) . '" + data-name="' . $t->locale . '|' . $t->key . '" id="username" data-type="textarea" data-pk="' . ($t ? $t->id : 0) . '" data-url="' . URL::action('Barryvdh\TranslationManager\Controller@postEdit', array($t->group)) . '" data-inputclass="editable-input" data-title="' . parent::trans('laravel-translation-manager::translations.enter-translation') . ': [' . $t->locale . '] ' . $key . '">' @@ -78,6 +82,7 @@ function notifyMissingKey($key) list($namespace, $group, $item) = $this->parseKey($key); if ($this->manager && $namespace === '*' && $group && $item) { + if (is_numeric($key)) xdebug_break(); $this->manager->missingKey($namespace, $group, $item); } } diff --git a/src/config/config.php b/src/config/config.php index 86d23df..879c4ed 100644 --- a/src/config/config.php +++ b/src/config/config.php @@ -8,19 +8,56 @@ * @type boolean */ 'delete_enabled' => true, - /** - * Exclude specific groups from Laravel Translation Manager. + * Exclude specific groups from Laravel Translation Manager. * This is useful if, for example, you want to avoid editing the official Laravel language files. * * @type array * - * array( - * 'pagination', - * 'reminders', - * 'validation', - * ) + * array( + * 'pagination', + * 'reminders', + * 'validation', + * ) */ 'exclude_groups' => array(), + /** + * Exclude specific groups from Laravel Translation Manager in page edit mode. + * This is useful for groups that are used exclusively for non-display strings like page titles and emails + * + * @type array + * + * array( + * 'page-titles', + * 'reminders', + * 'validation', + * ) + */ + 'exclude_page_edit_groups' => array( + 'page-titles', + 'reminders', + 'validation', + ), + + /** + * determines whether missing keys are logged + * @type boolean + */ + 'log_missing_keys' => false, + + /** + * determines one out of how many user sessions will have a chance to log missing keys + * since the operation hits the database for every missing key you can limit this by setting a + * higher number depending on the traffic load to your site. + * + * @type int + * + * 1 - means every user + * 10 - means 1 in 10 users + * 100 - 1 in a 100 users + * 1000 .... + * + */ + 'missing_keys_lottery' => 100, // 1 in 100 of users will have the missing translation keys logged. );