diff --git a/CHANGELOG.md b/CHANGELOG.md index 43d1fe0..89ea78e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ 2021.09.02.1 - dev - [ ] help translation - [ ] source translation -- [ ] fix constante for official modules -- [ ] fix third-party API (all are broken) -- [ ] add dashboard icon +- [ ] fix third-party API (temp remoed) +- [ ] add support for plural (.po ok) +- add dashboard icon +- fix constante for official modules +- fix superadmin permissions +- remove modules list tab and add button to existing lists +- remove multi-modules import/export 2021.09.02 - clean up code and fix typo diff --git a/_admin.php b/_admin.php index 78e85b6..4b3f7c9 100644 --- a/_admin.php +++ b/_admin.php @@ -15,21 +15,16 @@ if (!defined('DC_CONTEXT_ADMIN')) { return; } -$core->blog->settings->addNamespace('translater'); $core->addBehavior('adminModulesListGetActions', ['translaterAdminBehaviors', 'adminModulesGetActions']); $core->addBehavior('adminModulesListDoActions', ['translaterAdminBehaviors', 'adminModulesDoActions']); $core->addBehavior('adminDashboardFavorites', ['translaterAdminBehaviors', 'adminDashboardFavorites']); -$core->addBehavior('addTranslaterProposalTool', ['translaterAdminBehaviors', 'addGoogleProposalTool']); -$core->addBehavior('addTranslaterProposalTool', ['translaterAdminBehaviors', 'addYahooProposalTool']); -$core->addBehavior('addTranslaterProposalTool', ['translaterAdminBehaviors', 'addMicrosoftProposalTool']); -$core->rest->addFunction('getProposal', ['translaterRest', 'getProposal']); $_menu['Plugins']->addItem( __('Translater'), - $core->adminurl->get('admin.plugin.translater'), + $core->adminurl->get('translater'), dcPage::getPF('translater/icon.png'), preg_match( - '/' . preg_quote($core->adminurl->get('admin.plugin.translater')) . '(&.*)?$/', + '/' . preg_quote($core->adminurl->get('translater')) . '(&.*)?$/', $_SERVER['REQUEST_URI'] ), $core->auth->isSuperAdmin() @@ -37,6 +32,23 @@ $_menu['Plugins']->addItem( class translaterAdminBehaviors { + /** @var dcTranlsater dcTranslater instance */ + private static $translater = null; + + /** + * Create instance of dcTranslater once + * + * @param dCore $core dcCore instance + * @return dctranslater dcTranslater instance + */ + private static function translater($core) + { + if (!(self::$translater instanceof dcTranslater)) { + self::$translater = new dcTranslater($core); + } + return self::$translater; + } + /** * Add button to go to module translation * @@ -48,7 +60,7 @@ class translaterAdminBehaviors public static function adminModulesGetActions(adminModulesList $list, string $id, array $prop): ?string { if ($list->getList() != $prop['type'] . '-activate' - || !$list->core->blog->settings->translater->get('translater_' . $prop['type'] . '_menu') + || !self::translater($list->core)->getSetting($prop['type'] . '_menu') || !$list->core->auth->isSuperAdmin() ) { return null; @@ -74,7 +86,7 @@ class translaterAdminBehaviors } $list->core->adminurl->redirect( - 'admin.plugin.translater', + 'translater', ['part' => 'module', 'type' => $type, 'module' => key($_POST['translater'])], '#module-lang' ); @@ -90,40 +102,10 @@ class translaterAdminBehaviors { $favs->register('translater', [ 'title' => __('Translater'), - 'url' => $core->adminurl->get('admin.plugin.translater'), + 'url' => $core->adminurl->get('translater'), 'small-icon' => urldecode(dcPage::getPF('translater/icon.png')), 'large-icon' => urldecode(dcPage::getPF('translater/icon-big.png')), 'permissions' => $core->auth->isSuperAdmin() ]); } - - /** - * Register Google Translater tools in translate - * - * @param translaterProposals $proposal translaterProposals instance - */ - public static function addGoogleProposalTool(translaterProposals $proposal) - { - $proposal->addTool('googleProposalTool'); - } - - /** - * Register Yahoo Babelfish tools in translater - * - * @param translaterProposals $proposal translaterProposals instance - */ - public static function addYahooProposalTool(translaterProposals $proposal) - { - $proposal->addTool('yahooProposalTool'); - } - - /** - * Register Microsoft Bing tools in translater - * - * @param translaterProposals $proposal translaterProposals instance - */ - public static function addMicrosoftProposalTool(translaterProposals $proposal) - { - $proposal->addTool('microsoftProposalTool'); - } } \ No newline at end of file diff --git a/_config.php b/_config.php index c9ee3c5..377b42c 100644 --- a/_config.php +++ b/_config.php @@ -15,148 +15,89 @@ if (!defined('DC_CONTEXT_MODULE')) { return null; } -$redir = empty($_REQUEST['redir']) ? - $list->getURL() . '#plugins' : $_REQUEST['redir']; - -# -- Get settings -- -$core->blog->settings->addNamespace('translater'); -$s = $core->blog->settings->translater; - $translater = new dcTranslater($core); -$combo_backup_limit = [ - 5 => 5, - 10 => 10, - 15 => 15, - 20 => 20, - 40 => 40, - 60 => 60 -]; - -$combo_backup_folder = [ - 'module' => __('locales folders of each module'), - 'plugin' => __('plugins folder root'), - 'public' => __('public folder root'), - 'cache' => __('cache folder of Dotclear'), - 'translater' =>__('locales folder of translater') -]; - -$combo_start_page = [ - 'modules_plugin' => __('Plugins'), - 'modules_theme' => __('Themes'), - 'pack' => __('Import/Export') -]; - -# -- Set settings -- if (!empty($_POST['save'])) { - try { - if (empty($_POST['translater_write_po']) - && empty($_POST['translater_write_langphp'])) { - throw new Exception('You must choose one file format at least'); - } - foreach($translater->getDefaultSettings() as $k => $v) { - $translater->setSetting($k, (isset($_POST['translater_' . $k]) ? $_POST['translater_' . $k] : '')); - } - foreach($translater->proposal->getTools() AS $k => $v) { - $v->save(); - } - dcPage::addSuccessNotice( - __('Configuration has been successfully updated.') + try { + if (empty($_POST['write_po']) && empty($_POST['write_langphp'])) { + throw new Exception( + __('You must at least choose one file format to write') ); - http::redirect( - $list->getURL('module=translater&conf=1&redir=' . - $list->getRedir()) - ); - } catch (Exception $e) { - $core->error->add(sprintf($errors[$action], $e->getMessage())); } -} - -# -- Display form -- -echo ' -
-

' . __('Translation') . '

-

-

-

-

-

-

-


-' . form::field('translater_parse_userinfo', 65, 255, $translater->parse_userinfo) . '

-
- -
-

' . __('Tools') . '

-

- -

' . __('Select and configure the tool to use to translate strings:') . '

'; - -foreach($translater->proposal->getTools() AS $k => $v) { - $form = $v->form(); - - echo ' -
-
' . - (empty($form) ? - '

' . sprintf(__('Nothing to configure for %s tool . '), $v->getName()) . '

' : - $form - ) . '
'; + foreach($translater->getDefaultSettings() as $k => $v) { + $translater->setSetting($k, (isset($_POST[$k]) ? $_POST[$k] : '')); + } + dcPage::addSuccessNotice( + __('Configuration successfully updated.') + ); + $core->adminurl->redirect( + 'admin.plugins', + ['module' => 'translater', 'conf' => 1, 'redir' => $list->getRedir()] + ); + } catch (Exception $e) { + $core->error->add($e->getMessage()); + } } echo ' +

' . __('Translation') . '

+

+

+

+

+

+

+

+

' . +form::field('parse_userinfo', 65, 255, $translater->parse_userinfo) . '

+

' . sprintf( + __('Following informations can be used: %s '), implode(', ', $translater::$allowed_user_informations)) . ' +

-
-

' . __('Import/Export') . '

-

-

+

' . __('Import/Export') . '

+

+

' . +form::field('export_filename', 65, 255, $translater->export_filename) . '

-
-

' . __('Backups') . '

-

-

-

+

' . __('Backups') . '

+

+

+

' . +form::combo('backup_folder', $translater::$allowed_backup_folders, $translater->backup_folder) . '

-
-

' . __('Behaviors') . '

-

-

-

-
-'; \ No newline at end of file +

' . __('Behaviors') . '

+

' . +form::combo('start_page',[ + __('Plugins') => 'plugin', + __('Themes') => 'theme', + __('Home') => '-' +], $translater->start_page) . '

+

+

+
'; \ No newline at end of file diff --git a/_install.php b/_install.php index 8e9ee33..fa41ce2 100644 --- a/_install.php +++ b/_install.php @@ -13,38 +13,21 @@ if (!defined('DC_CONTEXT_ADMIN')) { return null; -} - -$s = [ - ['translater_plugin_menu', 0, 'boolean', 'Put a link in plugins page'], - ['translater_theme_menu', 0,'boolean', 'Put a link in themes page'], - ['translater_backup_auto', 1,'boolean', 'Make a backup of languages old files when there are modified'], - ['translater_backup_limit', 20,'string', 'Maximum backups per module'], - ['translater_backup_folder', 'module',' string', 'In which folder to store backups'], - ['translater_start_page', 'setting,', 'string', 'Page to start on'], - ['translater_write_po', 1, 'boolean', 'Write .po languages files'], - ['translater_write_langphp', 1, 'boolean', 'Write .lang.php languages files'], - ['translater_scan_tpl', 0, 'boolean', 'Translate strings of templates files'], - ['translater_parse_nodc', 1, 'boolean', 'Translate only untranslated strings of Dotclear'], - ['translater_hide_default', 1, 'boolean', 'Hide default modules of Dotclear'], - ['translater_parse_comment', 1, 'boolean', 'Write comments and strings informations in lang files'], - ['translater_parse_user', 1,'boolean', 'Write inforamtions about author in lang files'], - ['translater_parse_userinfo', 'displayname, email', 'string','Type of informations about user to write'], - ['translater_import_overwrite', 0, 'boolean', 'Overwrite existing languages when import packages'], - ['translater_export_filename', 'type-module-l10n-timestamp', 'string','Name of files of exported package'], - ['translater_proposal_tool', 'google', 'string', 'Id of default tool for proposed translation'], - ['translater_proposal_lang', 'en', 'string', 'Default source language for proposed translation'] -]; +} try { if (version_compare($core->getVersion($id), $this->moduleInfo($id, 'version'), '>=')) { return null; } - $core->blog->settings->addNamespace('translater'); + + $t = new dcTranslater($core); + $s = $t->getDefaultSettings(); foreach($s as $v) { - $core->blog->settings->translater->put($v[0], $v[1], $v[2], $v[3], false, true); + $t->setSetting($v[0], $v[1], false); } + $core->setVersion($id, $this->moduleInfo($id, 'version')); + return true; } catch (Exception $e) { $core->error->add($e->getMessage()); diff --git a/_prepend.php b/_prepend.php index 4a59052..760a593 100644 --- a/_prepend.php +++ b/_prepend.php @@ -17,10 +17,9 @@ if (!defined('DC_RC_PATH')) { $d = dirname(__FILE__) . '/inc/'; -$__autoload['dcTranslater'] = $d . 'class.dc.translater.php'; -$__autoload['translaterRest'] = $d . 'class.translater.rest.php'; -$__autoload['translaterProposals'] = $d . 'class.translater.proposals.php'; +$__autoload['dcTranslater'] = $d . 'class.dc.translater.php'; +$__autoload['dcTranslaterModule'] = $d . 'class.dc.translater.module.php'; +$__autoload['dcTranslaterLang'] = $d . 'class.dc.translater.lang.php'; +$__autoload['translaterRest'] = $d . 'class.translater.rest.php'; -$__autoload['translaterProposalTool'] = $d . 'lib.translater.proposal.php'; -$__autoload['googleProposalTool'] = $d . 'lib.translater.google.php'; -$__autoload['microsoftProposalTool'] = $d . 'lib.translater.microsoft.php'; \ No newline at end of file +$core->adminurl->register('translater', 'plugin.php', ['p' => 'translater']); \ No newline at end of file diff --git a/css/translater.css b/css/translater.css new file mode 100644 index 0000000..4fc9664 --- /dev/null +++ b/css/translater.css @@ -0,0 +1 @@ +.addfield, .togglelist { border: none; } \ No newline at end of file diff --git a/inc/img/field.png b/img/field.png similarity index 100% rename from inc/img/field.png rename to img/field.png diff --git a/inc/img/toggle.png b/img/toggle.png similarity index 100% rename from inc/img/toggle.png rename to img/toggle.png diff --git a/inc/class.dc.translater.lang.php b/inc/class.dc.translater.lang.php new file mode 100644 index 0000000..5ef5790 --- /dev/null +++ b/inc/class.dc.translater.lang.php @@ -0,0 +1,221 @@ +core = $module->core; + $this->translater = $module->translater; + $this->module = $module; + + $this->prop['code'] = $lang; + $this->prop['name'] = l10n::getLanguageName($lang); + $this->prop['plural'] = explode(':', l10n::getLanguagePluralExpression($lang)); + } + + /** + * Get a lang property + * + * @param string $key The lang property key + * @return mixed The lang property value or null + */ + public function get(string $key) + { + return array_key_exists($key, $this->prop) ? $this->prop[$key] : null; + } + + /** + * Magic get + */ + public function __get($key) + { + return $this->get($key); + } + + /** + * Get a lang messages + * + * @return array The messages ids and translations + */ + public function getMessages(): array + { + $res = []; + $m_msgids = $this->getMsgIds(); + $m_msgstrs = $this->getMsgStrs(); + + foreach($this->translater->getModules() as $module) { + if ($module->id != $this->module->id) { + $m_o_msgstrs[$module->id] = $this->translater->getlang($module, $this->code)->getMsgStrs(); + } + } + $dc_module = new dcTranslaterModule($this->translater, ['id' => 'dotclear', 'root' => DC_ROOT]); + $dc_lang = new dctranslaterLang($dc_module, $this->code); + $m_o_msgstrs['dotclear'] = $dc_lang->getMsgStrs(); + + # From id list + foreach($m_msgids as $rs) { + $res[$rs['msgid']]['files'][] = [trim($rs['file'],'/'), $rs['line']]; + $res[$rs['msgid']]['group'] = 'main'; + $res[$rs['msgid']]['plural'] = $rs['msgid_plural']; + $res[$rs['msgid']]['msgstr'] = ['']; + $res[$rs['msgid']]['in_dc'] = false; + $res[$rs['msgid']]['o_msgstrs'] = []; + } + + # From str list + foreach($m_msgstrs as $rs) { + + if (!isset($res[$rs['msgid']])) { + $res[$rs['msgid']]['files'][] = []; + $res[$rs['msgid']]['in_dc'] = false; + $res[$rs['msgid']]['o_msgstrs'] = []; + } + $res[$rs['msgid']]['group'] = $rs['group']; + $res[$rs['msgid']]['plural'] = $rs['msgid_plural']; + $res[$rs['msgid']]['msgstr'] = is_array($rs['msgstr']) ? $rs['msgstr'] : [$rs['msgstr']]; + $res[$rs['msgid']]['in_dc'] = false; + } + + # From others str list + foreach($m_o_msgstrs as $o_module => $o_msgstrs) { + foreach($o_msgstrs as $rs) { + if (!isset($res[$rs['msgid']])) { + continue; + } + + $res[$rs['msgid']]['o_msgstrs'][] = [ + 'msgstr' => is_array($rs['msgstr']) ? $rs['msgstr'] : [$rs['msgstr']], + 'module' => $o_module, + 'file' => $rs['file'] + ]; + if ($o_module == 'dotclear') { + $res[$rs['msgid']]['in_dc'] = true; + } + } + } + return $res; + } + + /** + * Get messages ids + * + * @return array The messages ids + */ + public function getMsgIds(): array + { + $res = []; + $files = dcTranslater::scandir($this->module->root); + + $scan_ext = ['php']; + if ($this->translater->scan_tpl) { +// $scan_ext[] = 'html'; + } + + foreach($files AS $file) { + if (is_dir($this->module->root . '/' . $file) + || !in_array(files::getExtension($file), $scan_ext)) { + continue; + } + + $contents = file_get_contents($this->module->root . '/' . $file); + # php files + $msgs = dcTranslater::extractPhpMsgs($contents); + foreach($msgs as $msg) { + $res[] = [ + 'msgid' => dcTranslater::encodeMsg($msg[0][0]), + 'msgid_plural' => empty($msg[0][1]) ? '' : dcTranslater::encodeMsg($msg[0][1]), + 'file' => $file, + 'line' => $msg[1] + ]; + } + + unset($contents); + } + return $res; + } + + /** + * Get messages translations + * + * @return array The messages translations + */ + public function getMsgStrs(): array + { + $res = $scanned = []; + + $langs = $this->module->getLangs(true); + if (!isset($langs[$this->code])) { + return $res; + } + + foreach($langs[$this->code] as $file) { + if (in_array($file, $scanned)) { + continue; + } + $scanned[] = $file; + $path = path::clean($this->module->locales . '/' . $file); + + if (dcTranslater::isPoFile($file)) { + $po = l10n::parsePoFile($path); + if (!is_array($po)) { + continue; + } + $entries = $po[1]; + foreach($entries as $entry) { + $res[] = array( + 'msgid' => $entry['msgid'], + 'msgid_plural' => $entry['msgid_plural'] ?? '', + 'msgstr' => is_array($entry['msgstr']) ? $entry['msgstr'] : [$entry['msgstr']], + 'lang' => $this->code, + 'type' => 'po', + 'path' => $path, + 'file' => basename($file), + 'group'=> str_replace('.po', '', basename($file)) + ); + } +/* + # .lang.php files + } elseif (self::isLangphpFile($file)) { + $php = self::getLangphpFile($path); + foreach($php AS $id => $str) { + # Don't overwrite .po + if (isset($is_po[$requested_lang][$id])) { + continue; + } + $res[] = array( + 'msgid' => self::encodeMsg($id), + 'msgstr' => self::encodeMsg($str), + 'lang' => $requested_lang, + 'type' => 'php', + 'path' => $path, + 'file' => basename($file), + 'group'=> str_replace('.lang.php', '', basename($file)) + ); + } +*/ + } + } + return $res; + } +} \ No newline at end of file diff --git a/inc/class.dc.translater.module.php b/inc/class.dc.translater.module.php new file mode 100644 index 0000000..5d6e946 --- /dev/null +++ b/inc/class.dc.translater.module.php @@ -0,0 +1,749 @@ +core = $translater->core; + $this->translater = $translater; + $this->prop = $module; + + $this->prop['root'] = path::real($this->prop['root']); + $i = path::info($this->prop['root']); + $this->prop['basename'] = $i['basename']; + $this->prop['locales'] = $this->prop['root'] . '/locales'; + } + + /** + * Get a module property + * + * @param string $key The module property key + * @return mixed The module property value or null + */ + public function get(string $key) + { + return array_key_exists($key, $this->prop) ? $this->prop[$key] : null; + } + + /** + * Magic get + */ + public function __get($key) + { + return $this->get($key); + } + + /// @name backup methods + //@{ + /** + * Find backup folder of a module + * + * @param boolean $throw Silently failed + * @return mixed The backup folder directory or false + */ + public function getBackupRoot(bool $throw = false) + { + $dir = false; + switch($this->translater->backup_folder) { + case 'module': + if ($this->prop['root_writable']) { + $dir = $this->prop['locales']; + } + break; + + case 'plugin': + $tmp = path::real(array_pop(explode(PATH_SEPARATOR, DC_PLUGINS_ROOT))); + if ($tmp && is_writable($tmp)) { + $dir = $tmp; + } + break; + + case 'public': + $tmp = path::real($this->core->blog->public_path); + if ($tmp && is_writable($tmp)) { + $dir = $tmp; + } + break; + + case 'cache': + $tmp = path::real(DC_TPL_CACHE); + if ($tmp && is_writable($tmp)) { + @mkDir($tmp . '/l10n'); + $dir = $tmp . '/l10n'; + } + break; + + case 'translater': + $tmp = path::real($this->core->plugins->moduleRoot('translater')); + if ($tmp && is_writable($tmp)) { + @mkDir($tmp . '/locales'); + $dir = $tmp . '/locales'; + } + break; + } + if (!$dir && $throw) { + throw new Exception(sprintf( + __('Cannot find backups folder for module %s'), $id + )); + } + + return $dir; + } + + /** + * Get a list of available backups + * + * @param boolean $return_filename Return only filenames + * @return array The module backups info + */ + public function getBackups(bool $return_filename = false): array + { + $backup = $this->getBackupRoot(); + if (!$backup) { + return []; + } + + $res = []; + $files = dcTranslater::scandir($backup); + foreach($files AS $file) { + $is_backup = preg_match(sprintf($this->backup_file_regexp, preg_quote($this->prop['id'])), $file, $m); + + if (is_dir($backup . '/' . $file) + || !$is_backup + || !l10n::isCode($m[1]) + ) { + continue; + } + + if ($return_filename) { + $res[] = $file; + } else { + $res[$m[1]][$file]['code'] = $m[1]; + $res[$m[1]][$file]['name'] = l10n::getLanguageName($m[1]); + $res[$m[1]][$file]['path'] = path::info($backup . '/' . $file); + $res[$m[1]][$file]['time'] = filemtime($backup . '/' . $file); + $res[$m[1]][$file]['size'] = filesize($backup . '/' . $file); + $res[$m[1]][$file]['module'] = $this->prop['id']; + } + } + return $res; + } + + /** + * Create a backup + * + * @param string $lang The backup lang + * @return boolean True on success + */ + public function createBackup(string $lang): bool + { + $backup = $this->getBackupRoot(true); + + if (!is_dir($this->prop['locales'] . '/' . $lang)) { + throw new Exception(sprintf( + __('Cannot find language folder %s for module %s') ,$lang, $this->prop['id'] + )); + } + + $res = []; + $files = dcTranslater::scandir($this->prop['locales'] . '/' . $lang); + foreach($files as $file) { + if (!is_dir($this->prop['locales'] . '/' . $lang . '/' . $file) + && (dcTranslater::isLangphpFile($file) || dcTranslater::isPoFile($file)) + ) { + $res[$this->prop['locales'] . '/' . $lang . '/' .$file] = + $this->prop['id'] . '/locales/' . $lang . '/' . $file; + } + } + + if (!empty($res)) { + dcTranslater::isBackupLimit($backup, $this->translater->backup_limit, true); + + @set_time_limit(300); + $fp = fopen($backup . '/l10n-' . $this->prop['id'] . '-' . $lang . '-' . time() . '.bck.zip', 'wb'); + $zip = new fileZip($fp); + foreach($res AS $from => $to) { + $zip->addFile($from, $to); + } + $zip->write(); + $zip->close(); + unset($zip); + + return true; + } + } + + /** + * Retore a backup + * + * @param string $file The backup filename + * @return boolean True on success + */ + public function restoreBackup(string $file): bool + { + $backup = self::getBackupRoot(true); + + if (!file_exists($backup . '/' . $file)) { + throw new Exception(sprintf( + __('Cannot find backup file %s'), $file + )); + } + + $zip = new fileUnzip($backup . '/' . $file); + $zip_files = $zip->getFilesList(); + + foreach($zip_files AS $zip_file) { + $f = $this->parseZipFilename($zip_file, true); + $zip->unzip($zip_file, $this->prop['locales'] . '/' . $f['lang'] . '/' . $f['group'] . $f['ext']); + $done = true; + } + $zip->close(); + unset($zip); + + return true; + } + + /** + * Delete a module backup + * + * @param string $file The backup filename + * @return boolean True on success + */ + public function deleteBackup(string $file): bool + { + $backup = $this->getBackupRoot(true); + + $is_backup = preg_match(sprintf($this->backup_file_regexp, preg_quote($this->prop['id'])), $file, $m); + + if (!file_exists($backup . '/' . $file) + || !$is_backup + || !l10n::isCode($m[1]) + ) { + return false; + } + + if (!files::isDeletable($backup . '/' . $file)) { + throw new Exception(sprintf( + __('Cannot delete backup file %s'), $file + )); + } + + unlink($backup . '/' . $file); + + return true; + } + + /** + * Import a language pack + * + * @param array $zip_file The uploaded file info + * @return boolean True on success + */ + public function importPack(array $zip_file): bool + { + files::uploadStatus($zip_file); + + $imported = false; + $not_overwrited = []; + $res = []; + + # Load Unzip object + $zip = new fileUnzip($zip_file['tmp_name']); + $files = $zip->getFilesList(); + + foreach($files AS $file) { + $f = $this->parseZipFilename($file, true); + + if (!$this->translater->import_overwrite + && file_exists($this->prop['locales'] . '/' . $f['lang'] . '/' . $f['group'] . $f['ext']) + ) { + $not_overwrited[] = implode('-', [$f['lang'], $f['group'], $f['ext']]); + continue; + } + + $res[] = [ + 'from' => $file, + 'root' => $this->prop['locales'] . '/' . $f['lang'], + 'to' => $this->prop['locales'] . '/' . $f['lang'] . '/' . $f['group'] . $f['ext'] + ]; + } + + foreach ($res AS $rs) { + if (!is_dir($rs['root'])) { + files::makeDir($rs['root'], true); + } + + $zip->unzip($rs['from'], $rs['to']); + $imported = true; + } + $zip->close(); + unlink($zip_file['tmp_name']); + + if (!empty($not_overwrited)) { + throw new Exception(sprintf( + __('Some languages has not been overwrited %s'), implode(', ', $not_overwrited) + )); + } elseif (!$done) { + throw new Exception(sprintf( + __('Nothing to import for these modules in pack %s'), $zip_file['name'] + )); + } + return true; + } + + /** + * Export (to output) language pack + * + * @param array $modules The modules to work on + * @param array $langs Langs to export + */ + public function exportPack(array $langs) + { + if (empty($langs)) { + throw new Exception( + __('Wrong export query') + ); + } + + $filename = files::tidyFileName($this->translater->export_filename); + if (empty($filename)) { + throw new Exception( + __('Export mask is not set in plugin configuration') + ); + } + + $res = []; + foreach($langs AS $lang) { + if (!is_dir($this->prop['locales'] . '/' . $lang)) { + continue; + } + + $files = dcTranslater::scandir($this->prop['locales'] . '/' . $lang); + foreach($files as $file) { + + if (is_dir($this->prop['locales'] . '/' . $lang . '/' . $file) + || !dcTranslater::isLangphpFile($file) + && !dcTranslater::isPoFile($file) + ) { + continue; + } + + $res[$this->prop['locales'] . '/' . $lang . '/' . $file] = + $this->prop['id'] . '/locales/' . $lang . '/' . $file; + } + } + + if (empty($res)) { + throw new Exception( + __('Nothing to export') + ); + } + + @set_time_limit(300); + $fp = fopen('php://output', 'wb'); + $zip = new fileZip($fp); + foreach($res as $from => $to) { + $zip->addFile($from, $to); + } + + $filename = files::tidyFileName(dt::str(str_replace( + ['timestamp', 'module', 'type', 'version'], + [time(), $this->prop['id'], $this->prop['type'], $this->prop['version']], + $this->translater->export_filename + ))); + + header('Content-Disposition: attachment;filename=' . $filename . '.zip'); + header('Content-Type: application/x-zip'); + $zip->write(); + unset($zip); + exit; + } + + /** + * Parse zip filename to module, lang info + * + * @param string $file The zip filename + * @param boolean $throw Silently failed + * @return mixed Array of file info or false + */ + public function parseZipFilename(string $file = '', bool $throw = false) + { + $is_file = preg_match('/^(.*?)\/locales\/(.*?)\/(.*?)(.po|.lang.php)$/', $file, $f); + + if ($is_file) { + $module = $f[1] == $this->prop['id'] ?$f[1] : false; + $lang = l10n::isCode($f[2]) ? $f[2] : false; + $group = in_array($f[3], dctranslater::$allowed_l10n_groups) ? $f[3] : false; + $ext = dctranslater::isLangphpFile($f[4]) || dctranslater::isPoFile($f[4]) ? $f[4] : false; + } + + if (!$is_file || !$module || !$lang || !$group || !$ext) { + if ($throw) { + throw new Exception(sprintf( + __('Zip file %s is not in translater format'), $file + )); + } + return false; + } + return [ + 'module' => $module, + 'lang' => $lang, + 'group' => $group, + 'ext' => $ext + ]; + } + //@} + + /// @name lang methods + //@{ + /** + * List available langs of a module + * + * @param boolean $return_path Return path or name + * @return array The lang list + */ + public function getLangs(bool $return_path = false) + { + $res = []; + + $prefix = preg_match('/(locales(.*))$/', $this->prop['locales']) ? 'locales' : ''; + + $files = dcTranslater::scandir($this->prop['locales']); + foreach($files as $file) { + if (!preg_match('/.*?locales\/([^\/]*?)\/([^\/]*?)(.lang.php|.po)$/', $prefix . $file, $m)) { + continue; + } + + if (!l10n::isCode($m[1])) { + continue; + } + + if ($return_path) { + $res[$m[1]][] = $file; // Path + } else { + $res[$m[1]] = l10n::getLanguageName($m[1]); // Lang name + } + } + return $res; + } + + /** + * List of used langs of a module + * + * @return array The list of iso names and codes + */ + public function getUsedLangs() + { + return array_flip($this->getLangs()); + } + + /** + * List of unsused langs of a module + * + * @return array The list of iso names and codes + */ + public function getUnusedLangs() + { + return array_diff(l10n::getISOcodes(true, false), $this->getUsedLangs()); + } + + /** + * Add a lang to a module + * + * @param string $lang The lang id + * @param string $from_lang The lang to copy from + * @return boolean True on success + */ + public function addLang(string $lang, string $from_lang = '') + { + if (!l10n::isCode($lang)) { + throw new Exception(sprintf( + __('Wrong language %s'), $lang + )); + } + + $langs = $this->getLangs(); + if (isset($langs[$lang])) { + throw new Exception(sprintf( + __('Language %s already exists'), $lang + )); + } + + files::makeDir($this->prop['locales'] . '/' . $lang, true); + + if (!empty($from_lang) && !isset($langs[$from_lang])) { + throw new Exception(sprintf( + __('Cannot copy file from language %s'), $from_lang + )); + } + + if (!empty($from_lang) && isset($langs[$from_lang])) { + $files = dcTranslater::scandir($this->prop['locales'] . '/' . $from_lang); + foreach($files as $file) { + if (is_dir($this->prop['locales'] . '/' . $from_lang . '/' . $file) + || !dcTranslater::isLangphpFile($file) + && !dcTranslater::isPoFile($file) + ) { + continue; + } + + files::putContent($this->prop['locales'] . '/' . $lang . '/' . $file, + file_get_contents($this->prop['locales'] . '/' . $from_lang . '/' . $file) + ); + } + } else { + //$this->setLangphpFile($lang, 'main', []); + $this->setPoContent($lang, 'main', []); + } + } + + /** + * Update an existing lang + * + * @param string $lang The lang + * @param array $msgs The messages + */ + public function updLang(string $lang, array $msgs) + { + if (!l10n::isCode($lang)) { + throw new Exception(sprintf( + __('Wrong language %s'), $lang + )); + } + + $langs = $this->getLangs(); + if (!isset($langs[$lang])) { + throw new Exception(sprintf( + __('Failed to find language %s'), $lang + )); + } + + if ($this->translater->backup_auto) { + $this->createBackup($lang); + } + + $rs = []; + foreach($msgs as $msg) { + if (empty($msg['msgstr'][0])) { + continue; + } + $rs[$msg['group']][] = $msg; + } + + foreach(dcTranslater::$allowed_l10n_groups as $group) { + if (isset($rs[$group])) { + continue; + } + + $po_file = $this->prop['locales'] . '/' . $lang . '/' . $group . '.po'; + $langphp_file = $this->prop['locales'] . '/' . $lang . '/' . $group . '.lang.php'; + + if (file_exists($po_file)) { + unlink($po_file); + } + if (file_exists($langphp_file)) { + unlink($langphp_file); + } + } + + if (empty($rs)) { + throw new Exception(sprintf( + __('No string to write, language %s deleted for module %s'), + $lang, $id + )); + } + + foreach($rs as $group => $msgs) { + $this->setPoContent($lang, $group, $msgs); + //$this->setLangphpFile($lang, $group, $msgs); + } + } + + /** + * Delete a lang + * + * @param string $lang The lang code + * @param boolean $del_empty_dir Also remove empty locales dir + * @return boolean True on success + */ + public function delLang(string $lang, bool $del_empty_dir = true): bool + { + # Path is right formed + if (!l10n::isCode($lang)) { + throw new Exception(sprintf( + __('Wrong language %s'), $lang + )); + } + + $files = $this->getLangs(true); + if (!isset($files[$lang])) { + throw new Exception(sprintf( + __('Cannot find language folder %s'), $lang + )); + } + + foreach($files[$lang] as $file) { + unlink($this->prop['locales'] . '/' . $file); + } + + $dir = dcTranslater::scandir($this->prop['locales'] . '/' . $lang); + if (empty($dir)) { + rmdir($this->prop['locales'] . '/' . $lang); + } + + $loc = dcTranslater::scandir($this->prop['locales']); + if (empty($loc)) { + rmdir($this->prop['locales']); + } + return true; + } + + /** + * Write a lang file + * + * @param string $file The full file path + * @param string $content The file content + * @param boolean $throw Silently failed + * @return boolean True on success + */ + private function writeLang(string $file, string $content, bool $throw = false) + { + $path = path::info($file); + if (is_dir($path['dirname']) && !is_writable($path['dirname']) + || file_exists($file) && !is_writable($file)) { + throw new Exception(sprintf( + __('Cannot grant write acces on lang file %s'), $file + )); + } + + # -- BEHAVIOR -- dcTranslaterBeforeWriteLangFile + $this->core->callBehavior('dcTranslaterBeforeWriteLangFile', $file, $content, $throw); + + $f = @files::putContent($file,$content); + if (!$f && $throw) { + throw new Exception(sprintf( + __('Cannot write lang file %s'), $file + )); + } + + # -- BEHAVIOR -- dcTranslaterAfterWriteLangFile + $this->core->callBehavior('dcTranslaterAfterWriteLangFile', $f, $file, $content, $throw); + + return $f; + } + + /** + * Construct and parse a .po file + * + * @param string $lang The lang code + * @param string $group The lang group + * @param array $msgs The strings + */ + private function setPoContent(string $lang, string $group, array $msgs) + { + if (!$this->translater->write_po) { + return null; + } + + $lang = new dcTranslaterLang($this, $lang); + + $l = ''; + if ($this->translater->parse_comment) { + $l .= + '# Language: ' . $lang->name . "\n" . + '# Module: ' . $this->prop['id'] . " - " . $this->prop['version'] . "\n" . + '# Date: ' . dt::str('%Y-%m-%d %H:%M:%S') . "\n"; + + if ($this->translater->parse_user && !empty($this->translater->parse_userinfo)) { + $search = dctranslater::$allowed_user_informations; + foreach($search AS $n) { + $replace[] = $this->core->auth->getInfo('user_' . $n); + } + $info = trim(str_replace($search, $replace, $this->translater->parse_userinfo)); + if (!empty($info)) { + $l .= '# Author: ' . html::escapeHTML($info) . "\n"; + } + } + $l .= + '# Translated with translater ' . $this->core->plugins->moduleInfo('translater', 'version') . "\n"; + } + $l .= + "\n". + "msgid \"\"\n" . + "msgstr \"\"\n" . + '"Content-Type: text/plain; charset=UTF-8\n"' . "\n" . + '"Project-Id-Version: ' . $this->prop['id'] . ' ' . $this->prop['version'] . '\n"' . "\n" . + '"POT-Creation-Date: \n"' . "\n" . + '"PO-Revision-Date: ' . date('c') . '\n"' . "\n" . + '"Last-Translator: ' . $this->core->auth->getInfo('user_cn') . '\n"' . "\n" . + '"Language-Team: \n"' . "\n" . + '"MIME-Version: 1.0\n"' . "\n" . + '"Content-Transfer-Encoding: 8bit\n"' . "\n" . + '"Plural-Forms: nplurals=2; plural=(n > 1);\n"' . "\n\n"; + + $comments = []; + if ($this->translater->parse_comment) { + $msgids = $lang->getMsgids(); + foreach($msgids as $msg) { + $comments[$msg['msgid']] = (isset($comments[$msg['msgid']]) ? + $comments[$msg['msgid']] : '') . + '#: '.trim($msg['file'],'/') . ':' . $msg['line'] . "\n"; + } + } + + $content = ''; + foreach($msgs as $msg) { + if (empty($msg['msgstr'][0])) { + continue; + } + if ($this->translater->parse_comment && isset($comments[$msg['msgid']])) { + $content .= $comments[$msg['msgid']]; + } + $content .= 'msgid "' . dcTranslater::poString($msg['msgid'], true) . '"' . "\n"; + if (empty($msg['msgid_plural'])) { + $content .= 'msgstr "' . dcTranslater::poString($msg['msgstr'][0], true) . '"' . "\n"; + } else { + $content .= 'msgid_plural "' . dcTranslater::poString($msg['msgid_plural'], true) . '"' . "\n"; + foreach($msg['msgstr'] as $i => $plural) { + $content .= 'msgstr[' . $i . '] "' . dcTranslater::poString(($msg['msgstr'][$i] ?: ''), true) . '"' . "\n"; + } + } + $content .= "\n"; + } + + self::writeLang($this->locales . '/' . $lang->code . '/' . $group . '.po', $content, true); + } + //@} +} \ No newline at end of file diff --git a/inc/class.dc.translater.php b/inc/class.dc.translater.php index 52d88bc..c11b0b7 100644 --- a/inc/class.dc.translater.php +++ b/inc/class.dc.translater.php @@ -20,42 +20,11 @@ if (!defined('DC_CONTEXT_ADMIN')) { */ class dcTranslater { + /** @var dCore dcCore instance */ public $core; - public $proposal; - - /** @var array $iso List of l10n code/name allowed from clearbricks l10n class */ - protected static $iso = []; - - /** @var array $allowed_backup_folders List of allowed backup folder */ - public static $allowed_backup_folders = [ - 'public', - 'module', - 'plugin', - 'cache', - 'translater' - ]; - - /** @var array $allowed_l10n_groups List of place of tranlsations */ - public static $allowed_l10n_groups = [ - 'main', - 'public', - 'theme', - 'admin', - 'date', - 'error' - ]; - - /** @var array $allowed_user_informations List of user info can be parsed */ - public static $allowed_user_informations = [ - 'firstname', - 'displayname', - 'name', - 'email', - 'url' - ]; /** @var array $default_settings Plugins default settings */ - private $default_settings = [ + private static $default_settings = [ 'plugin_menu' => [ 'id' => 'translater_plugin_menu', 'value' => 0, @@ -100,7 +69,7 @@ class dcTranslater ], 'write_langphp' => [ 'id' => 'translater_write_langphp', - 'value' => 1, + 'value' => 0, 'type' => 'boolean', 'label' => 'Write .lang.php languages files' ], @@ -166,30 +135,50 @@ class dcTranslater ] ]; - /** @var array $default_dotclear_modules List of distributed plugins and themes */ - public static $default_dotclear_modules = ['plugin' => [], 'theme' => []]; + /** @var array $allowed_backup_folders List of allowed backup folder */ + public static $allowed_backup_folders = []; + + /** @var array $allowed_l10n_groups List of place of tranlsations */ + public static $allowed_l10n_groups = [ + 'main', 'public', 'theme', 'admin', 'date', 'error' + ]; + + /** @var array $allowed_user_informations List of user info can be parsed */ + public static $allowed_user_informations = [ + 'firstname', 'displayname', 'name', 'email', 'url' + ]; + + /** @var array $default_distrib_modules List of distributed plugins and themes */ + public static $default_distrib_modules = ['plugin' => [], 'theme' => []]; /** @var array $modules List of modules we could work on */ private $modules = []; - /** @var array $module Module to work on */ - private $module = []; - /** - * Main translater object + * translater instance * * @param dcCore $core dcCore instance */ - public function __construct(dcCore $core) + public function __construct($core) { - self::$default_dotclear_modules = [ - 'plugin' => explode(',', DC_DISTRIB_PLUGINS), - 'theme' => explode(',', DC_DISTRIB_THEMES) - ]; $this->core = $core; $core->blog->settings->addNamespace('translater'); $this->loadModules(); - $this->proposal = new translaterProposals($core); + + // fill with translated string + self::$allowed_backup_folders = [ + __('locales folders of each module') => 'module', + __('plugins folder root') => 'plugin', + __('public folder root') => 'public', + __('cache folder of Dotclear') => 'cache', + __('locales folder of translater') => 'translater' + ]; + + // fill distrib modules list + self::$default_distrib_modules = [ + 'plugin' => explode(',', DC_DISTRIB_PLUGINS), + 'theme' => explode(',', DC_DISTRIB_THEMES) + ]; } /// @name settings methods @@ -201,7 +190,7 @@ class dcTranslater */ public function getDefaultSettings(): array { - return $this->default_settings; + return self::$default_settings; } /** @@ -211,13 +200,9 @@ class dcTranslater * @return mixed The setting value if exists or null */ public function getSetting(string $id) - { - if (!isset($this->default_settings[$id])) { - return null; - } - return $this->core->blog->settings->translater->get( - $this->default_settings[$id]['id'] - ); + { + return array_key_exists($id, self::$default_settings) ? + $this->core->blog->settings->translater->get(self::$default_settings[$id]['id']) : ''; } /** @@ -231,99 +216,72 @@ class dcTranslater /** * Set a setting according to default settings list * - * @param string $k The setting short id - * @param mixed $v The setting value + * @param string $id The setting short id + * @param mixed $value The setting value + * @param mixed $overwrite Overwrite settings if exists + * @return boolean Success */ - public function setSetting(string $k, $v) + public function setSetting(string $id, $value, $overwrite = true) { - if (!isset($this->default_settings[$k])) { + if (!array_key_exists($id, self::$default_settings)) { return false; } - - $this->core->blog->settings->translater->drop($id); + $this->core->blog->settings->translater->drop(self::$default_settings[$id]['id']); $this->core->blog->settings->translater->put( - $this->default_settings[$k]['id'], - $v, - $this->default_settings[$k]['type'], - $this->default_settings[$k]['label'], - true, + self::$default_settings[$id]['id'], + $value, + self::$default_settings[$id]['type'], + self::$default_settings[$id]['label'], + $overwrite, true ); return true; } /** - * Magis setSetting + * Magic setSetting */ - public function __set($k, $v) + public function __set($id, $value) { - return $this->setSetting($k, $v); + return $this->setSetting($id, $value); } //@} /// @name modules methods //@{ - /** - * Retrieve a particular info for a given module - * - * @param string $id The module id - * @param string $info The module info - * @return mixed The module info value or null - */ - public function moduleInfo(string $id, string $info) - { - if (isset($this->modules['plugin'][$id])) { - $type = 'plugin'; - } elseif (isset($this->modules['theme'][$id])) { - $type = 'theme'; - } else { - return null; - } - - if ($info == 'type') { - return $type; - } - - return isset($this->modules[$type][$id][$info]) ? $this->modules[$type][$id][$info] : null; - } - /** * Load array of modules infos by type of modules */ private function loadModules() { - $themes = new dcThemes($this->core); - $themes->loadModules($this->core->blog->themes_path, null); $this->modules['theme'] = $this->modules['plugin'] = []; - $m = $themes->getModules(); - foreach($m AS $k => $v) { - if (!$v['root_writable']) { - continue; - } - $this->modules['theme'][$k] = $v; - $this->modules['theme'][$k]['id'] = $k; - $this->modules['theme'][$k]['type'] = 'theme'; - } + $themes = new dcThemes($this->core); + $themes->loadModules($this->core->blog->themes_path, null); - $m = $this->core->plugins->getModules(); - foreach($m AS $k => $v) { - if (!$v['root_writable']) { - continue; + $list = [ + 'theme' => $themes->getModules(), + 'plugin' => $this->core->plugins->getModules() + ]; + foreach($list as $type => $modules) { + foreach($modules as $id => $info) { + if (!$info['root_writable']) { +// continue; + } + $info['id'] = $id; + $info['type'] = $type; + $this->modules[$type][$id] = new dcTranslaterModule($this, $info); } - $this->modules['plugin'][$k] = $v; - $this->modules['plugin'][$k]['id'] = $k; - $this->modules['plugin'][$k]['type'] = 'plugin'; } } /** * Return array of modules infos by type of modules * - * @param string $type The module type - * @return array The list of modules infos + * @param string $type The modules type + * @return array The list of modules infos */ - public function listModules(string $type = '') + public function getModules(string $type = ''): array { return in_array($type, ['plugin', 'theme']) ? $this->modules[$type] : @@ -331,1276 +289,62 @@ class dcTranslater } /** - * Return array object of a particular module for a given type of module + * Return module class of a particular module for a given type of module * - * @param string $id The module id - * @param string $type The module type - * @return ArrayObject The module info + * @param string $type The module type + * @param string $id The module id + * @return dcTranslaterModule The dcTranslaterModule instance */ - public function getModule(string $id = '', string $type = '') + public function getModule(string $type, string $id) { - $o = new ArrayObject(); - - # Load nothing? - if (empty($id)) { - return false; - } - - # Unknow type? - $modules = !in_array($type, ['plugin', 'theme']) ? - array_merge($this->modules['theme'], $this->modules['plugin']) : - $this->modules[$type]; - - # Unknow module? - if (!isset($modules[$id])) { + if (!isset($this->modules[$type][$id])) { throw new Exception( sprintf(__('Cannot find module %s'), $id) ); return false; } + return $this->modules[$type][$id]; + } - # Module info - foreach($modules[$id] as $a => $b) { - $o->{$a} = $b; + /** + * Return module class of a particular module for a given type of module + * + * @param string $module dcTranslaterModule instance + * @param string $lang The lang iso code + * @return dcTranslaterLang dcTranslaterLang instance or false + */ + public function getLang(dcTranslaterModule $module, string $lang) + { + if (!l10n::isCode($lang)) { + throw new Exception( + sprintf(__('Cannot find lang %s'), $lang) + ); + return false; } - $o->root = path::real($o->root); - # Locales path - $o->locales = $o->root . '/locales'; - # Module exists - $o->exists = true; - # Module Basename - $i = path::info($o->root); - $o->basename = $i['basename']; - - return $o; + return new dcTranslaterLang($module, $lang); } //@} - /** - * Find backup folder of a module + /// @name helper methods + //@{ + /** + * Scan recursively a folder and return files and folders names * - * @param string $id The module id - * @param boolean $throw Silently failed - * @return mixed The backup folder directory or false + * @param string $path The path to scan + * @param string $dir Internal recursion + * @param array $res Internal recursion + * @return array List of path */ - public function getBackupFolder(string $id, bool $throw = false) + public static function scandir(string $path, string $dir = '', array $res = []) { - $dir = false; - switch($this->backup_folder) { - case 'module': - # plugin - if (isset($this->modules['plugin'][$id]) - && $this->modules['plugin'][$id]['root_writable'] - ) { - $dir = path::real($this->modules['plugin'][$id]['root']) . '/locales'; - #theme - } elseif (isset($this->modules['theme'][$id]) - && $this->modules['theme'][$id]['root_writable'] - ) { - $dir = path::real($this->modules['theme'][$id]['root']) . '/locales'; - } - break; - - case 'plugin': - $tmp = path::real(array_pop(explode(PATH_SEPARATOR, DC_PLUGINS_ROOT))); - if ($tmp && is_writable($tmp)) { - $dir = $tmp; - } - break; - - case 'public': - $tmp = path::real($this->core->blog->public_path); - if ($tmp && is_writable($tmp)) { - $dir = $tmp; - } - break; - - case 'cache': - $tmp = path::real(DC_TPL_CACHE); - if ($tmp && is_writable($tmp)) { - @mkDir($tmp . '/l10n'); - $dir = $tmp . '/l10n'; - } - break; - - case 'translater': - $tmp = path::real($this->modules['plugin']['translater']['root']); - if ($tmp && is_writable($tmp)) { - @mkDir($tmp . '/locales'); - $dir = $tmp . '/locales'; - } - break; - } - if (!$dir && $throw) { - throw new Exception(sprintf( - __('Cannot find backups folder for module %s'), $id - )); - } - - return $dir; - } - - /** - * Find language path of a module - * - * @param string $id The module id - * @param boolean $throw Silently failed - * @return mixed The module language path or false - */ - public function getLangsFolder(string $id = '', bool $throw = false) - { - $dir = $id == 'dotclear' ? - DC_ROOT : - self::getModuleFolder($id, false); - - if (!$dir && $throw) { - throw new Exception( - sprintf(__('Cannot find languages folder for module %s'), $id) - ); - } - - return !$dir ? false : $dir . '/locales'; - } - - /** - * Find root path of a module - * - * @param string $module The module id - * @param boolean $throw Silently failed - * @return mixed The module root path or false - */ - public function getModuleFolder($id = '', $throw = false) - { - $dir = false; - if ((isset($this->modules['plugin'][$id]['root']) - && ($tmp = path::real($this->modules['plugin'][$id]['root']))) - || (isset($this->modules['theme'][$id]['root']) - && ($tmp = path::real($this->modules['theme'][$id]['root'])) - )) { - $dir = $tmp; - } - if (!$dir && $throw) { - throw new Exception( - sprintf(__('Cannot find root folder for module %s'), $id) - ); - } - - return $dir; - } - - /** - * Check limit number of backup for a module - * - * @param string $id The module id - * @param boolean $throw Silently failed - * @return boolean True if limit is riched - */ - public function isBackupLimit(string $id, bool $throw = false): bool - { - # Find folder of backups - $backup = self::getBackupFolder($id, true); - - # Count backup files - $count = 0; - foreach(self::scandir($backup) AS $file) { - if (!is_dir($backup . '/' . $file) - && preg_match('/^(l10n-'. $id . '(.*?).bck.zip)$/', $backup) - ) { - $count++; - } - } - - # Limite exceed - if ($count >= $this->backup_limit) { - if ($throw) { - throw new Exception( - sprintf(__('Limit of %s backups for module %s exceed'), $this->backup_limit, $id) - ); - } - return true; - } - return false; - } - - /** - * Get a list of available backups - * - * @param string $id The module id - * @param bool|boolean $return_filename Return only filenames - * @return array The module backups info - */ - public function listBackups(string $id, bool $return_filename = false): array - { - # Not a module installed - self::getLangsFolder($id, true); - - # No backup folder for this module - $backup = self::getBackupFolder($id, false); - if (!$backup) { + $path = path::real($path, false); + if (!is_dir($path) || !is_readable($path)) { return []; } - # Scan files for backups - $res = $sort = []; - $files = self::scandir($backup); - foreach($files AS $file) { - # Not a bakcup file - $is_backup = preg_match( - '/^(l10n-(' . $id . ')-(.*?)-([0-9]*?).bck.zip)$/', $file, $m - ); - - if (is_dir($backup . '/' . $file) - || !$is_backup - || !self::isIsoCode($m[3]) - ) { - continue; - } - - # Backup infos - if ($return_filename) { - $res[] = $file; - } else { - $res[$m[3]][$file] = path::info($backup . '/' . $file); - $res[$m[3]][$file]['time']= filemtime($backup . '/' . $file); - $res[$m[3]][$file]['size'] = filesize($backup . '/' . $file); - $res[$m[3]][$file]['module'] = $id; - } - } - return $res; - } - - /** - * Create a backup - * - * @param string $id The module id - * @param string $lang The backup lang - * @return boolean True on success - */ - public function createBackup(string $id, string $lang): bool - { - # Not a module installed - $locales = self::getLangsFolder($id, true); - - # No backup folder for this module - $backup = self::getBackupFolder($id, true); - - # Not an existing lang - if (!is_dir($locales . '/' . $lang)) { - throw new Exception(sprintf( - __('Cannot find language folder %s for module %s') ,$lang, $id - )); - } - - # Scan files for a lang - $res = []; - $files = self::scandir($locales . '/' . $lang); - foreach($files as $file) { - # Only lang file - if (!is_dir($locales . '/' . $lang . '/' . $file) - && (self::isLangphpFile($file) - || self::isPoFile($file))) { - $res[$locales . '/' . $lang . '/' . $file] = - $id . '/locales/' . $lang . '/' . $file; - } - } - - # Do Zip - if (!empty($res)) { - self::isBackupLimit($id, true); - - @set_time_limit(300); - $fp = fopen($backup . '/l10n-' . $id . '-' . $lang . '-' . time() . '.bck.zip', 'wb'); - $zip = new fileZip($fp); - foreach($res AS $src => $dest) { - $zip->addFile($src, $dest); - } - $zip->write(); - $zip->close(); - unset($zip); - - return true; - } - } - - /** - * Delete a module backup - * - * @param string $id The module id - * @param string $file The backup filename - * @return boolean True on success - */ - public function deleteBackup(string $id, string $file): bool - { - # Not a module installed - self::getLangsFolder($id, true); - - # No backup folder for this module - $backup = self::getBackupFolder($id, true); - - # Not a bakcup file - $is_backup = preg_match('/^(l10n-(' . $id . ')-(.*?)-([0-9]*?).bck.zip)$/', $file, $m); - - if (is_dir($backup . '/' . $file) - || !$is_backup - || !self::isIsoCode($m[3]) - ) { - return false; - } - - if (!files::isDeletable($backup . '/' . $file)) { - throw new Exception(sprintf( - __('Cannot delete backup file %s'), $file - )); - } - - unlink($backup . '/' . $file); - - return true; - } - - /** - * Retore a backup - * - * @param string $module The module id - * @param string $file The backup filename - * @return boolean True on success - */ - public function restoreBackup(string $id, string $file): bool - { - # Not a module installed - $locales = self::getModuleFolder($id, true); - - # No backup folder for this module - $backup = self::getBackupFolder($id, true); - - if (!file_exists($backup . '/' . $file)) { - throw new Exception(sprintf( - __('Cannot find backup file %s'), $file - )); - } - - $zip = new fileUnzip($backup . '/' . $file); - $zip_files = $zip->getFilesList(); - - foreach($zip_files AS $zip_file) { - $f = self::explodeZipFilename($zip_file, true); - if ($id != $f['module']) { - continue; - } - - $zip->unzip($zip_file, $locales . '/locales/' . $f['lang'] . '/' . $f['group'] . $f['ext']); - $done = true; - } - $zip->close(); - unset($zip); - - return true; - } - - /** - * Export (to output) language pack - * - * @param array $modules The modules to work on - * @param array $langs Langs to export - */ - public function exportPack(array $modules, array $langs) - { - # Not a query well formed - if (!is_array($modules) || 1 > count($modules) - || !is_array($langs) || 1 > count($langs) - ) { - throw new Exception( - __('Wrong export query') - ); - } - - # Filter default filename - $filename = files::tidyFileName($this->export_filename); - - # Not a filename good formed - if (empty($filename)) { - throw new Exception( - sprintf(__('Cannot use export mask %s'), $this->export_filename) - ); - } - - # Modules folders - $res = $count = []; - foreach($modules AS $module) { - # Not a module installed - $locales = self::getLangsFolder($module, false); - if (!$locales) { - continue; - } - - # Langs folders - foreach($langs AS $lang) { - # Not a lang folder - if (!is_dir($locales . '/' . $lang)) { - continue; - } - - # Scan files for a lang - $files = self::scandir($locales . '/' . $lang); - foreach($files as $file) { - # Not a lang file - if (is_dir($locales . '/' . $lang . '/' . $file) - || !self::isLangphpFile($file) - && !self::isPoFile($file) - ) { - continue; - } - - # Add file to zip in format "module/locales/lang/filename" - $res[$locales . '/' . $lang . '/' . $file] = - $module . '/locales/' . $lang . '/' . $file; - - $count[$module] = 1; - } - } - } - - # Nothing to do - if (empty($res)) { - throw new Exception('Nothing to export'); - } - - # Prepare files to zip - @set_time_limit(300); - $fp = fopen('php://output', 'wb'); - $zip = new fileZip($fp); - foreach($res as $from => $to) { - $zip->addFile($from,$to); - } - - # Set filename - $file_infos = 1 < count($count) ? - [time(), 'modules', 'multi', self::$dcTranslaterVersion] : - [ - time(), - $modules[0], - self::moduleInfo($modules[0], 'type'), - self::moduleInfo($modules[0], 'version') - ]; - $filename = - files::tidyFileName( - dt::str( - str_replace( - ['timestamp', 'module', 'type', 'version'], - $file_infos, - $this->export_filename - ) - ) - ); - - # Send Zip - header('Content-Disposition: attachment;filename=' . $filename . '.zip'); - header('Content-Type: application/x-zip'); - $zip->write(); - unset($zip); - exit; - } - - /** - * Import a language pack - * - * @param array $modules The modules to work on - * @param array $zip_file The uploaded file info - * @return boolean True on success - */ - public function importPack($modules, $zip_file) - { - # Not a file uploaded - files::uploadStatus($zip_file); - - # No modules to update - if (!is_array($modules) || 1 > count($modules)) { - throw new Exception(__('Wrong import query')); - } - - $done = false; - $res = []; - - # Load Unzip object - $zip = new fileUnzip($zip_file['tmp_name']); - $files = $zip->getFilesList(); - - # Scan zip - foreach($files AS $file) { - $f = self::explodeZipFilename($file, false); - - # Not a requested module - if (!is_array($f) - || !in_array($f['module'],$modules) - ) { - continue; - } - - # Get locales folder (even if "locales" is not set) - if (!$dir = self::getModuleFolder($f['module'], false)) { - continue; - } - $locales = $dir . '/locales'; - - # Not allow overwrite - if (!$this->import_overwrite - && file_exists($locales . '/' . $f['lang'] . '/' . $f['group'] . $f['ext']) - ) { - continue; - } - - $res[] = [ - 'from' => $file, - 'root' => $locales . '/' . $f['lang'], - 'to' => $locales . '/' . $f['lang'] . '/' . $f['group'] . $f['ext'] - ]; - } - # Unzip files - foreach ($res AS $rs) { - if (!is_dir($rs['root'])) { - files::makeDir($rs['root'], true); - } - - $zip->unzip($rs['from'], $rs['to']); - $done = true; - } - $zip->close(); - unlink($zip_file['tmp_name']); - - # No file unzip - if (!$done) { - throw new Exception( - sprintf(__('Nothing to import for these modules in pack %s'), $zip_file['name']) - ); - } - return true; - } - - /** - * Parse zip filename to module, lang info - * - * @param string $file The zip filename - * @param boolean $throw Silently failed - * @return mixed Array of file info or false - */ - public function explodeZipFilename(string $file = '', bool $throw = false) - { - # module/locales/lang/group.ext - $is_file = preg_match( - '/^(.*?)\/locales\/(.*?)\/(.*?)(.po|.lang.php)$/', $file, $f - ); - - # Explode file to infos - if ($is_file) { - $module = null !== self::moduleInfo($f[1], 'name') ? - $f[1] : false; - $lang = self::isIsoCode($f[2]) ? - $f[2] : false; - $group = in_array($f[3], self::$allowed_l10n_groups) ? - $f[3] : false; - $ext = self::isLangphpFile($f[4]) || self::isPoFile($f[4]) ? - $f[4] : false; - } - # Not good formed - if (!$is_file || !$module || !$lang || !$group || !$ext) { - if ($throw) { - throw new Exception( - sprintf(__('Zip file %s is not in translater format'), $file) - ); - } - return false; - } - return [ - 'module' => $module, - 'lang' => $lang, - 'group' => $group, - 'ext' => $ext - ]; - } - - /** - * List available langs of a module - * - * @param string $id The module id - * @param boolean $return_path Return path or name - * @return array The lang list - */ - public function listLangs(string $id, bool $return_path = false) - { - $res = []; - - # Not a module installed - $locales = self::getLangsFolder($id, true); - - # Add prefix "locales" as scandir remove it - $prefix = preg_match('/(locales(.*))$/', $locales) ? 'locales' : ''; - - # Retrieve langs folders - $files = self::scandir($locales); - foreach($files as $file) { - if (!preg_match( - '/(.*?(locales\/)([^\/]*?)\/([^\/]*?)(.lang.php|.po))$/', - $prefix . $file, $m - )) { - continue; - } - - if (!self::isIsoCode($m[3])) { - continue; - } - - if ($return_path) { - $res[$m[3]][] = $file; # Path - } else { - $res[$m[3]] = self::$iso[$m[3]]; # Lang name - } - } - return $res; - } - - /** - * Add a lang to a module - * - * @param string $module The module id - * @param string $lang The lang id - * @param string $from_lang The lang to copy from - */ - public function addLang(string $id, string $lang, string $from_lang = '') - { - # Not a module installed - $locales = self::getLangsFolder($id, true); - - # Path is right formed - self::isIsoCode($lang, true); - - # Retrieve langs folders - $langs = self::listLangs($id); - - # Lang folder is not present - if (isset($langs[$lang])) { - throw new Exception( - sprintf(__('Language %s already exists for module %s'), $lang, $id) - ); - } - - # Create new lang directory - files::makeDir($locales . '/' . $lang, true); - - # Verify folder of other lang - if (!empty($from_lang) && !isset($langs[$from_lang])) { - throw new Exception( - sprintf(__('Cannot copy file from language %s for module %s'), $from_lang, $id) - ); - } - - # Copy files from other lang - if (!empty($from_lang) - && isset($langs[$from_lang]) - ) { - $files = self::scandir($locales . '/' . $from_lang); - foreach($files as $file) { - if (is_dir($locales . '/' . $from_lang . '/' . $file) - || !self::isLangphpFile($file) - && !self::isPoFile($file) - ) { - continue; - } - - files::putContent($locales . '/' . $lang . '/' . $file, - file_get_contents($locales . '/' . $from_lang . '/' . $file) - ); - } - } else { - # Create basic empty lang file as translater need these files to be present - self::setLangphpFile($id, $lang, 'main', []); - self::setPoFile($id, $lang, 'main', []); - } - } - - /** - * Update an existing lang - * - * @param string $module The module id - * @param string $lang The lang - * @param array $msgs The messages - */ - public function updLang(string $id, string $lang, array $msgs) - { - # Not a module installed - $locales = self::getLangsFolder($id, true); - - # Path is right formed - self::isIsoCode($lang, true); - - # Retrieve langs folders - $langs = self::listLangs($id); - - # Lang folder is not present - if (!isset($langs[$lang])) { - throw new Exception(sprintf( - __('Cannot find language folder %s for module %s'), $lang, $id - )); - } - - # Sort msgids by groups - $rs = []; - foreach($msgs as $msg) { - $msg['group'] = isset($msg['group']) ? $msg['group'] : ''; - $msg['msgid'] = isset($msg['msgid']) ? $msg['msgid'] : ''; - $msg['msgstr'] = isset($msg['msgstr']) ? trim($msg['msgstr']) : ''; -/* - if (get_magic_quotes_gpc()) { - $msg['msgid'] = stripcslashes($msg['msgid']); - $msg['msgstr'] = stripcslashes($msg['msgstr']); - } -*/ if ($msg['msgstr'] == '') { - continue; - } - - $rs[$msg['group']][$msg['msgid']] = $msg['msgstr']; - } - - # Backup files if auto-backup is on - if ($this->backup_auto) { - self::createBackup($id, $lang); - } - - # Delete empty files (files with no group) - foreach(self::$allowed_l10n_groups AS $group) { - if (isset($rs[$group])) { - continue; - } - - $po_file = $locales . '/' . $lang . '/' . $group . '.po'; - $langphp_file = $locales . '/' . $lang . '/' . $group . '.lang.php'; - - if (file_exists($po_file)) { - unlink($po_file); - } - if (file_exists($langphp_file)) { - unlink($langphp_file); - } - } - - # No msgstr to write - if (empty($rs)) { - throw new Exception(sprintf( - __('No string to write, language %s deleted for module %s'), - $lang, $id - )); - } - - # Write .po and .lang.php files - foreach($rs AS $group => $msgs) { - self::setLangphpFile($id, $lang, $group, $msgs); - self::setPoFile($id, $lang, $group, $msgs); - } - } - - public function delLang($module, $lang, $del_empty_dir = true) - { - # Not a module installed - $locales = self::getLangsFolder($module, true); - - # Path is right formed - self::isIsoCode($lang, true); - - # Retrieve langs folders - $files = self::listLangs($module, true); - - # Lang folder is not present - if (!isset($files[$lang])) { - throw new Exception(sprintf( - __('Cannot find language folder %s for module %s'), $lang, $module - )); - } - - # Delete .po and .lang.php files - foreach($files[$lang] as $file) { - unlink($locales . '/' . $file); - } - - # Delete lang folder if empty - $dir = self::scandir($locales . '/' . $lang); - if (empty($dir)) { - rmdir($locales . '/' . $lang); - } - - # Delete locales folder if empty - $loc = self::scandir($locales); - if (empty($loc)) { - rmdir($locales); - } - } - - public static function encodeMsg($str) - { - return text::toUTF8(stripslashes(trim($str))); - } - - /* Scan a module folder to find all l10n strings in .php files */ - public function getMsgIds($module) - { - $res = array(); - - # Not a module installed - $dir = self::getModuleFolder($module, true); - - $files = self::scandir($dir); - - $scan_ext = array('php'); - if ($this->scan_tpl) { - $scan_ext[] = 'html'; - } - - foreach($files AS $file) { - if (is_dir($dir . '/' . $file) - || !in_array(files::getExtension($file),$scan_ext)) { - continue; - } - - $contents = file($dir . '/' . $file); - foreach($contents AS $line => $content) { - # php files - //if (preg_match_all("|__\((['\"]{1})(.*)([\"']{1})\)|U", $content, $matches)) - if (preg_match_all("|__\((['\"]{1})(.*?)([\"']{1})\)|", $content, $matches)) { - foreach($matches[2] as $id) { - $res[] = array( - 'msgid' => self::encodeMsg($id), - 'file' => $file, - 'line' => $line + 1 - ); - } - } - # tpl files - if ($this->scan_tpl - && preg_match_all('/\{\{tpl:lang\s([^}]+)\}\}/', $content, $matches)) { - foreach($matches[1] as $id) { - $res[] = array( - 'msgid' => self::encodeMsg($id), - 'file' => $file, - 'line' => $line + 1 - ); - } - } - } - unset($contents); - } - return $res; - } - - /* Scan a lang folder to find l10n translations in files */ - public function getMsgStrs($module, $requested_lang = '') - { - $res = array(); - - # Not a module installed - $locales = self::getLangsFolder($module, true); - - $langs = self::listLangs($module, true); - - # Not an existing lang - if (!isset($langs[$requested_lang])) { - return $res; - } - - # Lang files - $exists = array(); - foreach($langs[$requested_lang] as $file) { - if (in_array($file,$exists)) { - continue; - } - $exists[] = $file; - $path = path::clean($locales . '/' . $file); - - # .po files - if (self::isPoFile($file)) { - $po = self::getPoFile($path); - if (!is_array($po)) { - continue; - } - - foreach($po as $id => $str) { - $is_po[$requested_lang][$id] = 1; - - $res[] = array( - 'msgid' => self::encodeMsg($id), - 'msgstr' => self::encodeMsg($str), - 'lang' => $requested_lang, - 'type' => 'po', - 'path' => $path, - 'file' => basename($file), - 'group'=> str_replace('.po', '', basename($file)) - ); - } - # .lang.php files - } elseif (self::isLangphpFile($file)) { - $php = self::getLangphpFile($path); - foreach($php AS $id => $str) { - # Don't overwrite .po - if (isset($is_po[$requested_lang][$id])) { - continue; - } - $res[] = array( - 'msgid' => self::encodeMsg($id), - 'msgstr' => self::encodeMsg($str), - 'lang' => $requested_lang, - 'type' => 'php', - 'path' => $path, - 'file' => basename($file), - 'group'=> str_replace('.lang.php', '', basename($file)) - ); - } - } - } - return $res; - } - - public function getMsgs($module, $requested_lang = '') - { - # Get messages ids of a module - $m_msgids = self::getMsgIds($module); - - # Get messages translations for a module - $m_msgstrs = self::getMsgStrs($module, $requested_lang); - - # Get messages translations for others modules - foreach(self::listModules() AS $o_module => $o_infos) { - if ($o_module == $module) continue; - $m_o_msgstrs[$o_module] = self::getMsgStrs($o_module, $requested_lang); - } - $m_o_msgstrs['dotclear'] = self::getMsgStrs('dotclear', $requested_lang); - - # Only one lang or all - $langs = '' == $requested_lang ? - self::listLangs($module) : - array($requested_lang => self::isIsoCode($requested_lang)); - - # Let's go reorder the mixture - $res = array(); - foreach($langs AS $lang => $iso) { - $res[$lang] = array(); - - # From id list - foreach($m_msgids AS $rs) { - $res[$lang][$rs['msgid']]['files'][] = array(trim($rs['file'],'/'), $rs['line']); - $res[$lang][$rs['msgid']]['group'] = 'main'; - $res[$lang][$rs['msgid']]['msgstr'] = ''; - $res[$lang][$rs['msgid']]['in_dc'] = false; - $res[$lang][$rs['msgid']]['o_msgstrs'] = array(); - } - - # From str list - foreach($m_msgstrs AS $rs) { - if ($rs['lang'] != $lang) { - continue; - } - - if (!isset($res[$lang][$rs['msgid']])) { - $res[$lang][$rs['msgid']]['files'][] = array(); - $res[$lang][$rs['msgid']]['in_dc'] = false; - $res[$lang][$rs['msgid']]['o_msgstrs'] = array(); - } - $res[$lang][$rs['msgid']]['group'] = $rs['group']; - $res[$lang][$rs['msgid']]['msgstr'] = $rs['msgstr']; - $res[$lang][$rs['msgid']]['in_dc'] = false; - } - - # From others str list - foreach($m_o_msgstrs AS $o_module => $o_msgstrs) { - foreach($o_msgstrs AS $rs) { - if ($rs['lang'] != $lang) { - continue; - } - - if (!isset($res[$lang][$rs['msgid']])) { - continue; - } - - $res[$lang][$rs['msgid']]['o_msgstrs'][] = array( - 'msgstr' => $rs['msgstr'], - 'module' => $o_module, - 'file' => $rs['file'] - ); - if ($o_module == 'dotclear') { - $res[$lang][$rs['msgid']]['in_dc'] = true; - } - } - } - } - return '' == $requested_lang ? $res : $res[$requested_lang]; - } - - /* Write a lang file */ - private function writeLangFile($dir, $content, $throw) - { - $path = path::info($dir); - if (is_dir($path['dirname']) && !is_writable($path['dirname']) - || file_exists($dir) && !is_writable($dir)) { - throw new Exception(sprintf( - __('Cannot grant write acces on lang file %s'), $dir - )); - } - - # -- BEHAVIOR -- dcTranslaterBeforeWriteLangFile - $this->core->callBehavior('dcTranslaterBeforeWriteLangFile', $dir, $content, $throw); - - $f = @files::putContent($dir,$content); - if (!$f && $throw) { - throw new Exception(sprintf( - __('Cannot write lang file %s'), $dir - )); - } - - # -- BEHAVIOR -- dcTranslaterAfterWriteLangFile - $this->core->callBehavior('dcTranslaterAfterWriteLangFile', $f, $dir, $content, $throw); - - return $f; - } - - /* Try if a file is a .lang.php file */ - public static function isLangphpFile($file) - { - return files::getExtension($file) == 'php' && stristr($file, '.lang.php'); - } - - /* Get and parse a .lang.php file */ - public static function getLangphpFile($file) - { - if (!file_exists($file)) { - return array(); - } - - $res = array(); - $content = implode('',file($file)); - $count = preg_match_all('/(\$GLOBALS\[\'__l10n\'\]\[\'(.*?)\'\]\s*\x3D\s*\'(.*?)\';)/', $content, $m); - - if (!$count) { - return array(); - } - - for ($i = 0; $i < $count; $i++) { - $id = $m[2][$i]; - $str = self::langphpString($m[3][$i]); - - if ($str) { - $res[self::langphpString($id)] = $str; - } - } - if (!empty($res[''])) { - $res = array_diff_key($res,array(''=>1)); - } - return $res; - } - - /* Construct and write a .lang.php file */ - private function setLangphpFile($module, $lang, $group, $fields) - { - if (!$this->write_langphp) { - return; - } - - # Not a module installed - $locales = self::getLangsFolder($module, true); - - # Path is right formed - $lang_name = self::isIsoCode($lang, true); - - $l = "parse_comment) { - $l .= - '// Language: ' . $lang_name . " \n" . - '// Module: ' . $module . " - " . self::moduleInfo($module, 'version') . "\n" . - '// Date: ' . dt::str('%Y-%m-%d %H:%M:%S') . " \n"; - - if ($this->parse_user && !empty($this->parse_userinfo)) { - $search = self::$allowed_user_informations; - foreach($search AS $n) { - $replace[] = $this->core->auth->getInfo('user_' . $n); - } - $info = trim(str_replace($search,$replace,$this->parse_userinfo)); - if (!empty($info)) { - $l .= '// Author: ' . html::escapeHTML($info) . "\n"; - } - } - $l .= - '// Translated with dcTranslater - ' . $this->core->plugins->moduleInfo('translater', 'version') . " \n\n"; - } - if ($this->parse_comment) { - $infos = self::getMsgids($module); - foreach($infos AS $info) { - if (isset($fields[$info['msgid']])) { - $comments[$info['msgid']] = (isset($comments[$info['msgid']]) ? - $comments[$info['msgid']] : '') . - '#'.trim($info['file'],'/') . ':' . $info['line'] . "\n"; - } - } - } - - foreach($fields as $id => $str) { - if ($this->parse_comment && isset($comments[$id])) { - $l .= $comments[$id]; - } - - $l .= - '$GLOBALS[\'__l10n\'][\'' . addcslashes($id, "'") . '\'] = ' . - '\'' . self::langphpString($str, true) . "';\n"; - - if ($this->parse_comment) { - $l .= "\n"; - } - } - $l .= ""; - - self::writeLangFile($locales . '/' . $lang . '/' . $group . '.lang.php', $l, true); - } - - /* Parse a .lang.php string */ - private static function langphpString($string, $reverse = false) - { - if ($reverse) { - $smap = array('\'', "\n", "\t", "\r"); - $rmap = array('\\\'', '\\n"' . "\n" . '"', '\\t', '\\r'); - return trim((string) str_replace($smap, $rmap, $string)); - } else { - $smap = array('/\\\\n/', '/\\\\r/', '/\\\\t/', "/\\\'/"); - $rmap = array("\n", "\r", "\t", "'"); - return trim((string) preg_replace($smap, $rmap, $string)); - } - } - - /* Try if a file is a .po file */ - public static function isPoFile($file) - { - return files::getExtension($file) == 'po'; - } - - /* Get and parse a .po file */ - public static function getPoFile($file) - { - if (!file_exists($file)) { - return false; - } - - $res = array(); - $content = implode('',file($file)); - - $count = preg_match_all('/msgid\s(.*(?:\n".*")*)\nmsgstr\s(.*(?:\n".*")*)/', $content, $m); - - if (!$count) return false; - - for ($i = 0; $i < $count; $i++) { - $id = preg_replace('/"(.*)"/s','\\1', $m[1][$i]); - $str= preg_replace('/"(.*)"/s','\\1', $m[2][$i]); - - $str = self::poString($str); - - if ($str) { - $res[self::poString($id)] = $str; - } - } - - if (!empty($res[''])) { - $res = array_diff_key($res, array(''=>1)); - } - return $res; - } - /* Construct and parse a .po file */ - private function setPoFile($module, $lang, $group, $fields) - { - if (!$this->write_po) { - return; - } - - # Not a module installed - $locales = self::getLangsFolder($module, true); - - # Path is right formed - self::isIsoCode($lang, true); - - $l = ''; - if ($this->parse_comment) { - $l .= - '# Language: ' . self::$iso[$lang] . "\n" . - '# Module: ' . $module . " - " . self::moduleInfo($module, 'version') . "\n" . - '# Date: ' . dt::str('%Y-%m-%d %H:%M:%S') . "\n"; - - if ($this->parse_user && !empty($this->parse_userinfo)) { - $search = self::$allowed_user_informations; - foreach($search AS $n) { - $replace[] = $this->core->auth->getInfo('user_' . $n); - } - $info = trim(str_replace($search, $replace, $this->parse_userinfo)); - if (!empty($info)) { - $l .= '# Author: ' . html::escapeHTML($info) . "\n"; - } - } - $l .= - '# Translated with translater ' . $this->core->plugins->moduleInfo('translater', 'version') . "\n"; - } - $l .= - "\n". - "msgid \"\"\n" . - "msgstr \"\"\n" . - '"Content-Type: text/plain; charset=UTF-8\n"' . "\n" . - '"Project-Id-Version: ' . $module . ' ' . self::moduleInfo($module, 'version') . '\n"' . "\n" . - '"POT-Creation-Date: \n"' . "\n" . - '"PO-Revision-Date: ' . date('c') . '\n"' . "\n" . - '"Last-Translator: ' . $this->core->auth->getInfo('user_cn') . '\n"' . "\n" . - '"Language-Team: \n"' . "\n" . - '"MIME-Version: 1.0\n"' . "\n" . - '"Content-Transfer-Encoding: 8bit\n"' . "\n" . - '"Plural-Forms: nplurals=2; plural=(n > 1);\n"' . "\n\n"; - - if ($this->parse_comment) { - $infos = self::getMsgids($module); - foreach($infos AS $info) { - if (isset($fields[$info['msgid']])) { - $comments[$info['msgid']] = (isset($comments[$info['msgid']]) ? - $comments[$info['msgid']] : '') . - '#: '.trim($info['file'],'/') . ':' . $info['line'] . "\n"; - } - } - } - - foreach($fields as $id => $str) { - if ($this->parse_comment && isset($comments[$id])) { - $l .= $comments[$id]; - } - $l .= - 'msgid "' . self::poString($id, true) . '"' . "\n" . - 'msgstr "' . self::poString($str, true) . '"' . "\n\n"; - } - - self::writeLangFile($locales . '/' . $lang . '/' . $group . '.po', $l, true); - } - - /* Parse .po string */ - private static function poString($string, $reverse = false) - { - if ($reverse) { - $smap = array('"', "\n", "\t", "\r"); - $rmap = array('\\"', '\\n"' . "\n" . '"', '\\t', '\\r'); - return trim((string) str_replace($smap, $rmap, $string)); - } else { - $smap = array('/"\s+"/', '/\\\\n/', '/\\\\r/', '/\\\\t/', '/\\\"/'); - $rmap = array('', "\n", "\r", "\t", '"'); - return trim((string) preg_replace($smap, $rmap, $string)); - } - } - - /* Scan recursively a folder and return files and folders names */ - public static function scandir($path, $dir = '', $res = array()) - { - $path = path::real($path); - if (!is_dir($path) || !is_readable($path)) { - return array(); - } - $files = files::scandir($path); - foreach($files AS $file) { - if ($file == '.' || $file == '..') { + if (in_array($file, ['.', '..'])) { continue; } @@ -1614,25 +358,130 @@ class dcTranslater return $res; } - /* Return array of langs like in clearbreaks l10n */ - public static function getIsoCodes($flip = false, $name_with_code = false) + /* Try if a file is a .lang.php file */ + public static function isLangphpFile($file) { - if (empty(self::$iso)) { - self::$iso = l10n::getISOcodes($flip, $name_with_code); - } - return self::$iso; + return files::getExtension($file) == 'php' && stristr($file, '.lang.php'); } - /* Find if lang code exists or lang name */ - public static function isIsoCode($iso, $throw = false) + /* Try if a file is a .po file */ + public static function isPoFile($file) { - $codes = self::getIsoCodes(); - $code = isset($codes[$iso]) ? $codes[$iso] : false; - if (!$code && $throw) { - throw new Exception(sprintf( - __('Cannot find language for code %s'), $iso - )); - } - return $code; + return files::getExtension($file) == 'po'; } + + /** + * Check limit number of backup for a module + * + * @param string $root The backups root + * @param string $limit The backups limit + * @param boolean $throw Silently failed + * @return boolean True if limit is riched + */ + public static function isBackupLimit(string $root, int $limit = 10, bool $throw = false): bool + { + $count = 0; + foreach(self::scandir($root) AS $file) { + if (!is_dir($root . '/' . $file) + && preg_match('/^(l10n-'. $id . '(.*?).bck.zip)$/', $root) + ) { + $count++; + } + } + + # Limite exceed + if ($count >= $limit) { + if ($throw) { + throw new Exception( + sprintf(__('Limit of %s backups for module %s exceed'), $this->backup_limit, $id) + ); + } + return true; + } + return false; + } + + /** + * Extract messages from a php contents + * + * support plurals + * + * @param string $content The contents + * @param string $func The function name + * @return array The messages + */ + public static function extractPhpMsgs(string $content, string $func = '__'): array + { + $duplicate = $final_strings = $lines = []; + // split content by line to combine match/line on the end + $content = str_replace("\r\n", "\n", $content); + $o = 0; + $parts = explode("\n", $content); + foreach($parts as $li => $part) { + $m = explode($func . '(', $part); + for($i = 1; $i < count($m); $i++) { + $lines[$o] = $li+1; + $o++; + } + } + // split content by translation function + $parts = explode($func . '(', $content); + // remove fisrt element from array + array_shift($parts); + // put back first parenthesis + $parts = array_map(function($v){ return '(' . $v;}, $parts); + // walk through parts + $p = 0; + foreach($parts as $part) { + // find pairs of parenthesis + preg_match_all("/\((?:[^\)\(]+|(?R))*+\)/s", $part, $subparts); + // find quoted strings (single or double) + preg_match_all("/\'(?:[^\']+)\'|\"(?:[^\"]+)\"/s", $subparts[0][0], $strings); + // strings exist + if (!empty($strings[0])) { + // remove quotes + $strings[0] = array_map(function($v){ return substr($v, 1, -1);}, $strings[0]); + // filter duplicate strings (only check first string for plurals form) + if (!in_array($strings[0][0], $duplicate)) { + // fill final array + $final_strings[] = [$strings[0], $lines[$p]]; + $duplicate[] = $strings[0][0]; + } + } + $p++; + } + return $final_strings; + } + + /** + * Encode a string + * + * @param string $str The string to encode + * @return string The encoded string + */ + public static function encodeMsg(string $str): string + { + return text::toUTF8(stripslashes(trim($str))); + } + + /** + * Clean a po string + * + * @param string $string The string to clean + * @param boolean $reverse Un/escape string + * @return string The cleaned string + */ + public static function poString(string $string, bool $reverse = false): string + { + if ($reverse) { + $smap = array('"', "\n", "\t", "\r"); + $rmap = array('\\"', '\\n"' . "\n" . '"', '\\t', '\\r'); + return trim((string) str_replace($smap, $rmap, $string)); + } else { + $smap = array('/"\s+"/', '/\\\\n/', '/\\\\r/', '/\\\\t/', '/\\\"/'); + $rmap = array('', "\n", "\r", "\t", '"'); + return trim((string) preg_replace($smap, $rmap, $string)); + } + } + //@} } \ No newline at end of file diff --git a/index.php b/index.php index f112363..5390f18 100644 --- a/index.php +++ b/index.php @@ -17,1061 +17,655 @@ if (!defined('DC_CONTEXT_ADMIN')) { dcPage::checkSuper(); -# -# Init some variables -# - $translater = new dcTranslater($core); -$succes = [ - 'save_setting' => __('Configuration successfully updated'), - 'update_lang' => __('Translation successfully updated'), - 'add_lang' => __('Translation successfully created'), - 'delete_lang' => __('Translation successfully deleted'), - 'create_backup' => __('Backups successfully create'), - 'restore_backup' => __('Backups successfully restored'), - 'delete_backup' => __('Backups successfully deleted'), - 'import_pack' => __('Package successfully imported'), - 'export_pack' => __('Package successfully exported') -]; +$type = $_REQUEST['type'] ?? $translater->start_page ?: ''; +$module = $_REQUEST['module'] ?? ''; +$lang = $_REQUEST['lang'] ?? ''; +$action = $_POST['action'] ?? ''; -$errors = [ - 'save_setting' => __('Failed to update settings: %s'), - 'update_lang' => __('Failed to update translation: %s'), - 'add_lang' => __('Failed to create translation: %s'), - 'delete_lang' => __('Failed to delete translation: %s'), - 'create_backup' => __('Failed to create backups: %s'), - 'restore_backup' => __('Failed to restore backups: %s'), - 'delete_backup' => __('Failed to delete backups: %s'), - 'import_pack' => __('Failed to import package: %s'), - 'export_pack' => __('Failed to export package: %s') -]; - -$p_url = 'plugin.php?p=translater'; -$start_page = @explode('_', $translater->start_page); -if (count($start_page) < 2) { - $start_page[1] = ''; +if (!in_array($type, ['plugin', 'theme'])) { + $type = ''; } -# -# Parse request -# - -$msg = isset($_REQUEST['msg']) ? $_REQUEST['msg'] : ''; -$part = isset($_REQUEST['part']) ? $_REQUEST['part'] : $start_page[0]; -$tab = isset($_REQUEST['tab']) ? $_REQUEST['tab'] : ''; -$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : ''; -$type = isset($_REQUEST['type']) ? $_REQUEST['type'] : $start_page[1]; -$module = isset($_REQUEST['module']) ? $_REQUEST['module'] : ''; -$from = isset($_POST['from']) && $_POST['from'] != '-' ? $_POST['from'] : ''; -$lang = isset($_REQUEST['lang']) && $_REQUEST['lang'] != '-' ? $_REQUEST['lang'] : ''; - -if ($type == '-' || $module == '-') { - $type = $module = ''; -} - -# -# Manage action -# - -switch ($action) { - case '': - break; - - /** - * Create lang for a module - */ - case 'module_add_lang': - try { - if (empty($lang)) { - throw new Exception(__('No lang to create')); - } - $translater->addLang($module, $lang, $from); - - http::redirect($p_url . '&part=lang&module=' . $module . '&type=' . $type . '&lang=' . $lang . '&msg=add_lang'); - } catch (Exception $e) { - $core->error->add(sprintf($errors[$action], $e->getMessage())); - } - break; - - /** - * Delete lang for a module - */ - case 'module_delete_lang': - try { - if (empty($lang)) { - throw new Exception(__('No lang to delete')); - } - $translater->delLang($module, $lang); - - http::redirect($p_url . '&part=module&module=' . $module . '&type=' . $type . '&tab=module-lang&msg=delete_lang'); - } catch (Exception $e) { - $core->error->add(sprintf($errors[$action], $e->getMessage())); +if (!empty($type) && !empty($module)) { + try { + $module = $translater->getModule($type, $module); + } catch(Exception $e) { + $core->error->add($e->getMessage()); + $module = ''; } - break; +} - /** - * Create backup for a module - */ - case 'module_create_backup': - try { - if (empty($_POST['modules']) || empty($_POST['langs'])) { - throw new Exception(__('No lang to backup')); - } +if (!empty($module) && !empty($lang)) { + try { + $lang = $translater->getLang($module, $lang); + } catch(Exception $e) { + $core->error->add($e->getMessage()); + $lang = ''; + } +} - foreach($_POST['modules'] as $b_module) { - $b_list = $translater->listLangs($b_module); +$breadcrumb = [__('Translater') => $core->adminurl->get('translater')]; +if (empty($type)) { + $breadcrumb = [__('Translater') => '']; +} elseif (empty($module)) { + $breadcrumb[$type == 'plugin' ? __('Plugins') : __('Themes')] = ''; +} elseif (empty($lang)) { + $breadcrumb[$type == 'plugin' ? __('Plugins') : __('Themes')] = $core->adminurl->get('translater', ['type' => $type]); + $breadcrumb[html::escapeHTML($module->name)] = ''; +} elseif (!empty($lang)) { + $breadcrumb[$type == 'plugin' ? __('Plugins') : __('Themes')] = $core->adminurl->get('translater', ['type' => $type]); + $breadcrumb[html::escapeHTML($module->name)] = $core->adminurl->get('translater', ['type' => $type, 'module' => $module->id]); + $breadcrumb[html::escapeHTML(sprintf(__('%s language edition'), $lang->name))] = ''; +} - foreach($_POST['langs'] as $b_lang) { - if (isset($b_list[$b_lang])) { - $translater->createBackup($b_module, $b_lang); - } - } - } - - http::redirect($p_url . '&part=module&module=' . $module . '&type=' . $type . '&tab=module-backup&msg=creat_backup'); - } catch (Exception $e) { - $core->error->add(sprintf($errors[$action], $e->getMessage())); +try { + if ($action == 'module_create_backup') { + if (empty($module) || empty($_POST['codes'])) { + throw new Exception(__('No lang to backup')); } - break; - - /** - * Restore backup for a module - */ - case 'module_restore_backup': - try { - if (empty($_POST['modules']) || empty($_POST['files'])) { - throw New Exception(__('No blackup to restore')); + $module_codes = $module->getUsedlangs(); + foreach($module_codes as $code_id) { + if (in_array($code_id, $_POST['codes'])) { + $module->createBackup($code_id); } - - sort($_POST['files']); - $done = false; - foreach($_POST['modules'] as $b_module) { - $b_list = $translater->listBackups($b_module, true); - - foreach($_POST['files'] as $b_file) { - if (in_array($b_file, $b_list)) { - $translater->restoreBackup($b_module, $b_file); - $done = true; - } - } - } - if (!$done) { - throw new Exception(__('No bakcup to to restore')); - } - - http::redirect($p_url . '&part=module&module=' . $module . '&type=' . $type . '&tab=module-backup&msg=restore_backup'); - } catch (Exception $e) { - $core->error->add(sprintf($errors[$action], $e->getMessage())); } - break; + dcPage::addSuccessNotice(__('Backup successfully created')); + $core->adminurl->redirect('translater', ['type' => $type, 'module' => $module->id], '#module-backup'); + } - /** - * Delete backup for a module - */ - case 'module_delete_backup': - try { - if (empty($_POST['modules']) || empty($_POST['files'])) { - throw New Exception(__('No backup to delete')); - } - - $done = false; - foreach($_POST['modules'] as $b_module) { - $b_list = $translater->listBackups($b_module, true); - - foreach($_POST['files'] as $b_file) { - if (in_array($b_file, $b_list)) { - $translater->deleteBackup($b_module, $b_file); - $done = true; - } - } - } - if (!$done) { - throw new Exception(__('No backup to delete')); - } - - http::redirect($p_url . '&part=module&module=' . $module . '&type=' . $type . '&tab=module-backup&msg=delete_backup'); - } catch (Exception $e) { - $core->error->add(sprintf($errors[$action],$e->getMessage())); + if ($action == 'module_restore_backup') { + if (empty($module) || empty($_POST['files'])) { + throw New Exception(__('No backup to restore')); } - break; - - /** - * Import language package for a module - */ - case 'module_import_pack': - try { - if (empty($_FILES['packfile']['name'])) { - throw new Exception(__('Nothing to import')); + $module_backups = $module->getBackups(true); + foreach($module_backups as $backup_file) { + if (in_array($backup_file, $_POST['files'])) { + $module->restoreBackup($backup_file); } - $translater->importPack($_POST['modules'], $_FILES['packfile']); - - http::redirect($p_url . '&part=module&module=' . $module . '&type=' . $type . '&tab=module-pack&msg=import_pack'); - } catch (Exception $e) { - $core->error->add(sprintf($errors[$action], $e->getMessage())); } - break; + dcPage::addSuccessNotice(__('Backup successfully restored')); + $core->adminurl->redirect('translater', ['type' => $type, 'module' => $module->id], '#module-backup'); + } - /** - * Export language package for a module - */ - case 'module_export_pack': - try { - if (empty($_POST['modules']) || empty($_POST['entries'])) { - throw new Exception(__('Nothing to export')); - } - $translater->exportPack($_POST['modules'], $_POST['entries']); - - http::redirect($p_url . '&part=module&module=' . $module . '&type=' . $type . '&tab=module-pack&msg=export_pack'); - } catch (Exception $e) { - $core->error->add(sprintf($errors[$action], $e->getMessage())); + if ($action == 'module_delete_backup') { + if (empty($module) || empty($_POST['files'])) { + throw New Exception(__('No backup to delete')); } - break; - - /** - * Update language - */ - case 'update_lang': - try { - if (empty($_POST['entries']) || empty($lang) || empty($module)) { - throw new Exception(__('No language to update')); + $module_backups = $module->getBackups(true); + foreach($module_backups as $backup_file) { + if (in_array($backup_file, $_POST['files'])) { + $module->deleteBackup($backup_file); } + } + dcPage::addSuccessNotice(__('Backup successfully deleted')); + $core->adminurl->redirect('translater', ['type' => $type, 'module' => $module->id], '#module-backup'); + } + + if ($action == 'module_export_pack') { + if (empty($module) || empty($_POST['codes'])) { + throw new Exception(__('Nothing to export')); + } + $module->exportPack($_POST['codes']); + + dcPage::addSuccessNotice(__('Package successfully exported')); + $core->adminurl->redirect('translater', ['type' => $type, 'module' => $module->id], '#module-pack'); + } + + if ($action == 'module_import_pack') { + if (empty($_FILES['packfile']['name'])) { + throw new Exception(__('Nothing to import')); + } + $module->importPack($_FILES['packfile']); + + dcPage::addSuccessNotice(__('Package successfully imported')); + $core->adminurl->redirect('translater', ['type' => $type, 'module' => $module->id], '#module-pack'); + } + + if ($action == 'module_add_code') { + if (empty($module) || empty($_POST['code'])) { + throw new Exception(__('No lang to create')); + } + $module->addLang($_POST['code'], $_POST['from'] ?? ''); + + dcPage::addSuccessNotice(__('Lang successfully added')); + $core->adminurl->redirect('translater', ['type' => $type, 'module' => $module->id, 'lang' => $_POST['code']]); + } + + if ($action == 'module_delete_code') { + if (empty($module) || empty($_POST['code'])) { + throw new Exception(__('No lang to delete')); + } + $module->delLang($_POST['code']); + + dcPage::addSuccessNotice(__('Lang successfully deleted')); + $core->adminurl->redirect('translater', ['type' => $type, 'module' => $module->id, 'lang' => $_POST['code']]); + } + + if ($action == 'module_update_code') { + if (empty($module) || empty($_POST['code']) || empty($_POST['entries'])) { + throw new Exception(__('No lang to update')); + } + if (!empty($_POST['update_group'])) { foreach($_POST['entries'] as $i => $entry) { if (isset($entry['check']) && isset($_POST['multigroup'])) { $_POST['entries'][$i]['group'] = $_POST['multigroup']; - unset($_POST['entries'][$i]['check']); } } - $translater->updLang($module, $lang, $_POST['entries']); - - http::redirect($p_url . '&part=lang&module=' . $module . '&type=' . $type . '&lang=' . $lang . '&msg=' . $action); - } catch (Exception $e) { - $core->error->add(sprintf($errors[$action], $e->getMessage())); } - break; + $module->updLang($_POST['code'], $_POST['entries']); - /** - * Import language packages - */ - case 'import_pack': - try { - if (empty($_FILES['packfile']['name'])) { - throw new Exception(__('Nothing to import')); - } - $translater->importPack($_POST['modules'], $_FILES['packfile']); + dcPage::addSuccessNotice(__('Lang successfully updated')); + $core->adminurl->redirect('translater', ['type' => $type, 'module' => $module->id, 'lang' => $_POST['code']]); - http::redirect($p_url . '&part=pack&msg=' . $action . '&tab=pack-import'); - } catch (Exception $e) { - $core->error->add(sprintf($errors[$action], $e->getMessage())); - } - break; + } - /** - * Export language packages - */ - case 'export_pack': - try { - if (empty($_POST['modules']) || empty($_POST['entries'])) { - throw new Exception(__('Nothing to export')); - } - $translater->exportPack($_POST['modules'], $_POST['entries']); - - http::redirect($p_url . '&part=pack&msg=' . $action . '&tab=pack-export'); - } catch (Exception $e) { - $core->error->add(sprintf($errors[$action], $e->getMessage())); - } - break; - - /** - * Modules / Unknow / None - */ - default: - break; +} catch (Exception $e) { + $core->error->add($e->getMessage()); } -# -# Fill in title and prepare display -# - -switch ($part) { - /** - * Modules - */ - case 'modules': - $title = '' . ($type == 'theme' ? __('Themes') : __('Plugins')) . ''; - break; - - /** - * Module - */ - case 'module': - # Get info about requested module - try { - $M = $translater->getModule($module, $type); - } catch(Exception $e) { - $core->error->add(sprintf(__('Failed to launch translater: %s'), $e->getMessage())); - $action = $module = $type = ''; - $M = false; - } - if (!empty($module) && !empty($type) && !$M) { - $action = $module = $type = ''; - $M = false; - } - - $M->langs = $translater->listLangs($module); - $M->backups = $translater->listBackups($module); - $M->unused_langs = array_flip(array_diff($translater->getIsoCodes(), $M->langs)); - $M->used_langs = array_flip(array_diff($M->langs, array_flip($translater->getIsoCodes()))); - $allowed_groups = array_combine( - dcTranslater::$allowed_l10n_groups, - dcTranslater::$allowed_l10n_groups - ); - - $title = - '' . ($type == 'theme' ? __('Themes') : __('Plugins')) . '' . - ' › ' . $module . ''; - break; - - /** - * Lang - */ - case 'lang': - - # Get infos on module wanted - try { - $M = $translater->getModule($module, $type); - - # Retrieve some infos - $M->langs = $translater->listLangs($module); - $M->backups = $translater->listBackups($module); - $M->unused_langs = array_flip(array_diff($translater->getIsoCodes(), $M->langs)); - $M->used_langs = array_flip(array_diff($M->langs, array_flip($translater->getIsoCodes()))); - $allowed_groups = array_combine( - dcTranslater::$allowed_l10n_groups, - dcTranslater::$allowed_l10n_groups - ); - } catch(Exception $e) { - $core->error->add(sprintf(__('Failed to launch translater: %s'), $e->getMessage())); - $action = $module = $type = ''; - $M = false; - } - if (!empty($module) && !empty($type) && !$M) { - $action = $module = $type = ''; - $M = false; - } - - $title = - '' . ($type == 'theme' ? __('Themes') : __('Plugins')) . '' . - ' › ' . - '' . $module . ''; - if (!empty($M->langs) && isset($M->langs[$lang])) { - $title .= ' › ' . $M->langs[$lang] . ''; - } - break; - - /** - * Import/Export (pack) - */ - case 'pack': - $title = '' . __('Import/Export') . ''; - break; -} - -# -# Display page -# - -echo ' - -' . __('Translater') . '' . -dcPage::jsPageTabs($tab) . -dcPage::jsLoad('js/_posts_list.js') . -dcPage::jsLoad('index.php?pf=translater/js/jquery.translater.js'); - -if ('' != $translater->proposal_tool) { - echo - '\n" . - "\n"; -} - -# --BEHAVIOR-- translaterAdminHeaders -$core->callBehavior('translaterAdminHeaders'); - echo -' -

