full rework (in progress) see changelog

master
Jean-Christian Paul Denis 2021-09-25 01:58:59 +02:00
parent bdb4b6d865
commit 174e7a4090
Signed by: JcDenis
GPG Key ID: 1B5B8C5B90B6C951
16 changed files with 1962 additions and 3647 deletions

View File

@ -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

View File

@ -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');
}
}

View File

@ -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 '
<div class="fieldset">
<h4>' . __('Translation') . '</h4>
<p><label class="classic">' .
form::checkbox('translater_write_po', '1' ,$translater->write_po) . '
' . __('Write .po files') . '</label></p>
<p><label class="classic">' .
form::checkbox('translater_write_langphp', '1', $translater->write_langphp) . '
' . __('Write .lang.php files') . '</label></p>
<p><label class="classic">' .
form::checkbox('translater_scan_tpl', '1', $translater->scan_tpl) . '
' . __('Translate also strings of template files') . '</label></p>
<p><label class="classic">' .
form::checkbox('translater_parse_nodc', '1', $translater->parse_nodc) . '
' . __('Translate only unknow strings') . '</label></p>
<p><label class="classic">' .
form::checkbox('translater_hide_default', '1', $translater->hide_default) . '
' . __('Hide default modules of Dotclear') . '</label></p>
<p><label class="classic">' .
form::checkbox('translater_parse_comment', '1', $translater->parse_comment) . '
' . __('Write comments in files') . '</label></p>
<p><label class="classic">' .
form::checkbox('translater_parse_user', '1', $translater->parse_user) . '
' . __('Write informations about author in files') . '</label><br />
' . form::field('translater_parse_userinfo', 65, 255, $translater->parse_userinfo) . '</p>
</div>
<div class="fieldset">
<h4>' . __('Tools') . '</h4>
<p><label class="classic">' . __('Default language of l10n source:') . '<br />' .
form::combo('translater_proposal_lang',
array_flip($translater->getIsoCodes()), $translater->proposal_lang) . '</label></p>
<h4>' . __('Select and configure the tool to use to translate strings:') . '</h4>';
foreach($translater->proposal->getTools() AS $k => $v) {
$form = $v->form();
echo '
<dd>
<dt><label class="classic">' .
form::radio('translater_proposal_tool', $k, $k == $translater->proposal_tool) . '
' . $v->getDesc() . '</label></dt><dd>' .
(empty($form) ?
'<p>' . sprintf(__('Nothing to configure for %s tool . '), $v->getName()) . '</p>' :
$form
) . '</dd></dl>';
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 '
<div class="fieldset"><h4>' . __('Translation') . '</h4>
<p><label for="write_po">' .
form::checkbox('write_po', '1' ,$translater->write_po) .
__('Write .po files') . '</label></p>
<p><label for="write_langphp">' .
form::checkbox('write_langphp', '1', $translater->write_langphp) .
__('Write .lang.php files') . '</label></p>
<p><label for="scan_tpl">' .
form::checkbox('scan_tpl', '1', $translater->scan_tpl) .
__('Translate also strings of template files') . '</label></p>
<p><label for="parse_nodc">' .
form::checkbox('parse_nodc', '1', $translater->parse_nodc) .
__('Translate only unknow strings') . '</label></p>
<p><label for="hide_default">' .
form::checkbox('hide_default', '1', $translater->hide_default) .
__('Hide default modules of Dotclear') . '</label></p>
<p><label for="parse_comment">' .
form::checkbox('parse_comment', '1', $translater->parse_comment) .
__('Write comments in files') . '</label></p>
<p><label for="parse_user">' .
form::checkbox('parse_user', '1', $translater->parse_user) .
__('Write informations about author in files') . '</label></p>
<p><label for="parse_userinfo">' . __('User info:') . '</label>' .
form::field('parse_userinfo', 65, 255, $translater->parse_userinfo) . '</p>
<p class="form-note">' . sprintf(
__('Following informations can be used: %s '), implode(', ', $translater::$allowed_user_informations)) . '
</p>
</div>
<div class="fieldset">
<h4>' . __('Import/Export') . '</h4>
<p><label class="classic">' .
form::checkbox('translater_import_overwrite', '1', $translater->import_overwrite) . '
' . __('Overwrite existing languages') . '</label></p>
<p><label class="classic">' . __('Name of exported package') . '<br />
' . form::field('translater_export_filename', 65, 255, $translater->export_filename) . '</label></p>
<div class="fieldset"><h4>' . __('Import/Export') . '</h4>
<p><label for="import_overwrite">' .
form::checkbox('import_overwrite', '1', $translater->import_overwrite) .
__('Overwrite existing languages') . '</label></p>
<p><label for="export_filename">' . __('Name of exported package:') . '</label>' .
form::field('export_filename', 65, 255, $translater->export_filename) . '</p>
</div>
<div class="fieldset">
<h4>' . __('Backups') . '</h4>
<p><label class="classic">' .
form::checkbox('translater_backup_auto', '1', $translater->backup_auto) . '
' . __('Make backups when changes are made') . '</label></p>
<p><label class="classic">' . sprintf(__('Limit backups to %s files per module'),
form::combo('translater_backup_limit',
array_flip($combo_backup_limit), $translater->backup_limit)) . '</label></p>
<p><label class="classic">' . sprintf(__('Store backups in %s'),
form::combo('translater_backup_folder',
array_flip($combo_backup_folder), $translater->backup_folder)) . '</label></p>
<div class="fieldset"><h4>' . __('Backups') . '</h4>
<p><label for="backup_auto">' .
form::checkbox('backup_auto', '1', $translater->backup_auto) .
__('Make backups when changes are made') . '</label></p>
<p><label for="backup_limit" class="classic">' . sprintf(__('Limit backups to %s files per module'),
form::number('backup_limit', ['min' => 0, 'max' => 50, 'default' => $translater->backup_limit])) . '</label></p>
<p><label for="backup_folder">' . __('Store backups in:') . '</label>' .
form::combo('backup_folder', $translater::$allowed_backup_folders, $translater->backup_folder) . '</p>
</div>
<div class="fieldset">
<h4>' . __('Behaviors') . '</h4>
<p><label class="classic">' . __('Default start menu:') . '<br />' .
form::combo('translater_start_page',
array_flip($combo_start_page), $translater->start_page) . '</label></p>
<p><label class="classic">' .
form::checkbox('translater_plugin_menu', '1', $translater->plugin_menu) . '
' . __('Enable menu on extensions page') . '</label></p>
<p><label class="classic">' .
form::checkbox('translater_theme_menu', '1', $translater->theme_menu) . '
' . __('Enable menu on themes page') . '</label></p>
</div>
';
<div class="fieldset"><h4>' . __('Behaviors') . '</h4>
<p><label for="start_page">' . __('Default start menu:') . '</label>' .
form::combo('start_page',[
__('Plugins') => 'plugin',
__('Themes') => 'theme',
__('Home') => '-'
], $translater->start_page) . '</p>
<p><label for="plugin_menu">' .
form::checkbox('plugin_menu', '1', $translater->plugin_menu) .
__('Enable menu on extensions page') . '</label></p>
<p><label for="theme_menu">' .
form::checkbox('theme_menu', '1', $translater->theme_menu) .
__('Enable menu on themes page') . '</label></p>
</div>';

