public function exportTranslations($group, $recursing = 0)
    {
        // TODO: clean up this recursion crap
        if (!$recursing) {
            $this->clearErrors();
        }
        if ($group && $group !== '*') {
            $this->translation->getConnection()->affectingStatement("DELETE FROM ltm_translations WHERE is_deleted = 1");
        } elseif (!$recursing) {
            $this->translation->getConnection()->affectingStatement("DELETE FROM ltm_translations WHERE is_deleted = 1 AND `group` = ?", [$group]);
        }
        $inDatabasePublishing = $this->inDatabasePublishing();
        if ($inDatabasePublishing < 3 && $inDatabasePublishing && ($inDatabasePublishing < 2 || !$recursing)) {
            if ($group && $group !== '*') {
                $this->translation->getConnection()->affectingStatement(<<<SQL
UPDATE ltm_translations SET saved_value = value, status = ? WHERE (saved_value <> value || status <> ?) AND `group` = ?
SQL
, [Translation::STATUS_SAVED_CACHED, Translation::STATUS_SAVED, $group]);
                $translations = $this->translation->query()->where('status', '<>', Translation::STATUS_SAVED)->where('group', '=', $group)->get(['group', 'key', 'locale', 'saved_value']);
            } else {
                $this->translation->getConnection()->affectingStatement(<<<SQL
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 */
            $this->clearCache($group);
            $translations->each(function ($tr) {
                $this->cacheTranslation($tr->group . '.' . $tr->key, $tr->saved_value, $tr->locale);
            });
        }
        if (!$inDatabasePublishing || $inDatabasePublishing === 2 || $inDatabasePublishing === 3) {
            if (!in_array($group, $this->config()['exclude_groups'])) {
                if ($group == '*') {
                    $this->exportAllTranslations(1);
                }
                if ($inDatabasePublishing !== 3) {
                    $this->clearCache($group);
                }
                $tree = $this->makeTree(Translation::where('group', $group)->whereNotNull('value')->orderby('key')->get());
                $configRewriter = new TranslationFileRewriter();
                $exportOptions = array_key_exists('export_format', $this->config()) ? TranslationFileRewriter::optionFlags($this->config()['export_format']) : null;
                // Laravel 5.1
                $base_path = $this->app->basePath();
                $pathTemplateResolver = new PathTemplateResolver($this->files, $base_path, $this->config()['language_dirs'], '5');
                $zipRoot = $base_path . $this->config('zip_root', mb_substr($this->app->langPath(), 0, -4));
                // Laravel 4.2
                //$base_path = base_path();
                //$pathTemplateResolver = new PathTemplateResolver($this->files, $base_path, $this->config()['language_dirs'], '4');
                //$zipRoot = $base_path . $this->config('zip_root', mb_substr($this->app->make('path').'/lang', 0, -4));
                if (mb_substr($zipRoot, -1) === '/') {
                    $zipRoot = substr($zipRoot, 0, -1);
                }
                foreach ($tree as $locale => $groups) {
                    if (isset($groups[$group])) {
                        $translations = $groups[$group];
                        // use the new path mapping
                        $computedPath = $base_path . $pathTemplateResolver->groupFilePath($group, $locale);
                        $path = $computedPath;
                        if ($path) {
                            $configRewriter->parseSource($this->files->exists($path) ? $this->files->get($path) : '');
                            $output = $configRewriter->formatForExport($translations, $exportOptions);
                            if ($this->zipExporting) {
                                $pathPrefix = mb_substr($path, 0, mb_strlen($zipRoot));
                                $filePathName = $pathPrefix === $zipRoot ? mb_substr($path, mb_strlen($zipRoot)) : $path;
                                //$this->makeDirPath($filePathName);
                                $this->zipExporting->addFromString($filePathName, $output);
                            } else {
                                try {
                                    $this->makeDirPath($path);
                                    if (($result = $this->files->put($path, $output)) === false) {
                                        $this->errors[] = "Failed to write to {$path}";
                                    }
                                } catch (Exception $e) {
                                    $this->errors[] = $e->getMessage();
                                }
                            }
                        }
                    }
                }
                if (!$inDatabasePublishing) {
                    Translation::where('group', $group)->update(array('status' => Translation::STATUS_SAVED, 'saved_value' => new Expression('value')));
                }
            }
        }
    }
    public function getIndex($group = null)
    {
        $locales = $this->locales;
        $currentLocale = \Lang::getLocale();
        $primaryLocale = $this->primaryLocale;
        $translatingLocale = $this->translatingLocale;
        $groups = Translation::groupBy('group');
        $excludedGroups = $this->manager->getConfig('exclude_groups');
        if ($excludedGroups) {
            $groups->whereNotIn('group', $excludedGroups);
        }
        $groups = array('' => noEditTrans($this->packagePrefix . 'messages.choose-group')) + $groups->lists('group', 'group')->all();
        $numChanged = Translation::where('group', $group)->where('status', Translation::STATUS_CHANGED)->count();
        // 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();
        $displayWhere = $this->displayLocales ? ' AND locale IN (\'' . implode("','", explode(',', $this->displayLocales)) . "')" : '';
        $allTranslations = Translation::hydrateRaw($sql = <<<SQL
SELECT * FROM ltm_translations WHERE `group` = ? {$displayWhere}
UNION ALL
SELECT DISTINCT
    NULL id,
    NULL status,
    locale,
    `group`,
    `key`,
    NULL value,
    NULL created_at,
    NULL updated_at,
    NULL source,
    NULL saved_value,
    NULL is_deleted
FROM
(SELECT * FROM (SELECT DISTINCT locale FROM ltm_translations WHERE 1=1 {$displayWhere}) lcs
    CROSS JOIN (SELECT DISTINCT `group`, `key` FROM ltm_translations WHERE `group` = ? {$displayWhere}) grp) m
WHERE NOT EXISTS(SELECT * FROM ltm_translations t WHERE t.locale = m.locale AND t.`group` = m.`group` AND t.`key` = m.`key`)
ORDER BY `key` ASC
SQL
, [$group, $group]);
        if (!count($allTranslations) && $group) {
            $pos = strrpos($url = Request::url(), '/index');
            if ($pos !== false) {
                $url = substr($url, 0, $pos);
                return Redirect::to($url);
            }
        }
        $numTranslations = count($allTranslations);
        $translations = array();
        foreach ($allTranslations as $translation) {
            $translations[$translation->key][$translation->locale] = $translation;
        }
        $stats = DB::select(<<<SQL
SELECT (mx.total_keys - lcs.total) missing, lcs.changed, lcs.deleted, lcs.locale, lcs.`group`
FROM
    (SELECT sum(total) total, sum(changed) changed, sum(deleted) deleted, `group`, locale
     FROM
         (SELECT count(value) total, sum(status) changed, sum(is_deleted) deleted, `group`, locale FROM ltm_translations lt WHERE 1=1 {$displayWhere} GROUP BY `group`, locale
          UNION ALL
          SELECT DISTINCT 0, 0, 0, `group`, locale FROM (SELECT DISTINCT locale FROM ltm_translations WHERE 1=1 {$displayWhere}) lc
              CROSS JOIN (SELECT DISTINCT `group` FROM ltm_translations) lg) a
     GROUP BY `group`, locale) lcs
    JOIN (SELECT count(DISTINCT `key`) total_keys, `group` FROM ltm_translations WHERE 1=1 {$displayWhere} GROUP BY `group`) mx
        ON lcs.`group` = mx.`group`
WHERE lcs.total < mx.total_keys OR lcs.changed > 0 OR lcs.deleted > 0
SQL
);
        // returned result set lists mising, changed, group, locale
        $summary = [];
        foreach ($stats as $stat) {
            if (!isset($summary[$stat->group])) {
                $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 . " ";
            }
        }
        $mismatches = null;
        $mismatchEnabled = $this->manager->getConfig('mismatch_enabled');
        if ($mismatchEnabled) {
            // get mismatches
            $mismatches = DB::select(<<<SQL
SELECT DISTINCT lt.*, ft.ru, ft.en
FROM (SELECT * FROM ltm_translations WHERE 1=1 {$displayWhere}) lt
    JOIN
    (SELECT DISTINCT mt.`key`, BINARY mt.ru ru, BINARY mt.en en
     FROM (SELECT lt.`group`, lt.`key`, group_concat(CASE lt.locale WHEN '{$primaryLocale}' THEN VALUE ELSE NULL END) en, group_concat(CASE lt.locale WHEN '{$translatingLocale}' THEN VALUE ELSE NULL END) ru
           FROM (SELECT value, `group`, `key`, locale FROM ltm_translations WHERE 1=1 {$displayWhere}
                 UNION ALL
                 SELECT NULL, `group`, `key`, locale FROM ((SELECT DISTINCT locale FROM ltm_translations WHERE 1=1 {$displayWhere}) lc
                     CROSS JOIN (SELECT DISTINCT `group`, `key` FROM ltm_translations WHERE 1=1 {$displayWhere}) lg)
                ) lt
           GROUP BY `group`, `key`) mt
         JOIN (SELECT lt.`group`, lt.`key`, group_concat(CASE lt.locale WHEN '{$primaryLocale}' THEN VALUE ELSE NULL END) en, group_concat(CASE lt.locale WHEN '{$translatingLocale}' THEN VALUE ELSE NULL END) ru
               FROM (SELECT value, `group`, `key`, locale FROM ltm_translations WHERE 1=1 {$displayWhere}
                     UNION ALL
                     SELECT NULL, `group`, `key`, locale FROM ((SELECT DISTINCT locale FROM ltm_translations WHERE 1=1 {$displayWhere}) lc
                         CROSS JOIN (SELECT DISTINCT `group`, `key` FROM ltm_translations WHERE 1=1 {$displayWhere}) lg)
                    ) lt
               GROUP BY `group`, `key`) ht ON mt.`key` = ht.`key`
     WHERE (mt.ru NOT LIKE BINARY ht.ru AND mt.en LIKE BINARY ht.en) OR (mt.ru LIKE BINARY ht.ru AND mt.en NOT LIKE BINARY ht.en)
    ) ft
        ON (lt.locale = '{$translatingLocale}' AND lt.value LIKE BINARY ft.ru) AND lt.`key` = ft.key
ORDER BY `key`, `group`
SQL
);
            $key = '';
            $rus = [];
            $ens = [];
            $rubases = [];
            // by key
            $enbases = [];
            // by key
            $extra = new \stdClass();
            $extra->key = '';
            $mismatches[] = $extra;
            foreach ($mismatches as $mismatch) {
                if ($mismatch->key !== $key) {
                    if ($key) {
                        // process diff for key
                        $txtru = '';
                        $txten = '';
                        if (count($ens) > 1) {
                            $maxen = 0;
                            foreach ($ens as $en => $cnt) {
                                if ($maxen < $cnt) {
                                    $maxen = $cnt;
                                    $txten = $en;
                                }
                            }
                            $enbases[$key] = $txten;
                        } else {
                            $txten = array_keys($ens)[0];
                            $enbases[$key] = $txten;
                        }
                        if (count($rus) > 1) {
                            $maxru = 0;
                            foreach ($rus as $ru => $cnt) {
                                if ($maxru < $cnt) {
                                    $maxru = $cnt;
                                    $txtru = $ru;
                                }
                            }
                            $rubases[$key] = $txtru;
                        } else {
                            $txtru = array_keys($rus)[0];
                            $rubases[$key] = $txtru;
                        }
                    }
                    $key = $mismatch->key;
                    $rus = [];
                    $ens = [];
                }
                if ($mismatch->key === '') {
                    break;
                }
                if (!isset($ens[$mismatch->en])) {
                    $ens[$mismatch->en] = 1;
                } else {
                    $ens[$mismatch->en]++;
                }
                if (!isset($rus[$mismatch->ru])) {
                    $rus[$mismatch->ru] = 1;
                } else {
                    $rus[$mismatch->ru]++;
                }
            }
            array_splice($mismatches, count($mismatches) - 1, 1);
            foreach ($mismatches as $mismatch) {
                $mismatch->en_value = $mismatch->ru;
                $mismatch->en = mb_renderDiffHtml($enbases[$mismatch->key], $mismatch->en);
                $mismatch->ru_value = $mismatch->ru;
                $mismatch->ru = mb_renderDiffHtml($rubases[$mismatch->key], $mismatch->ru);
            }
        }
        // returned result set lists group key ru, en columns for the locale translations, ru has different values for same values in en
        $displayLocales = explode(',', $this->displayLocales);
        $displayLocales = array_combine($displayLocales, $displayLocales);
        return \View::make($this->packagePrefix . 'index')->with('controller', '\\' . get_class($this))->with('package', $this->package)->with('public_prefix', '/vendor/')->with('translations', $translations)->with('yandex_key', !!$this->manager->getConfig('yandex_translator_key'))->with('locales', $locales)->with('primaryLocale', $primaryLocale)->with('currentLocale', $currentLocale)->with('translatingLocale', $translatingLocale)->with('displayLocales', $displayLocales)->with('groups', $groups)->with('group', $group)->with('numTranslations', $numTranslations)->with('numChanged', $numChanged)->with('adminEnabled', $this->manager->getConfig('admin_enabled') && UserCan::admin_translations())->with('mismatchEnabled', $mismatchEnabled)->with('stats', $summary)->with('mismatches', $mismatches);
    }