' . __('Translater') . ' › ' . $title; -if ($part == 'pack' || $type != 'plugin') { - echo ' / ' . __('Plugins') . ''; -} -if ($part == 'pack' || $type != 'theme') { - echo ' / ' . __('Themes') . ''; -} -if ($part != 'pack') { - echo ' / ' . __('Import/Export') . ''; -} -echo -'

'; +'' . __('translater') . '' . +dcPage::jsPageTabs() . +dcPage::cssLoad(dcPage::getPF('translater/css/translater.css')) . +dcpage::jsJson('translater', [ + 'title_add' => __('Use this text'), + 'image_field' => dcPage::getPF('translater/img/field.png'), + 'image_toggle' => dcPage::getPF('translater/img/toggle.png') +]) . +dcPage::jsLoad(dcPage::getPF('translater/js/translater.js')) . -if (isset($succes[$msg])) { - dcPage::success($succes[$msg]); -} +# --BEHAVIOR-- translaterAdminHeaders +$core->callBehavior('translaterAdminHeaders') . -switch ($part) { - /** - * Modules - */ - case 'modules': - echo '
'; +'' . - $res = ''; - foreach ($translater->listModules($type) as $k => $v) { - if ($translater->hide_default && in_array($k, dcTranslater::$default_dotclear_modules[$type])) { - continue; - } +dcPage::breadcrumb($breadcrumb) . +dcPage::notices(); - if ($v['root_writable']) { - $res .= - '' . - '' . - '' . $k . ''; - } else { - $res .= - '' . - '' . $k . ''; - } - $res .= - '' . $v['version'] . '' . - ''; +if (empty($module) && $type != '') { + // modules list + echo ''; - $array_langs = array(); - foreach ($translater->listLangs($k) as $lang_name => $lang_infos) { - $array_langs[$lang_name] = - '' . - $lang_name . ''; - } - $res .= implode(', ', $array_langs) . - '' . - ''; + $res = ''; + foreach ($translater->getModules($type) as $module) { + if ($translater->hide_default && in_array($module->id, $translater::$default_distrib_modules[$type])) { + continue; } - if ($res) { - echo ' - - - - - - ' . - $res . - '
' . __('Id') . '' . __('Version') . '' . __('Languages') . '
'; - + if ($module->root_writable) { + $res .= sprintf( + '%s', + $core->adminurl->get('translater', ['type' => $module->type, 'module' => $module->id]), + html::escapeHTML(sprintf(__('Translate module %s'), __($module->name))), + html::escapeHTML(__($module->name)) + ); } else { - echo '' . __('There is no editable modules') . ''; + $res .= sprintf( + '%s', + html::escapeHTML(__($module->name)) + ); } - echo ' -

 