View File

@ -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());

View File

@ -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';
$core->adminurl->register('translater', 'plugin.php', ['p' => 'translater']);

View File

@ -0,0 +1 @@
.addfield, .togglelist { border: none; }

View File

Before

Width:  |  Height:  |  Size: 321 B

After

Width:  |  Height:  |  Size: 321 B

View File

Before

Width:  |  Height:  |  Size: 209 B

After

Width:  |  Height:  |  Size: 209 B

View File

@ -0,0 +1,221 @@
<?php
/**
* @brief translater, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis & contributors
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
class dcTranslaterLang
{
/** @var dCore dcCore instance */
public $core = null;
/** @var dcTranslater dcTranslater instance */
public $translater = null;
/** @var dcTranslaterModule dcTranslaterModule instance */
public $module = null;
/** @var array Lang properies */
private $prop = [];
public function __construct(dcTranslaterModule $module, string $lang)
{
$this->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;
}
}

View File

@ -0,0 +1,749 @@
<?php
/**
* @brief translater, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis & contributors
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_CONTEXT_ADMIN')) {
return;
}
/**
* Translater tools.
*/
class dcTranslaterModule
{
/** @var dCore dcCore instance */
public $core = null;
/** @var dcTranslater dcTranslater instance */
public $translater = null;
/** @var array Module properies */
private $prop = [];
/** @var string Backup file regexp */
private $backup_file_regexp = '/^l10n-%s-(.*?)-[0-9]*?\.bck\.zip$/';
/** @var string Locales file regexp */
private $locales_file_regexp = '/^(.*?)\/locales\/(.*?)\/(.*?)(.po|.lang.php)$/';
public function __construct(dcTranslater $translater, array $module)
{
$this->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);
}
//@}
}

File diff suppressed because it is too large Load Diff

1580
index.php

File diff suppressed because it is too large Load Diff

View File

@ -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 = '<img src="index.php?pf=translater/inc/img/field.png"