-
'; - break; - - /** - * Module - */ - case 'module': - # Summary - echo ' -
-

' . __('Module') . '

- - - - - - - - - - - - - - - -
' . __('About') . '
' . __('Name') . ' ' . $M->name . '
' . __('Version') . ' ' . $M->version . '
' . __('Author') . ' ' . $M->author . '
' . __('Type') . ' ' . $M->type . '
' . __('Root') . ' ' . $M->root . '
' . __('Backups') . ' ' . - $translater->getBackupFolder($module) . '
-

 

'; - - if (count($M->langs)) { - echo - '

' . __('l10n') . '

' . - '' . - '' . - '' . - '' . - '' . - '' . - ''; - - foreach($M->langs AS $lang => $name) { - echo - '' . - '' . - ''; - - if (isset($M->backups[$lang])) { - foreach($M->backups[$lang] AS $file => $info) { - $time[$lang] = isset($time[$lang]) && $time[$lang] > $info['time'] ? - $time[$lang] : $info['time']; - } - echo - '' . - ''; - } else { - echo ''; - } - echo ''; - } - echo '
' . __('Languages') . '' . __('Code') . '' . __('Backups') . '' . __('Last backup') . '
' . - '' . $name . '' . - ' ' . $lang . '' . count($M->backups[$lang]) . ' ' . - dt::str('%Y-%m-%d %H:%M', $time[$lang], $core->blog->settings->system->blog_timezone) . - '' . __('no backup') . '
'; - } - echo '
'; - - # Add/Remove/Edit lang - echo '
'; - - # Edit lang - if (!empty($M->langs)) { - echo ' -

' . __('Edit language') . '

-
-

' . __('Select language:') . ' ' . - form::combo(array('lang'), $M->used_langs,$lang) . '

-

' . - $core->formNonce() . - form::hidden(array('type'), $type) . - form::hidden(array('module'), $module) . - form::hidden(array('action'), '') . - form::hidden(array('part'), 'lang') . - form::hidden(array('p'), 'translater') . ' -

-
-

 

'; - } - - # New lang - if (!empty($M->unused_langs)) { - echo ' -

' . __('Add language') . '

-
-

' . __('Select language:') . ' ' . - form::combo(array('lang'), array_merge(array('-'=>'-'), $M->unused_langs), $core->auth->getInfo('user_lang')) . '

'; - if (!empty($M->used_langs)) { - echo - '

' . __('Copy from language:') . ' ' . - form::combo(array('from'),array_merge(array('-'=>'-'), $M->used_langs)) . - ' (' . __('Optionnal') . ')

'; + $codes = $module->getLangs(); + foreach ($codes as $code_id => $code_name) { + if ($module->root_writable) { + $codes[$code_id] = sprintf( + '%s (%s)', + html::escapeHTML(sprintf(__('Edit translation %s of the module %s'), html::escapeHTML($code_name), __($module->name))), + $core->adminurl->get('translater', ['type' => $module->type, 'module' => $module->id, 'lang' => $code_id]), + html::escapeHTML($code_name), + $code_id + ); } else { - echo '

' . form::hidden(array('from'), '') . '

'; + $codes[$code_id] = html::escapeHTML($code_name) . '(' .$code_id . ')'; } - echo ' -

' . - $core->formNonce() . - form::hidden(array('type'), $type) . - form::hidden(array('module'), $module) . - form::hidden(array('action'), 'module_add_lang') . - form::hidden(array('part'), 'module') . - form::hidden(array('tab'), 'module-lang') . - form::hidden(array('p'), 'translater') . ' -

-
-

 

'; } - - # Delete lang - if (!empty($M->used_langs)) { - echo ' -

' . __('Delete language') . '

-
-

' . __('Select language:') . ' ' . - form::combo(array('lang'), array_merge(array('-'=>'-'), $M->used_langs)) . '

-

' . - $core->formNonce() . - form::hidden(array('type'), $type) . - form::hidden(array('module'), $module) . - form::hidden(array('action'), 'module_delete_lang') . - form::hidden(array('part'), 'module') . - form::hidden(array('tab'), 'module-lang') . - form::hidden(array('p'), 'translater') . ' -

-
-

 

'; - } - echo '
'; - - # Create/delete/restore backups - if (!empty($M->used_langs) || !empty($M->backups)) { - - echo '
'; - - if (!empty($M->used_langs)) { - echo ' -

' . __('Create backups') . '

-
-

' . __('Choose languages to backup') . '

- - '; - $i=0; - foreach($M->used_langs AS $name => $lang) { - $i++; - echo ' - - - - - '; - } - echo ' -
' . form::checkbox(array('langs[]') ,$lang, '', '', '', false) . '' . $name . '' . $lang . '
-
-

 

-

 

-
-

- ' . - form::hidden(array('modules[]'), $module) . - $core->formNonce() . - form::hidden(array('type'), $type) . - form::hidden(array('module'), $module) . - form::hidden(array('action'), 'module_create_backup') . - form::hidden(array('part'), 'module') . - form::hidden(array('tab'), 'module-backup') . - form::hidden(array('p'), 'translater') . ' -

-
-

 

'; - } - - if (!empty($M->backups)) { - echo - '

' . __('List of backups') . '

' . - '
' . - '' . - '' . - '' . - '' . - '' . - '' . - ''; - $i=0; - foreach($M->backups as $lang => $langs) { - foreach($langs as $file => $infos) { - $i++; - echo - '' . - '' . - '' . - '' . - '' . - '' . - ''; - } - } - echo ' -
' . __('File') . '' . __('Date') . '' . __('Language') . '' . __('Size') . '
' . form::checkbox(array('files[]'), $file, '', '', '', false) . '' . $file . '' . - dt::str(__('%Y-%m-%d %H:%M:%S'), $infos['time'], $core->blog->settings->system->blog_timezone) . - '' . $translater->isIsoCode($lang) . '' . files::size($infos['size']) . '
-
-

 

-

' . __('Selected backups action:') . ' ' . - form::combo('action', array( - __('Restore backups') => 'module_restore_backup', - __('Delete backups') => 'module_delete_backup') - ) . ' - ' . - form::hidden(array('modules[]'), $module) . - $core->formNonce() . - form::hidden(array('type'), $type) . - form::hidden(array('module'), $module) . - form::hidden(array('part'), 'module') . - form::hidden(array('tab'), 'module-backup') . - form::hidden(array('p'), 'translater') . ' -

-
-
-

 

'; - } - - echo '
'; - } // end if (!empty($M->used_langs) || !empty($M->backups)) { - - # Import/Export pack - echo '
'; - - # Import + $res .= sprintf( + '%s%s%s', + implode(', ', $codes), + html::escapeHTML($module->id), + $module->version + ); + } + if ($res) { echo ' -

' . __('Import') . '

-
-

' . __('Choose language package to import') . '
-

-

- ' . - form::hidden(array('modules[]'), $module) . - $core->formNonce() . - form::hidden(array('type'), $type) . - form::hidden(array('module'), $module) . - form::hidden(array('action'), 'module_import_pack') . - form::hidden(array('part'), 'module') . - form::hidden(array('tab'), 'module-pack') . - form::hidden(array('p'), 'translater') . ' -

-
-

 

'; +
+ + + + + + + + ' . + $res . + '
' . __('Name') . '' . __('Languages') . '' . __('Id') . '' . __('Version') . '
'; - # Export - if (!empty($M->used_langs)) { + } else { + echo '' . __('There is no editable modules') . ''; + } + echo ''; + +} elseif (!empty($module) && empty($lang)) { + $codes = $module->getUsedLangs(); + $backups = $module->getBackups(); + $unused_codes = $module->getUnusedLangs(); + + // module summary + echo ' +
+

' . __('Module') . '

+ + + + + + + + + + + + + + + + + +
' .__('Module summary') . '
' . __('Name') . ' ' . $module->name . '
' . __('Version') . ' ' . $module->version . '
' . __('Author') . ' ' . $module->author . '
' . __('Type') . ' ' . $module->type . '
' . __('Root') . ' ' . $module->root . '
' . __('Locales') . ' ' . $module->locales . '
' . __('Backups') . ' ' . $module->getBackupRoot() . '
+

 

'; + if (count($codes)) { + echo + '

' . __('Translations') . '

' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + ''; + + foreach($codes AS $code_name => $code_id) { echo - '

' . __('Export') . '

' . - '' . - '

' . __('Choose languages to export') . '

' . - '
' . __('Existing module languages') .'
' . __('Languages') . '' . __('Code') . '' . __('Backups') . '' . __('Last backup') . '
' . - ''; - $i=0; - foreach($M->used_langs AS $name => $lang) { - $i++; + '' . + '' . + ''; + + if (isset($backups[$code_id])) { + foreach($backups[$code_id] AS $file => $info) { + $time[$code_id] = isset($time[$code_id]) && $time[$code_id] > $info['time'] ? + $time[$code_id] : $info['time']; + } echo - '' . - '' . - '' . - '' . - ''; + '' . + ''; + } else { + echo ''; } + echo ''; + } + echo '
' . + '' . $code_name . '' . + ' ' . $code_id . '
' . - form::checkbox(array('entries[]'), $lang, '', '', '', false) . - '' . $name . '' . $lang . '
' . count($backups[$code_id]) . ' ' . + dt::str('%Y-%m-%d %H:%M', $time[$code_id], $core->blog->settings->system->blog_timezone) . + '' . __('no backup') . '-
'; + } + echo '
'; + + // Add/Remove/Edit lang + echo '
'; + + // edit lang + if (!empty($codes)) { + echo '

' . __('Edit language') . '

+ +

' . + form::combo(['lang'], $codes, $core->auth->getInfo('user_lang')) . '

+

' . + $core->formNonce() . + $core->adminurl->getHiddenFormFields( + 'translater', + ['type' => $module->type, 'module' => $module->id] + ) . ' +

 

'; + } + + // add lang + if (!empty($unused_codes)) { + echo '

' . __('Add language') . '

+
+

' . + form::combo(['code'], array_merge(['-' => '-'], $unused_codes), $core->auth->getInfo('user_lang')) . '

'; + if (empty($codes)) { + echo '

' . form::hidden(['from'], '') . '

'; + } else { echo - '' . - '
' . - '

 

' . - '

 

' . - '
' . - '

' . - '' . - form::hidden(array('modules[]'), $module) . + '

' . + form::combo(['from'], array_merge(['-' => ''], $codes)) . ' (' . __('Optionnal') . ')

'; + } + echo ' +

' . + $core->formNonce() . + $core->adminurl->getHiddenFormFields( + 'translater', + ['type' => $module->type, 'module' => $module->id, 'action' => 'module_add_code'] + ) . ' +

 

'; + } + + // delete langs + if (!empty($codes)) { + echo '

' . __('Delete language') . '

+
+

' . + form::combo(['code'], array_merge(['-' => ''], $codes)) . '

+

' . + $core->formNonce() . + $core->adminurl->getHiddenFormFields( + 'translater', + ['type' => $module->type, 'module' => $module->id, 'action' => 'module_delete_code'] + ) . ' +

 

'; + } + echo '
'; + + // backups + if (!empty($codes) || !empty($backups)) { + + echo '
'; + + // create backups + if (!empty($codes)) { + echo '

' . __('Create backups') . '

+
+ + + '; + $i=0; + foreach($codes AS $code_name => $code_id) { + $i++; + echo ' + + + + + '; + } + echo ' +
' . __('Existing module languages') . '
' . __('Language') . '' . __('Code') . '
' . form::checkbox(['codes[]', 'backup_code_' . $code_id], $code_id, '', '', '', false) . '' . $code_id . '
+
+

+ +

+

' . $core->formNonce() . - form::hidden(array('type'), $type) . - form::hidden(array('module'), $module) . - form::hidden(array('action'), 'module_export_pack') . - form::hidden(array('part'), 'module') . - form::hidden(array('tab'), 'module-pack') . - form::hidden(array('p'), 'translater') . - '

' . - '' . - '

 

'; + $core->adminurl->getHiddenFormFields( + 'translater', + ['type' => $module->type, 'module' => $module->id, 'action' => 'module_create_backup'] + ) . ' +

 

'; + } + + // delete / retore backups + if (!empty($backups)) { + echo '

' . __('List of backups') . '

' . + '
' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + ''; + $i=0; + foreach($backups as $backup_codes) { + foreach($backup_codes as $backup_file => $backup_code) { + $i++; + $form_id = 'form_file_' . $backup_code['code'] . $backup_code['time']; + echo + '' . + '' . + '' . + '' . + '' . + '' . + '' . + ''; + } + } + echo ' +
' . __('Existing backuped languages') . '
' . __('Name') . '' . __('Code') . '' . __('Date') . '' . __('File') . '' . __('Size') . '
' . form::checkbox(['files[]', $form_id], $backup_file, '', '', '', false) . '' . $backup_code['code'] . '' . dt::str( + $core->blog->settings->system->date_format . ' ' . $core->blog->settings->system->time_format, + $backup_code['time'], + $core->blog->settings->system->blog_timezone + ) . '' . $backup_code['path']['basename'] . '' . files::size($backup_code['size']) . '
+
+

+ +

' . __('Selected backups action:') . ' ' . + form::combo('action', [ + __('Restore backups') => 'module_restore_backup', + __('Delete backups') => 'module_delete_backup' + ]) . ' +

' . + $core->formNonce() . + $core->adminurl->getHiddenFormFields( + 'translater', + ['type' => $module->type, 'module' => $module->id] + ) . ' +

 

'; } echo '
'; - break; + } - /** - * Lang - */ - case 'lang': - # Existing langs - if (empty($M->langs) || !isset($M->langs[$lang])) { - break; + // Import/Export pack + echo '
'; + + // Import + echo '

' . __('Import') . '

+
+

+

+ ' . + $core->formNonce() . + $core->adminurl->getHiddenFormFields( + 'translater', + ['type' => $module->type, 'module' => $module->id, 'action' => 'module_import_pack'] + ) . ' +

 

'; + + // Export + if (!empty($codes)) { + echo '

' . __('Export') . '

' . + '
' . + '' . + '' . + ''; + $i=0; + foreach($codes AS $code_name => $code_id) { + $i++; + echo + '' . + '' . + '' . + '' . + ''; + } + echo + '
' . __('Existing module languages') . '
' . __('Language') . '' . __('Code') . '
' . + form::checkbox(['codes[]', 'export_code_' . $code_id], $code_id, '', '', '', false) . + '' . $code_id . '
' . + '
' . + '

' . + + '

' . + '

' . + $core->formNonce() . + $core->adminurl->getHiddenFormFields( + 'translater', + ['type' => $module->type, 'module' => $module->id, 'action' => 'module_export_pack'] + ) . + '

 

'; + } + echo '
'; + +} elseif (!empty($lang)) { + $lines = $lang->getMessages(); + + echo + '
' . + '
' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + ''; + + $table_line = + '' . + '' . + '' . + '' . + '' . + '' . + '' . + ''; + $table_ul = '
%s
%s

'; + $table_li = '%s
'; + + $i = 1; + foreach ($lines AS $msgid => $rs) { + $in_dc = ($rs['in_dc'] && $translater->parse_nodc); + $allowed_l10n_groups = array_combine($translater::$allowed_l10n_groups, $translater::$allowed_l10n_groups); + $t_msgstr = $t_files = $strin = []; + + foreach($rs['o_msgstrs'] as $o_msgstr) { + if (!isset($strin[$o_msgstr['msgstr'][0]])) { + $strin[$o_msgstr['msgstr'][0]] = []; + } + $strin[$o_msgstr['msgstr'][0]][] = ['module' => $o_msgstr['module'], 'file' => $o_msgstr['file']]; + } + foreach($strin as $k => $v) { + $res = []; + foreach($v as $str) { + $res[] = sprintf($table_li, html::escapeHTML($str['module'] . ':' . $str['file'])); + } + $t_msgstr[] = sprintf($table_ul, html::escapeHTML($k), implode('', $res)); } - $iso = $M->langs[$lang]; - - $i = 0; - $sort_order = 'asc'; - $lines = $translater->getMsgs($module, $lang); - - # Sort array - if (isset($_GET['sort']) && !empty($lines)) { - $sort = explode(',', $_GET['sort']); - $sort_by = $sort[0]; - $sort_order = isset($sort[1]) && $sort[1] == 'desc' ? 'asc' : 'desc'; - - switch($sort_by) { - case 'group': - foreach($lines AS $k => $v) { - $sort_list[] = $v['group']; - } - break; - - case 'msgid': - foreach($lines AS $k => $v) { - $sort_list[] = strtolower($k); - } - break; - - case 'file': - foreach($lines AS $k => $v) { - $file = array(); - foreach($v['files'] as $fv) { - $file[] = empty($fv[0]) || empty($fv[1]) ? '' : $fv[0] . ($fv[1] /1000); - } - sort($file); - $sort_list[] = $file[0]; - } - break; - - case 'msgstr': - foreach($lines AS $k => $v) { - $sort_list[] = strtolower($v['msgstr']); - } - break; - - default: - $sort_list = false; - break; + if (!empty($rs['files'][0])) { + if (count($rs['files']) == 1) { + $t_files[] = $rs['files'][0][0] . ':' . $rs['files'][0][1]; + } else { + $res = []; + foreach($rs['files'] as $location) { + $res[] = sprintf($table_li, implode(' : ', $location)); + } + $t_files[] = sprintf($table_ul, sprintf(__('%s occurrences'), count($rs['files'])), implode('', $res));; } - if ($sort_list) { - array_multisort( - $sort_list, - ($sort_order == 'asc' ? SORT_DESC : SORT_ASC), - SORT_STRING, - $lines + } + + echo sprintf($table_line, $in_dc ? ' offline' : ' translaterline', + form::checkbox(['entries[' . $i . '][check]'], 1), + form::combo(['entries[' . $i . '][group]'], $allowed_l10n_groups, $rs['group'], '', '', $in_dc), + html::escapeHTML($msgid), + form::hidden(['entries[' . $i . '][msgid]'], html::escapeHTML($msgid)) . + form::field(['entries[' . $i . '][msgstr][0]'], 48, 255, html::escapeHTML($rs['msgstr'][0]), '', '', $in_dc), + implode('', $t_msgstr), + implode('', $t_files) + ); + + if (!empty($rs['plural'])) { + $t_msgstr = $strin = []; + foreach($lang->plural as $j => $plural) { + foreach($rs['o_msgstrs'] as $o_msgstr) { + if (isset($o_msgstr['msgstr'][$j+1])) { + if (!isset($strin[$o_msgstr['msgstr'][$j+1]])) { + $strin[$o_msgstr['msgstr'][$j+1]] = []; + } + $strin[$o_msgstr['msgstr'][$j+1]][] = ['module' => $o_msgstr['module'], 'file' => $o_msgstr['file']]; + } + } + foreach($strin as $k => $v) { + $res = []; + foreach($v as $str) { + $res[] = sprintf($table_li, html::escapeHTML($str['module'] . ':' . $str['file'])); + } + $t_msgstr[] = sprintf($table_ul, html::escapeHTML($k), implode('', $res)); + } + + echo sprintf($table_line, $in_dc ? ' offline' : ' translaterline', + '+', + sprintf(__('Plural "%s"'), $plural), + sprintf(__('Plural form of "%s"'), $rs['plural']), + form::hidden(['entries[' . $i . '][msgid_plural]'], html::escapeHTML($rs['plural'])) . + form::field(['entries[' . $i . '][msgstr][' . $j+1 . ']'], 48, 255, html::escapeHTML($rs['msgstr'][$j+1] ?? ''), '', '', $in_dc), + implode('', $t_msgstr), + '' ); } } - - echo - '
' . - '' . - '
' . sprintf(__('List of %s localized strings'), count($lines)) . '
' . __('Group') . '' . __('String') . '' . __('Translation') . '' . __('Existing') . '' . __('File') . '
%s%s%s%s%s%s
' . - '' . - '' . - '' . - '' . - '' . - '' . - ''; - - foreach ($lines AS $msgid => $rs) { - $i++; - $in_dc = ($rs['in_dc'] && $translater->parse_nodc); - - echo - '' . - - '' . - - 'proposal_tool ? ' class="translatermsgid"' : '' ) . '>' . - html::escapeHTML($msgid) . '' . - - '' . - - '' . - - '' . - ''; - } - $i++; - echo - '' . - '' . - '' . - '' . - '' . - '' . - '' . - '
' . __('Group') . '' . __('String') . '' . __('Translation') . '' . __('Existing') . '' . __('File') . '
' . - form::checkbox(array('entries[' . $i . '][check]'), 1) . ' ' . - form::combo(array('entries[' . $i . '][group]'), - $allowed_groups,$rs['group'], '', '', $in_dc - ) . - '' . - form::hidden(array('entries[' . $i . '][msgid]'), html::escapeHTML($msgid)) . - form::field(array('entries[' . $i . '][msgstr]'), - 48, 255, html::escapeHTML($rs['msgstr']), '', '', $in_dc) . - ''; - $strin = array(); - foreach($rs['o_msgstrs'] AS $o_msgstr) { - if (!isset($strin[$o_msgstr['msgstr']])) { - $strin[$o_msgstr['msgstr']] = array(); - } - $strin[$o_msgstr['msgstr']][] = array('module' => $o_msgstr['module'], 'file' => $o_msgstr['file']); - } - foreach($strin as $k => $v) { - echo '
' . html::escapeHTML($k) . '
'; - foreach($v as $str) { - echo '' . html::escapeHTML($str['module'] . ' => ' . $str['file']) . '
'; - } - echo '

'; - } - echo - '
'; - if (empty($rs['files'][0])) { - echo ' '; - } elseif (count($rs['files']) == 1) { - echo $rs['files'][0][0] . ' : ' . $rs['files'][0][1]; - } else { - echo - '' . sprintf(__('%s occurrences'), count($rs['files'])) . '' . - '
'; - foreach($rs['files'] as $location) { - echo '' . implode(' : ', $location) . '
'; - } - echo '
'; - } - echo - '
' . - form::checkbox(array('entries[' . $i . '][check]'), 1) . ' ' . - form::combo(array('entries[' . $i . '][group]'), $allowed_groups, 'main') . - '' . form::field(array('entries[' . $i . '][msgid]'), 48, 255, '') . '' . form::field(array('entries[' . $i . '][msgstr]'), 48, 255, '') . '  
' . - '

' . sprintf(__('Total of %s strings . '), $i-1) . '

' . - '

' . - '

' . __('Change the group of the selected entries to:') . ' ' . - form::combo(array('multigroup'), $allowed_groups) . - '

' . - '

' . - '' . - $core->formNonce() . - form::hidden(array('lang'), $lang) . - form::hidden(array('type') ,$type) . - form::hidden(array('module'), $module) . - form::hidden(array('action'), 'update_lang') . - form::hidden(array('part'), 'lang') . - form::hidden(array('p'), 'translater') . - '

' . - '
' . - '

 

' . - '
'; - break; + } + echo sprintf($table_line, ' offline', + form::checkbox(['entries[' . $i . '][check]'], 1), + form::combo(['entries[' . $i . '][group]'], $allowed_l10n_groups, 'main'), + form::field(['entries[' . $i . '][msgid]'], 48, 255, ''), + form::field(['entries[' . $i . '][msgstr][0]'], 48, 255, ''), + '', + '' + ); + echo + '' . - /** - * Import/Export (Pack) - */ - case 'pack': - # Import - echo ' -
-
-

' . __('Choose language package to import') . '
-

-

- '; - $i=0; - foreach($translater->listModules() AS $k => $v) { - if ($translater->hide_default && ( - in_array($k,dcTranslater::$default_dotclear_modules['plugin']) || - in_array($k,dcTranslater::$default_dotclear_modules['theme']))) { - continue; - } - echo form::hidden(array('modules[]'), $k);$i++; - } - echo - $core->formNonce() . - form::hidden(array('type'), $type) . - form::hidden(array('module'), $module) . - form::hidden(array('action'), 'import_pack') . - form::hidden(array('part'), 'pack') . - form::hidden(array('tab'), 'pack-import') . - form::hidden(array('p'), 'translater') . ' -

-
-
'; + '
' . + '
' . + '

' . + '

' . + '
' . + '

' . + '

' . + $core->formNonce() . + form::hidden(['code'], $lang->code) . + $core->adminurl->getHiddenFormFields( + 'translater', + ['type' => $module->type, 'module' => $module->id, 'lang' => $lang->code, 'action' => 'module_update_code'] + ) . + '

' . + '' . + '

 

' . + '
'; - # Export - echo ' -
-
-

' . __('Choose modules to export') . '

- - '; - $i=0; - $langs_list = array(); - - foreach($translater->listModules() AS $k => $v) { - if ($translater->hide_default && ( - in_array($k,dcTranslater::$default_dotclear_modules['plugin']) || - in_array($k,dcTranslater::$default_dotclear_modules['theme']))) { - continue; - } - - $info_lang = $translater->listLangs($k); - if (!is_array($info_lang) || 1 > count($info_lang)) { - continue; - } - - $i++; - $langs_list = array_merge($langs_list, $info_lang); - - echo ' - - - - - '; - } - - echo ' -
' . __('Modules') . '' . __('Languages') . '
' . form::checkbox(array('modules[]'), $k, '', '', '', false) . '' . $v['name'] . '' . implode(', ', $info_lang) . '
-

' . __('Choose languages to export') . '

- - '; - $i=0; - foreach($langs_list AS $k => $v) { - $i++; - echo ' - - - - - '; - } - echo ' -
' . __('Languages') . '' . __('Code') . '
' . form::checkbox(array('entries[]'), $k, '', '', '', false) . '' . $v . '' . $k . '
-
-

-

 

-
-

- ' . - $core->formNonce() . - form::hidden(array('type'), $type) . - form::hidden(array('module'), $module) . - form::hidden(array('action'), 'export_pack') . - form::hidden(array('part'), 'pack') . - form::hidden(array('tab'), 'pack-export') . - form::hidden(array('p'), 'translater') . ' -

-
-
'; - break; +} else { + $line = '
  • %s
  • '; + echo sprintf( + '

      %s

    ', + sprintf( + $line, + $core->adminurl->get('translater', ['type' => 'plugin']), + $type == 'plugin' ? ' class="active"' : '', + __('Translate plugins')) . + sprintf( + $line, + $core->adminurl->get('translater', ['type' => 'theme']), + $type == 'theme' ? ' class="active"' : '', + __('Translate themes')) + ); } dcPage::helpBlock('translater'); -echo ' -'; \ No newline at end of file +echo ''; \ No newline at end of file diff --git a/js/jquery.translater.js b/js/jquery.translater.js deleted file mode 100644 index 896c0b0..0000000 --- a/js/jquery.translater.js +++ /dev/null @@ -1,91 +0,0 @@ -/* -- BEGIN LICENSE BLOCK ---------------------------------- - * - * This file is part of translater, a plugin for Dotclear 2. - * - * Copyright (c) 2009-2013 Jean-Christian Denis and contributors - * contact@jcdenis.fr - * - * Licensed under the GPL version 2.0 license. - * A copy of this license is available in LICENSE file or at - * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html - * - * -- END LICENSE BLOCK ------------------------------------*/ - -;if(window.jQuery) (function($){ - $.fn.translater = function(options){ - var opts = $.extend({}, $.fn.translater.defaults, options); - return this.each(function(){ - - var img = ''; - var tog = ''; - var line = this; - var msgid = $(line).children('.translatermsgid'); - var msgfile = $(line).children('.translatermsgfile'); - var msgstr = $(line).children('.translatermsgstr'); - var target = $(line).children('.translatertarget'); - - $('.strlist').hide(); - - var img_go = $(''+img+'').css('cursor','pointer'); - $(msgid).prepend(' ').prepend(img_go); - $(img_go).click(function(){ - var txt = $(msgid).text(); - $(img_go).css('cursor','wait'); - $.get(opts.url,{f:opts.func,langFrom:opts.from,langTo:opts.to,langTool:opts.tool,langStr:txt},function(data){ - data=$(data); - if(data.find('rsp').attr('status')=='ok' && $(data).find('proposal').attr('str_to')){ - var resp = $(data).find('proposal').attr('str_to'); - if (confirm(opts.title+'\n'+resp)){ - addText(target,resp); - $(img_go).css('cursor','pointer'); - } - else{ - $(img_go).css('cursor','pointer'); - } - }else{ - alert(opts.failed); - $(img_go).css('cursor','pointer'); - } - }); - }); - - $(msgstr).children('.subtranslatermsgstr').each(function(){ - var img_str = $(''+tog+'').css('cursor','pointer'); - $(this).children('strong').each(function(){ - var txt = $(this).text(); - var img_add = $(''+img+'').css('cursor','pointer'); - $(this).prepend(' ').prepend(img_add); - $(img_add).click(function(){addText(target,txt);}); - - $(this).append(' ').append(img_str); - var strlist=$(this).siblings('.strlist'); - $(strlist).click(function(){$(strlist).toggle();}); - $(img_str).click(function(){$(strlist).toggle();}); - }); - }); - - var img_file = $(''+tog+'').css('cursor','pointer'); - $(msgfile).children('strong').each(function(){ - $(this).append(' ').append(img_file); - var strlist=$(this).siblings('.strlist'); - $(strlist).click(function(){$(strlist).toggle();}); - $(img_file).click(function(){$(strlist).toggle();}); - }); - }); - }; - function addText(target,text){ - $(target).children(':text').val(text); - } - - $.fn.translater.defaults = { - url: '', - func: '', - from: 'en', - to: 'fr', - tool: 'google', - failed: 'Failed to translate this', - title: 'Copy translation to field:', - title_go: 'Find translation', - title_add: 'Add this translation' - }; -})(jQuery); \ No newline at end of file diff --git a/js/translater.js b/js/translater.js new file mode 100644 index 0000000..f2aa1de --- /dev/null +++ b/js/translater.js @@ -0,0 +1,64 @@ +/*global $, dotclear */ +'use strict'; +$(function () { + $('#module-backup-create-form .checkboxes-helpers').each(function () { + dotclear.checkboxesHelpers(this, undefined, '#module-backup-create-form td input[type=checkbox]', '#module-backup-create-form #do-action'); + }); + $('#module-backup-create-form td input[type=checkbox]').enableShiftClick(); + dotclear.condSubmit('#module-backup-create-form td input[type=checkbox]', '#module-backup-create-form #do-action'); + + $('#module-backup-edit-form .checkboxes-helpers').each(function () { + dotclear.checkboxesHelpers(this, undefined, '#module-backup-edit-form td input[type=checkbox]', '#module-backup-edit-form #do-action'); + }); + $('#module-backup-edit-form td input[type=checkbox]').enableShiftClick(); + dotclear.condSubmit('#module-backup-edit-form td input[type=checkbox]', '#module-backup-edit-form #do-action'); + + $('#module-pack-export-form .checkboxes-helpers').each(function () { + dotclear.checkboxesHelpers(this, undefined, '#module-pack-export-form td input[type=checkbox]', '#module-pack-export-form #do-action'); + }); + $('#module-pack-export-form td input[type=checkbox]').enableShiftClick(); + dotclear.condSubmit('#module-pack-export-form td input[type=checkbox]', '#module-pack-export-form #do-action'); + + $('#lang-edit-form .checkboxes-helpers').each(function () { + dotclear.checkboxesHelpers(this, undefined, '#lang-edit-form td input[type=checkbox]', undefined); + }); + $('#lang-edit-form td input[type=checkbox]').enableShiftClick(); + + const dctranslater = dotclear.getData('translater'); + $('.translaterline').each(function(){ + + var line = this; + var msgfile = $(line).children('.translatermsgfile'); + var msgstr = $(line).children('.translatermsgstr'); + var target = $(line).children('.translatertarget'); + var img = ''; + var tog = ''; + + $('.strlist').hide(); + + $(msgstr).children('.subtranslater').each(function(){ + var img_str = $(''+tog+'').css('cursor','pointer'); + $(this).children('strong').each(function(){ + var txt = $(this).text(); + var img_add = $(''+img+'').css('cursor','pointer'); + $(this).prepend(' ').prepend(img_add); + $(img_add).click(function(){$(target).children(':text').val(txt)}); + + $(this).append(' ').append(img_str); + var strlist=$(this).siblings('.strlist'); + $(strlist).click(function(){$(strlist).toggle();}); + $(img_str).click(function(){$(strlist).toggle();}); + }); + }); + + $(msgfile).children('.subtranslater').each(function(){ + var img_file = $(''+tog+'').css('cursor','pointer'); + $(this).children('strong').each(function(){ + $(this).append(' ').append(img_file); + var strlist=$(this).siblings('.strlist'); + $(strlist).click(function(){$(strlist).toggle();}); + $(img_file).click(function(){$(strlist).toggle();}); + }); + }); + }); +}); \ No newline at end of file diff --git a/locales/fr/main.lang.php b/locales/fr/main.lang.php deleted file mode 100644 index a1836c2..0000000 --- a/locales/fr/main.lang.php +++ /dev/null @@ -1,419 +0,0 @@ - 1);\n" - -#: _admin.php:25 -#: _admin.php:112 -#: index.php:536 -#: index.php:569 -#: index.php:1383 -msgid "Translater" -msgstr "Traducteur" - -#: _admin.php:43 -msgid "Translate plugins" -msgstr "Traduire les plugins" - -#: _admin.php:61 -msgid "Translate this plugin" -msgstr "Traduire ce plugin" - -#: _admin.php:84 -msgid "Translate this theme" -msgstr "Traduire ce thème" - -#: _uninstall.php:20 -msgid "delete all settings" -msgstr "effacer tous les paramètres" - -#: _uninstall.php:27 -msgid "delete plugin files" -msgstr "effacer les fichiers du plugin" - -#: _uninstall.php:34 -msgid "delete the version number" -msgstr "effacer le n° de version" - -#: _uninstall.php:42 -msgid "delete all %s settings" -msgstr "effacer tous les paramètres de %s" - -#: _uninstall.php:49 -msgid "delete %s plugin files" -msgstr "effacer les fichiers du plugin %s" - -#: _uninstall.php:56 -msgid "delete %s version number" -msgstr "effacer la version de %s" - -#: inc/class.dc.translater.php:359 -msgid "Cannot find module %s" -msgstr "Impossible de trouver le module %s" - -#: inc/class.dc.translater.php:437 -msgid "Cannot find backups folder for module %s" -msgstr "Impossible de trouver le dossier de sauvegarde du module %s" - -#: inc/class.dc.translater.php:453 -msgid "Cannot find languages folder for module %s" -msgstr "Impossible de trouver le dossier de langue du module %s" - -#: inc/class.dc.translater.php:473 -msgid "Cannot find root folder for module %s" -msgstr "Impossible de trouver le dossier racine du module %s" - -#: inc/class.dc.translater.php:502 -msgid "Limit of %s backups for module %s exceed" -msgstr "La limite de %s sauvegardes pour le module % est dépassée" - -#: inc/class.dc.translater.php:564 -#: inc/class.dc.translater.php:954 -#: inc/class.dc.translater.php:1031 -msgid "Cannot find language folder %s for module %s" -msgstr "Impossible de trouver le dossier de langue %s pour le module %s" - -#: inc/class.dc.translater.php:619 -msgid "Cannot delete backup file %s" -msgstr "Impossible d'effacer le fichier de sauvegarde %s" - -#: inc/class.dc.translater.php:637 -msgid "Cannot find backup file %s" -msgstr "Impossible de trouver le fichier de sauvegarde %s" - -#: inc/class.dc.translater.php:663 -msgid "Wrong export query" -msgstr "Mauvaise requète d'exportation" - -#: inc/class.dc.translater.php:674 -msgid "Cannot use export mask %s" -msgstr "Impossible d'utiliser le masque d'exportation %s" - -#: inc/class.dc.translater.php:762 -msgid "Wrong import query" -msgstr "Mauvaise requète d'importation" - -#: inc/class.dc.translater.php:811 -msgid "Nothing to import for these modules in pack %s" -msgstr "Rien à importer pour ces modules dans le paquetage %s" - -#: inc/class.dc.translater.php:840 -msgid "Zip file %s is not in translater format" -msgstr "Le fichier zip n'est pas au format de translater" - -#: inc/class.dc.translater.php:900 -msgid "Language %s already exists for module %s" -msgstr "La langue %s existe dèjà pour le module %s" - -#: inc/class.dc.translater.php:911 -msgid "Cannot copy file from language %s for module %s" -msgstr "Impossible de copier le fichier depuis le langage %s pour le module %s" - -#: inc/class.dc.translater.php:1003 -msgid "No string to write, language %s deleted for module %s" -msgstr "Aucune chaine à écrire, la langue %s a été effacée pour le module %s" - -#: inc/class.dc.translater.php:1267 -msgid "Cannot grant write acces on lang file %s" -msgstr "Impossible d'avoir les droits en écriture sur le fichier de langue %s" - -#: inc/class.dc.translater.php:1278 -msgid "Cannot write lang file %s" -msgstr "Impossible d'écrire le fichier de langue %s" - -#: inc/class.dc.translater.php:1583 -msgid "Cannot find language for code %s" -msgstr "Impossible de trouver de langue pour le code %s" - -#: inc/class.translater.rest.php:41 -msgid "Missing params" -msgstr "Paramètres manquants" - -#: inc/class.translater.rest.php:50 -msgid "Failed to get translation tool" -msgstr "Impossible de trouver l'outil de traduction" - -#: inc/class.translater.rest.php:54 -msgid "Translation tool is not configured" -msgstr "L'outil de traduction n'est pas configuré" - -#: inc/lib.translater.google.php:43 -msgid "You must have on Google API console:" -msgstr "Vous devez avoir sur la console des API Google :" - -#: inc/lib.translater.google.php:46 -msgid "Activate the \"translate API\" service" -msgstr "Activer le service \"translate API\"" - -#: inc/lib.translater.google.php:79 -msgid "Failed to query service." -msgstr "Impossible d'interroger le service." - -#: inc/lib.translater.microsoft.php:48 -msgid "You must have:" -msgstr "Vous devez avoir :" - -#: index.php:25 -#: index.php:525 -#: index.php:1381 -msgid "Settings" -msgstr "Paramètres" - -#: index.php:27 -#: index.php:426 -#: index.php:462 -#: index.php:500 -#: index.php:571 -msgid "Themes" -msgstr "Thèmes" - -#: index.php:41 -msgid "locales folders of each module" -msgstr "le répertoire locales de chaque module" - -#: index.php:42 -msgid "plugins folder root" -msgstr "le répertoire racine des plugins" - -#: index.php:43 -msgid "public folder root" -msgstr "le répertoire public du blog" - -#: index.php:44 -msgid "cache folder of Dotclear" -msgstr "le répertoire cache de Dotclear" - -#: index.php:45 -msgid "locales folder of translater" -msgstr "le répertoire locales du plugin translater" - -#: index.php:50 -msgid "Translation successfully updated" -msgstr "Traduction mise à jour avec succès" - -#: index.php:51 -msgid "Translation successfully created" -msgstr "Traduction créée avec succès" - -#: index.php:52 -msgid "Translation successfully deleted" -msgstr "Traduction effacée avec succès" - -#: index.php:53 -msgid "Backups successfully create" -msgstr "Sauvegardes créées avec succès" - -#: index.php:54 -msgid "Backups successfully restored" -msgstr "Sauvegardes effectuées avec succès" - -#: index.php:55 -msgid "Backups successfully deleted" -msgstr "Sauvegardes effacées avec succès" - -#: index.php:56 -msgid "Package successfully imported" -msgstr "Paquetage importé avec succès" - -#: index.php:57 -msgid "Package successfully exported" -msgstr "Paquetage exporté avec succès" - -#: index.php:61 -msgid "Failed to update settings: %s" -msgstr "Impossible de mettre à jour les paramètres : %s" - -#: index.php:62 -msgid "Failed to update translation: %s" -msgstr "Impossible de mettre à jour la traduction : %s" - -#: index.php:63 -msgid "Failed to create translation: %s" -msgstr "Impossible de créer la traduction : %s" - -#: index.php:64 -msgid "Failed to delete translation: %s" -msgstr "Impossible d'effacer la traduction : %s" - -#: index.php:65 -msgid "Failed to create backups: %s" -msgstr "Impossible de créer les sauvegardes : %s" - -#: index.php:66 -msgid "Failed to restore backups: %s" -msgstr "Impossible de restaurer les sauvegardes : %s" - -#: index.php:67 -msgid "Failed to delete backups: %s" -msgstr "Impossible d'effacer les sauvegardes : %s" - -#: index.php:68 -msgid "Failed to import package: %s" -msgstr "Impossible d'importer le paquetage : %s" - -#: index.php:69 -msgid "Failed to export package: %s" -msgstr "Impossible d'exporter le paquetage : %s" - -#: index.php:111 -msgid "No lang to create" -msgstr "Pas de langue à créer" - -#: index.php:133 -msgid "No lang to delete" -msgstr "Pas de langue à effacer" - -#: index.php:155 -msgid "No lang to backup" -msgstr "Pas de langue à sauvegarder" - -#: index.php:188 -msgid "No blackup to restore" -msgstr "Pas de langue à restaurer" - -#: index.php:207 -msgid "No bakcup to to restore" -msgstr "Pas de sauvegarde à restaurer" - -#: index.php:228 -#: index.php:246 -msgid "No backup to delete" -msgstr "Pas de sauvegarde à effacer" - -#: index.php:289 -#: index.php:363 -msgid "Nothing to export" -msgstr "Rien à exporter" - -#: index.php:311 -msgid "No language to update" -msgstr "Pas de langue à mettre à jour" - -#: index.php:442 -#: index.php:489 -msgid "Failed to launch translater: %s" -msgstr "Impossible de démarrer translater : %s" - -#: index.php:555 -msgid "Use this %s translation:" -msgstr "Utiliser la traduction de %s :" - -#: index.php:556 -msgid "Translate this text with %s" -msgstr "Traduire ce texte avec %s" - -#: index.php:557 -msgid "Use this text" -msgstr "Utiliser ce texte" - -#: index.php:605 -msgid "Translate theme \"%s\" (by %s)" -msgstr "Traduire le thème \"%s\" (de %s)" - -#: index.php:606 -msgid "Translate plugin \"%s\" (by %s)" -msgstr "Traduire le plugin \"%s\" (de %s)" - -#: index.php:626 -#: index.php:740 -msgid "Edit translation" -msgstr "Modifier la traduction" - -#: index.php:638 -msgid "Id" -msgstr "Id" - -#: index.php:648 -msgid "There is no editable modules" -msgstr "Il n'y a pas de modules modifiables." - -#: index.php:664 -msgid "Summary" -msgstr "Résumé" - -#: index.php:665 -msgid "Module" -msgstr "Module" - -#: index.php:667 -msgid "About" -msgstr "À propos" - -#: index.php:677 -msgid "Root" -msgstr "Racine" - -#: index.php:679 -#: index.php:693 -#: index.php:810 -#: index.php:1341 -msgid "Backups" -msgstr "Sauvegardes" - -#: index.php:694 -msgid "Last backup" -msgstr "Dernière sauvegarde" - -#: index.php:721 -msgid "no backup" -msgstr "Aucune sauvegarde" - -#: index.php:736 -msgid "Edit language" -msgstr "Modifier une langue" - -#: index.php:756 -msgid "Add language" -msgstr "Ajouter une langue" - -#: index.php:763 -msgid "Copy from language:" -msgstr "Copier depuis la langue :" - -#: index.php:765 -msgid "Optionnal" -msgstr "Optionnel" - -#: index.php:772 -msgid "Add translation" -msgstr "Ajouter la traduction" - -#: index.php:789 -msgid "Delete language" -msgstr "Effacer une langue" - -#: index.php:793 -msgid "Delete translation" -msgstr "Effacer la traduction" - -#: index.php:815 -msgid "Create backups" -msgstr "Créer une sauvegarde" - -#: index.php:817 -msgid "Choose languages to backup" -msgstr "Choisir la langue à sauvegarder" - -#: index.php:855 -msgid "List of backups" -msgstr "Liste des sauvegardes" - -#: index.php:859 -#: index.php:1065 -msgid "File" -msgstr "Fichier" - -#: index.php:886 -msgid "Selected backups action:" -msgstr "Action sur les sauvegardes sélectionnées :" - -#: index.php:888 -msgid "Restore backups" -msgstr "Restaurer les sauvegardes" - -#: index.php:889 -msgid "Delete backups" -msgstr "Effacer les sauvegardes" - -#: index.php:915 -#: index.php:1182 -msgid "Choose language package to import" -msgstr "Choisir le paquetage de langue à importer" - -#: index.php:937 -#: index.php:1239 -msgid "Choose languages to export" -msgstr "Choisir les langues à exporter" - -#: index.php:1058 -msgid "Group" -msgstr "Groupe" - -#: index.php:1060 -msgid "String" -msgstr "Chaîne" - -#: index.php:1062 -#: index.php:1283 -msgid "Translation" -msgstr "Traduction" - -#: index.php:1063 -msgid "Existing" -msgstr "Existant" - -#: index.php:1126 -msgid "%s occurrences" -msgstr "%s occurrences" - -#: index.php:1152 -msgid "Total of %s strings." -msgstr "Total de %s chaînes." - -#: index.php:1154 -msgid "Change the group of the selected entries to:" -msgstr "Changer le groupe des entrées sélectionnées vers :" - -#: index.php:1211 -msgid "Choose modules to export" -msgstr "Choisir les modules à exporter" - -#: index.php:1213 -msgid "Modules" -msgstr "Modules" - -#: index.php:1286 -msgid "Write .po files" -msgstr "Écrire les fichiers .po" - -#: index.php:1289 -msgid "Write .lang.php files" -msgstr "Écrire les fichiers .lang.php" - -#: index.php:1292 -msgid "Translate also strings of template files" -msgstr "Traduire également les chaînes des fichiers de template" - -#: index.php:1295 -msgid "Translate only unknow strings" -msgstr "Traduire uniquement les chaînes inconnues" - -#: index.php:1298 -msgid "Hide default modules of Dotclear" -msgstr "Cacher les modules d'origine de Dotclear" - -#: index.php:1301 -msgid "Write comments in files" -msgstr "Écrire les commentaires dans les fichiers" - -#: index.php:1304 -msgid "Write informations about author in files" -msgstr "Écrire les informations à propos de l'auteur dans les fichiers" - -#: index.php:1308 -msgid "Tools" -msgstr "Outils" - -#: index.php:1309 -msgid "Default language of l10n source:" -msgstr "Langue par défaut des sources l10n :" - -#: index.php:1313 -msgid "Select and configure the tool to use to translate strings:" -msgstr "Sélectionner et configurer un outil de traduction :" - -#: index.php:1325 -msgid "Nothing to configure for %s tool." -msgstr "Rien à configurer pour l'outil %s." - -#: index.php:1336 -msgid "Overwrite existing languages" -msgstr "Écraser les langages existants" - -#: index.php:1337 -msgid "Name of exported package" -msgstr "Nom du paquetage exporté" - -#: index.php:1344 -msgid "Make backups when changes are made" -msgstr "Faire des sauvegardes lors des modifications" - -#: index.php:1345 -msgid "Limit backups to %s files per module" -msgstr "Limiter les sauvegardes à %s fichiers par module" - -#: index.php:1348 -msgid "Store backups in %s" -msgstr "Stocker les sauvegardes dans %s" - -#: index.php:1353 -msgid "Behaviors" -msgstr "Comportements" - -#: index.php:1354 -msgid "Default start menu:" -msgstr "Menu de départ par défaut :" - -#: index.php:1359 -msgid "Enable menu on extensions page" -msgstr "Activer le menu sur la page des plugins" - -#: index.php:1362 -msgid "Enable menu on themes page" -msgstr "Activer le menu sur la page des thèmes" - -msgid "translater" -msgstr "Traducteur" - -msgid "Translate your Dotclear plugins and themes" -msgstr "Traduire vos plugins et thèmes pour Dotclear" -