1737 lines
67 KiB
PHP
Executable File
1737 lines
67 KiB
PHP
Executable File
#!/usr/bin/env php
|
|
<?php
|
|
/**
|
|
* Translation helper application for the Horde framework.
|
|
*
|
|
* Copyright 2013-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file COPYING for license information (LGPL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/lgpl.
|
|
*
|
|
* @category Horde
|
|
* @copyright 2013-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/lgpl LGPL-2
|
|
* @package Horde
|
|
*/
|
|
|
|
/**
|
|
* Base class for translation script.
|
|
*
|
|
* Implicitely set properties: gettext, msgattrib, msgcat, msgcomm, msgfmt,
|
|
* msginit, msgmerge, xgettext.
|
|
*/
|
|
class Horde_Translation_Script
|
|
{
|
|
/**
|
|
* CLI interface.
|
|
*
|
|
* @var Horde_Cli
|
|
*/
|
|
public $cli;
|
|
|
|
/**
|
|
* File_Find instance.
|
|
*
|
|
* @var File_Find
|
|
*/
|
|
public $ff;
|
|
|
|
/**
|
|
* The directory at startup time.
|
|
*
|
|
* @var string
|
|
*/
|
|
public $currentDir;
|
|
|
|
/**
|
|
* The directories of all found applications.
|
|
*
|
|
* @var array
|
|
*/
|
|
public $dirs = array();
|
|
|
|
/**
|
|
* All found applications.
|
|
*
|
|
* @var array
|
|
*/
|
|
public $apps = array();
|
|
|
|
/**
|
|
* Enable debug mode?
|
|
*
|
|
* @boolean
|
|
*/
|
|
public $debug = false;
|
|
|
|
/**
|
|
* Enable test mode?
|
|
*
|
|
* @boolean
|
|
*/
|
|
public $test = false;
|
|
|
|
/**
|
|
* Silencing appendix for error output.
|
|
*
|
|
* @var string
|
|
*/
|
|
public $silence;
|
|
|
|
/**
|
|
* Appendix to redirect error messages to standard output.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_redirErr;
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->_redirErr = substr(PHP_OS, 0, 3) == 'WIN' ? '' : ' 2>&1';
|
|
$this->currentDir = getcwd();
|
|
}
|
|
|
|
/**
|
|
* Shortcut for Horde_Cli::writeln().
|
|
*
|
|
* @param string $message The text to write on the screen.
|
|
*/
|
|
public function writeln($message = '')
|
|
{
|
|
$this->cli->writeln($message);
|
|
}
|
|
|
|
/**
|
|
* Prints the footer and halts the script.
|
|
*/
|
|
public function footer()
|
|
{
|
|
$this->writeln();
|
|
$this->writeln('Please report any bugs to i18n@lists.horde.org.');
|
|
|
|
chdir($this->currentDir);
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Prints usage information.
|
|
*/
|
|
public function usage()
|
|
{
|
|
if (count($this->options[1]) &&
|
|
($this->options[1][0] == 'help' && !empty($this->options[1][1]) ||
|
|
!empty($this->options[1][0]) && in_array($this->options[1][0], array('commit', 'compendium', 'extract', 'init', 'make', 'merge')))) {
|
|
if ($this->options[1][0] == 'help') {
|
|
$cmd = $this->options[1][1];
|
|
} else {
|
|
$cmd = $this->options[1][0];
|
|
}
|
|
$this->writeln('Usage:' . ' horde-translation [options] ' . $cmd . ' [command-options]');
|
|
if (!empty($cmd)) {
|
|
$this->writeln();
|
|
$this->writeln('Command options:');
|
|
}
|
|
switch ($cmd) {
|
|
case 'cleanup':
|
|
$this->writeln(' -l, --locale=ll Use only this locale.');
|
|
$this->writeln(' -m, --module=MODULE Cleanup PO files only for this (Horde) module.');
|
|
break;
|
|
case 'commit':
|
|
case 'commit-help':
|
|
$this->writeln(' -l, --locale=ll Use this locale.');
|
|
$this->writeln(' -m, --module=MODULE Commit translations only for this (Horde) module.');
|
|
$this->writeln(' -M, --message=MESSAGE Use this commit message instead of the default ones.');
|
|
$this->writeln(' -n, --new This is a new translation, commit also CREDITS,');
|
|
$this->writeln(' CHANGES and nls.php.');
|
|
break;
|
|
case 'compendium':
|
|
$this->writeln(' -a, --add=FILE Add this PO file to the compendium. Useful to');
|
|
$this->writeln(' include a compendium from a different branch to');
|
|
$this->writeln(' the generated compendium.');
|
|
$this->writeln(' -d, --directory=DIR Create compendium in this directory.');
|
|
$this->writeln(' -l, --locale=ll Use this locale.');
|
|
break;
|
|
case 'extract':
|
|
$this->writeln(' -m, --module=MODULE Generate POT file only for this (Horde) module.');
|
|
break;
|
|
case 'init':
|
|
$this->writeln(' -l, --locale=ll Use this locale.');
|
|
$this->writeln(' -m, --module=MODULE Create a PO file only for this (Horde) module.');
|
|
$this->writeln(' -c, --compendium=FILE Use this compendium file instead of the default');
|
|
$this->writeln(' one (compendium.po in the horde/locale directory).');
|
|
$this->writeln(' -n, --no-compendium Don\'t use a compendium.');
|
|
break;
|
|
case 'make':
|
|
$this->writeln(' -l, --locale=ll Use only this locale.');
|
|
$this->writeln(' -m, --module=MODULE Build MO files only for this (Horde) module.');
|
|
$this->writeln(' -c, --compendium=FILE Merge new translations to this compendium file');
|
|
$this->writeln(' instead of the default one (compendium.po in the');
|
|
$this->writeln(' horde/locale directory.');
|
|
$this->writeln(' -n, --no-compendium Don\'t merge new translations to the compendium.');
|
|
$this->writeln(' -s, --statistics Save translation statistics in a local file.');
|
|
break;
|
|
case 'make-help':
|
|
case 'update-help':
|
|
$this->writeln(' -l, --locale=ll Use only this locale.');
|
|
$this->writeln(' -m, --module=MODULE Update help files only for this (Horde) module.');
|
|
break;
|
|
case 'merge':
|
|
$this->writeln(' -l, --locale=ll Use this locale.');
|
|
$this->writeln(' -m, --module=MODULE Merge PO files only for this (Horde) module.');
|
|
$this->writeln(' -c, --compendium=FILE Use this compendium file instead of the default');
|
|
$this->writeln(' one (compendium.po in the horde/locale directory).');
|
|
$this->writeln(' -n, --no-compendium Don\'t use a compendium.');
|
|
break;
|
|
case 'update':
|
|
$this->writeln(' -l, --locale=ll Use this locale.');
|
|
$this->writeln(' -m, --module=MODULE Update only this (Horde) module.');
|
|
$this->writeln(' -c, --compendium=FILE Use this compendium file instead of the default');
|
|
$this->writeln(' one (compendium.po in the horde/locale directory).');
|
|
$this->writeln(' -n, --no-compendium Don\'t use a compendium.');
|
|
break;
|
|
}
|
|
} else {
|
|
$this->writeln('Usage:' . ' horde-translation [options] command [command-options]');
|
|
$this->writeln(str_repeat(' ', Horde_String::length('Usage:')) . ' horde-translation [help|-h|--help] [command]');
|
|
$this->writeln();
|
|
$this->writeln('Helper application to create and maintain translations for the Horde');
|
|
$this->writeln('framework and its applications.');
|
|
$this->writeln('For further information, see horde/docs/TRANSLATIONS.');
|
|
$this->writeln();
|
|
$this->writeln('Commands:');
|
|
$this->writeln(' help Show this help message.');
|
|
$this->writeln(' compendium Rebuild the compendium file. Warning: This overwrites the');
|
|
$this->writeln(' current compendium.');
|
|
$this->writeln(' extract Generate PO template (.pot) files.');
|
|
$this->writeln(' init Create one or more PO files for a new locale. Warning: This');
|
|
$this->writeln(' overwrites the existing PO files of this locale.');
|
|
$this->writeln(' merge Merge the current PO file with the current PO template file.');
|
|
$this->writeln(' update Run extract and merge sequent.');
|
|
$this->writeln(' update-help Extract all new and changed entries from the English XML help');
|
|
$this->writeln(' file and merge them with the existing ones.');
|
|
$this->writeln(' cleanup Cleans the PO files up from untranslated and obsolete entries.');
|
|
$this->writeln(' make Build binary MO files from the specified PO files.');
|
|
$this->writeln(' make-help Mark all entries in the XML help file being up-to-date and');
|
|
$this->writeln(' prepare the file for the next execution of update-help. You');
|
|
$this->writeln(' should only run make-help AFTER update-help and revising the');
|
|
$this->writeln(' help file.');
|
|
$this->writeln(' commit Commit translations (developers only).');
|
|
$this->writeln(' commit-help Commit help files (developers only).');
|
|
}
|
|
|
|
$this->writeln();
|
|
$this->writeln('Options:');
|
|
$this->writeln(' -b, --base=/PATH Full path to the (Horde) base directory that should be');
|
|
$this->writeln(' used.');
|
|
$this->writeln(' -d, --debug Show error messages from the executed binaries.');
|
|
$this->writeln(' -h, --help Show this help message.');
|
|
$this->writeln(' -t, --test Show the executed commands but don\'t run anything.');
|
|
}
|
|
|
|
/**
|
|
* Checks that all necessary binaries are available and have the correct
|
|
* version.
|
|
*
|
|
* Also sets the binary locations as object properties,
|
|
* e.g. $this->msgattrib, etc.
|
|
*/
|
|
public function check_binaries()
|
|
{
|
|
$this->writeln('Searching gettext binaries...');
|
|
foreach (array('gettext', 'msgattrib', 'msgcat', 'msgcomm', 'msgfmt', 'msginit', 'msgmerge', 'xgettext') as $binary) {
|
|
$this->$binary = System::which($binary);
|
|
if (!$this->$binary) {
|
|
$this->cli->message($binary . ' not found', 'cli.error');
|
|
$this->footer();
|
|
}
|
|
$this->cli->message($binary . ' found: ' . $this->$binary, 'cli.success');
|
|
}
|
|
$this->writeln();
|
|
|
|
$out = '';
|
|
exec($this->gettext . ' --version', $out, $ret);
|
|
$split = explode(' ', $out[0]);
|
|
$version_string = 'gettext version: ' . $split[count($split) - 1];
|
|
$gettext_version = explode('.', $split[count($split) - 1]);
|
|
if ($gettext_version[0] == 0 && $gettext_version[1] < 12) {
|
|
$this->writeln();
|
|
$this->cli->message($version_string, 'cli.error');
|
|
$this->cli->message('Your gettext version is too old and does not support PHP natively.', 'cli.error');
|
|
$this->footer();
|
|
}
|
|
$this->cli->message($version_string, 'cli.success');
|
|
$this->writeln();
|
|
}
|
|
|
|
/**
|
|
* Searches for files matching a PCRE.
|
|
*
|
|
* @param string $file Regular expression of the file names to search
|
|
* for.
|
|
* @param string $dir The directory to search.
|
|
* @param boolean $local Whether to search only the directory. If false,
|
|
* all sub-directories will be searched too.
|
|
*
|
|
* @return array A list of file names.
|
|
*/
|
|
public function search_file($file, $dir = '.', $local = false)
|
|
{
|
|
if (substr($file, 0, 1) != '/') {
|
|
$file = "/$file/";
|
|
}
|
|
|
|
if ($local) {
|
|
$files = $this->ff->glob($file, $dir, 'perl');
|
|
$files = array_map(create_function('$file', 'return "' . $dir . DS . '" . $file;'), $files);
|
|
return $files;
|
|
}
|
|
return $this->ff->search($file, $dir, 'perl', false);
|
|
}
|
|
|
|
/**
|
|
* Searches for files with a certain extension.
|
|
*
|
|
* @param string $ext The extension to search for.
|
|
* @param string $dir The directory to search.
|
|
* @param boolean $local Whether to search only the directory. If false,
|
|
* all sub-directories will be searched too.
|
|
*
|
|
* @return array A list of file names.
|
|
*/
|
|
public function search_ext($ext, $dir = '.', $local = false)
|
|
{
|
|
return $this->search_file("^[^.].*\\.$ext\$", $dir, $local);
|
|
}
|
|
|
|
/**
|
|
* Returns all .po files from a directory.
|
|
*
|
|
* @param string $dir The directory to search.
|
|
*
|
|
* @return array A list of .po files.
|
|
*/
|
|
public function get_po_files($dir)
|
|
{
|
|
$langs = $this->search_ext('po', $dir);
|
|
if (($key = array_search($dir . DS . 'messages.po', $langs)) !== false) {
|
|
unset($langs[$key]);
|
|
}
|
|
if (($key = array_search($dir . DS . 'compendium.po', $langs)) !== false) {
|
|
unset($langs[$key]);
|
|
}
|
|
return $langs;
|
|
}
|
|
|
|
/**
|
|
* Returns all translation languages from a directory.
|
|
*
|
|
* @param string $dir The directory to search.
|
|
*
|
|
* @return array A list of languages.
|
|
*/
|
|
public function get_languages($dir)
|
|
{
|
|
chdir($dir);
|
|
$langs = $this->get_po_files('locale');
|
|
$langs = array_map('basename', array_map('dirname', array_map('dirname', $langs)));
|
|
chdir($this->currentDir);
|
|
return $langs;
|
|
}
|
|
|
|
/**
|
|
* Searches all translateable applications and framework libraries.
|
|
*/
|
|
public function search_modules()
|
|
{
|
|
if (is_dir(BASE . '/base/locale')) {
|
|
$this->dirs[] = BASE . '/base';
|
|
} elseif (is_dir(BASE . '/locale')) {
|
|
$this->dirs[] = BASE;
|
|
}
|
|
$dh = opendir(BASE);
|
|
if (!$dh) {
|
|
return array();
|
|
}
|
|
while ($entry = readdir($dh)) {
|
|
$dir = BASE . '/' . $entry;
|
|
if (!is_dir($dir) ||
|
|
substr($entry, 0, 1) == '.' ||
|
|
fileinode(HORDE_BASE) == fileinode($dir)) {
|
|
continue;
|
|
}
|
|
$sub = opendir($dir);
|
|
if (!$sub) {
|
|
continue;
|
|
}
|
|
while ($subentry = readdir($sub)) {
|
|
if ($subentry == 'locale' && is_dir($dir . '/' . $subentry)) {
|
|
$this->dirs[] = $dir;
|
|
break;
|
|
}
|
|
if ($entry != 'framework') {
|
|
continue;
|
|
}
|
|
$framework = opendir($dir . '/' . $subentry);
|
|
if (!$framework) {
|
|
continue;
|
|
}
|
|
while ($package = readdir($framework)) {
|
|
if ($package == 'locale' &&
|
|
is_dir($dir . '/' . $subentry . '/' . $package)) {
|
|
$this->dirs[] = $dir . '/' . $subentry;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->apps = $this->strip_horde($this->dirs);
|
|
$this->apps[0] = 'horde';
|
|
}
|
|
|
|
/**
|
|
* Converts path names into application or library names.
|
|
*
|
|
* @param string|array $file A path name or a list of path names.
|
|
*
|
|
* @return string|array A module name or a list of module names.
|
|
*/
|
|
public function strip_horde($file)
|
|
{
|
|
if (is_array($file)) {
|
|
return array_map(array($this, 'strip_horde'), $file);
|
|
}
|
|
$info = Horde_Yaml::loadFile($file . '/.horde.yml');
|
|
switch ($info['type']) {
|
|
case 'application':
|
|
return $info['id'];
|
|
case 'library':
|
|
return 'Horde_' . $info['id'];
|
|
}
|
|
return basename($file);
|
|
}
|
|
|
|
/**
|
|
* Extracts messages from the source code.
|
|
*
|
|
* @param array $options Command line arguments.
|
|
*/
|
|
public function xtract($options)
|
|
{
|
|
foreach ($options as $option) {
|
|
switch ($option[0]) {
|
|
case 'h':
|
|
$this->usage();
|
|
$this->footer();
|
|
case 'm':
|
|
case '--module':
|
|
$module = $option[1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
for ($i = 0; $i < count($this->dirs); $i++) {
|
|
if (!empty($module) && $module != $this->apps[$i]) {
|
|
continue;
|
|
}
|
|
printf('Extracting from %s... ', $this->apps[$i]);
|
|
chdir($this->dirs[$i]);
|
|
/* Match all *.php and *.inc files in the current directory and
|
|
* sub-directories, unless they match *.local.php or have a
|
|
* directory name *.d/ in the path. */
|
|
$regexp = ';(?<!\.d)/[^.][^/]*?\.((?<!\.local\.)php|inc)$;';
|
|
if ($this->apps[$i] == 'horde') {
|
|
$files = glob('*.php');
|
|
foreach (array('admin', 'bin', 'config', 'lib', 'rpc', 'scripts', 'services', 'templates', 'themes', 'util') as $dir) {
|
|
$files = array_merge($files, $this->ff->search($regexp, $dir, 'perl', true));
|
|
}
|
|
} else {
|
|
$files = $this->ff->search($regexp, '.', 'perl', true);
|
|
}
|
|
$file = 'locale' . DS . $this->apps[$i] . '.pot';
|
|
/* Store the file list because it gets too long to be passed on the
|
|
* command line. */
|
|
file_put_contents($file . '.list', implode("\n", $files));
|
|
if (file_exists($file) && !is_writable($file)) {
|
|
$this->climessage(sprintf('%s is not writable.', $file), 'cli.error');
|
|
$this->footer();
|
|
}
|
|
/* We must use a .pot extension, otherwise msgcomm complains about
|
|
* an invalid charset being used in this file. */
|
|
$tmp_file = $file . '.tmp.pot';
|
|
|
|
$sh = $this->xgettext
|
|
. ' --language=PHP'
|
|
. ' --from-code=iso-8859-1'
|
|
. ' --keyword=_ --keyword=ngettext --keyword=t --keyword=n --keyword=r'
|
|
. ' --sort-output'
|
|
. ' --package-name=' . ($this->apps[$i] == 'imp' ? 'IMP' : ucfirst($this->apps[$i]))
|
|
. ' --copyright-holder="Horde LLC (http://www.horde.org/)"'
|
|
. ' --msgid-bugs-address="dev@lists.horde.org"'
|
|
. ' --files-from=' . $file . '.list'
|
|
. ' --output=' . $tmp_file;
|
|
if ($this->debug) {
|
|
$sh .= $this->silence;
|
|
}
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln('Executing:');
|
|
$this->writeln($sh);
|
|
}
|
|
if (!$this->test) {
|
|
exec($sh);
|
|
}
|
|
unlink($file . '.list');
|
|
$app = $this->apps[$i] == 'imp' ? 'IMP' : ucfirst($this->apps[$i]);
|
|
file_put_contents($tmp_file, str_replace('PACKAGE package.', $app . ' package.', file_get_contents($tmp_file)));
|
|
|
|
$diff = array();
|
|
if (file_exists($tmp_file)) {
|
|
/* Search for Horde_Template template files and extract <gettext>
|
|
* tags manually. */
|
|
$files = $this->search_ext('html', 'templates');
|
|
if (!$this->test) $tmp = fopen($file . '.templates', 'w');
|
|
foreach ($files as $template) {
|
|
$fp = fopen($template, 'r');
|
|
$lineno = 0;
|
|
while (($line = fgets($fp, 4096)) !== false) {
|
|
$lineno++;
|
|
$offset = 0;
|
|
while (($left = strpos($line, '<gettext>', $offset)) !== false) {
|
|
$left += 9;
|
|
$buffer = '';
|
|
$linespan = 0;
|
|
while (($end = strpos($line, '</gettext>', $left)) === false) {
|
|
$buffer .= substr($line, $left);
|
|
$left = 0;
|
|
$line = fgets($fp, 4096);
|
|
$linespan++;
|
|
if ($line === false) {
|
|
$this->climessage(sprintf("<gettext> tag not closed in file %s.\nOpening tag found in line %d.", $template, $lineno), 'cli.warning');
|
|
break 2;
|
|
}
|
|
}
|
|
$buffer .= substr($line, $left, $end - $left);
|
|
if (!$this->test) {
|
|
fwrite($tmp, "#: $template:$lineno\n");
|
|
fwrite($tmp, 'msgid "' . str_replace(array('"', "\n"), array('\"', "\\n\"\n\""), $buffer) . "\"\n");
|
|
fwrite($tmp, 'msgstr ""' . "\n\n");
|
|
}
|
|
$offset = $end + 10;
|
|
}
|
|
}
|
|
fclose($fp);
|
|
}
|
|
|
|
/* Merge with the base .pot file. */
|
|
if (!$this->test) fclose($tmp);
|
|
$sh = $this->msgcomm . " --more-than=0 --sort-output \"$tmp_file\" \"$file.templates\" --output-file \"$tmp_file\"" . $this->silence;
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln('Executing:');
|
|
$this->writeln($sh);
|
|
}
|
|
if (!$this->test) {
|
|
exec($sh);
|
|
unlink($file . '.templates');
|
|
}
|
|
|
|
/* Parse conf.xml files for <configphp> tags. */
|
|
if (file_exists('config/conf.xml')) {
|
|
if (!$this->test) $tmp = fopen($file . '.config', 'w');
|
|
$conf_content = file_get_contents('config/conf.xml');
|
|
if (!$this->test &&
|
|
preg_match_all('/<configphp .*?>([^<]*_\(".+?"\)[^<]*)<\/configphp>/s',
|
|
$conf_content, $matches)) {
|
|
foreach ($matches[1] as $configphp) {
|
|
if (!preg_match_all('/_\("(.+?)"\)/', $configphp, $strings)) {
|
|
continue;
|
|
}
|
|
foreach ($strings[1] as $string) {
|
|
fwrite($tmp, "#: config/conf.xml\n");
|
|
fwrite($tmp, 'msgid "' . $string . "\"\n");
|
|
fwrite($tmp, 'msgstr ""' . "\n\n");
|
|
}
|
|
}
|
|
}
|
|
if (!$this->test) fclose($tmp);
|
|
|
|
/* Merge with the base .pot file. */
|
|
$sh = $this->msgcomm . " --more-than=0 --sort-output \"$tmp_file\" \"$file.config\" --output-file \"$tmp_file\"" . $this->silence;
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln('Executing:');
|
|
$this->writeln($sh);
|
|
}
|
|
if (!$this->test) {
|
|
exec($sh);
|
|
unlink($file . '.config');
|
|
}
|
|
}
|
|
|
|
/* Check if the new .pot file has any changed content at
|
|
* all. */
|
|
if (file_exists($file)) {
|
|
$diff = array_merge(array_diff(file($tmp_file), file($file)),
|
|
array_diff(file($file), file($tmp_file)));
|
|
$diff = preg_grep('/^("POT-Creation-Date:|"Project-Id-Version:)/', $diff, PREG_GREP_INVERT);
|
|
}
|
|
}
|
|
if (!file_exists($file) || count($diff)) {
|
|
if (file_exists($file)) {
|
|
unlink($file);
|
|
}
|
|
rename($tmp_file, $file);
|
|
$this->writeln($this->cli->green('updated'));
|
|
} else {
|
|
if (file_exists($tmp_file)) {
|
|
unlink($tmp_file);
|
|
}
|
|
$this->writeln($this->cli->bold('not changed'));
|
|
}
|
|
chdir($this->currentDir);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Merges old translations with new .pot files and optionally a compendium.
|
|
*
|
|
* @param array $options Command line arguments.
|
|
*/
|
|
public function merge($options)
|
|
{
|
|
$compendium = ' --compendium="' . HORDE_BASE . DS . 'locale' . DS . 'compendium.po"';
|
|
foreach ($options as $option) {
|
|
switch ($option[0]) {
|
|
case 'h':
|
|
$this->usage();
|
|
$this->footer();
|
|
case 'l':
|
|
case '--locale':
|
|
$lang = $option[1];
|
|
break;
|
|
case 'm':
|
|
case '--module':
|
|
$module = $option[1];
|
|
break;
|
|
case 'c':
|
|
case '--compendium':
|
|
$compendium = ' --compendium=' . $option[1];
|
|
break;
|
|
case 'n':
|
|
case '--no-compendium':
|
|
$compendium = '';
|
|
break;
|
|
}
|
|
}
|
|
|
|
$this->cleanup($options);
|
|
|
|
for ($i = 0; $i < count($this->dirs); $i++) {
|
|
if (!empty($module) && $module != $this->apps[$i]) {
|
|
continue;
|
|
}
|
|
$this->writeln(sprintf('Merging translation for module %s...', $this->cli->bold($this->apps[$i])));
|
|
$dir = $this->dirs[$i] . DS . 'locale' . DS;
|
|
$po = $dir . '%s' . DS . 'LC_MESSAGES' . DS . $this->apps[$i] . '.po';
|
|
if (empty($lang)) {
|
|
$langs = $this->get_languages($this->dirs[$i]);
|
|
} else {
|
|
if (!file_exists(sprintf($po, $lang))) {
|
|
$this->writeln('Skipped...');
|
|
$this->writeln();
|
|
continue;
|
|
}
|
|
$langs = array($lang);
|
|
}
|
|
foreach ($langs as $locale) {
|
|
$this->writeln(sprintf('Merging locale %s... ', $this->cli->bold($locale)));
|
|
$sh = $this->msgmerge
|
|
. sprintf(' --update -v%s "%s" "%s.pot"',
|
|
$compendium, sprintf($po, $locale), $dir . $this->apps[$i]);
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln('Executing:');
|
|
$this->writeln($sh);
|
|
}
|
|
if (!$this->test) exec($sh);
|
|
$this->writeln($this->cli->green('done'));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unused yet.
|
|
*
|
|
* @param array $options Command line arguments.
|
|
*/
|
|
public function status($options)
|
|
{
|
|
$output = 'status.html';
|
|
foreach ($options as $option) {
|
|
switch ($option[0]) {
|
|
case 'h':
|
|
$this->usage();
|
|
$this->footer();
|
|
case 'l':
|
|
case '--locale':
|
|
$lang = $option[1];
|
|
break;
|
|
case 'm':
|
|
case '--module':
|
|
$module = $option[1];
|
|
break;
|
|
case 'o':
|
|
case '--output':
|
|
$output = $option[1];
|
|
break;
|
|
}
|
|
}
|
|
for ($i = 0; $i < count($this->dirs); $i++) {
|
|
if (!empty($module) && $module != $this->apps[$i]) {
|
|
continue;
|
|
}
|
|
$this->writeln(sprintf('Generating status for module %s...', $this->cli->bold($this->apps[$i])));
|
|
if (empty($lang)) {
|
|
$langs = $this->get_languages($this->dirs[$i]);
|
|
} else {
|
|
if (!file_exists($this->dirs[$i] . '/locale/' . $lang . '/LC_MESSAGES/' . $this->apps[$i] . '.po')) {
|
|
$this->writeln('Skipped...');
|
|
$this->writeln();
|
|
continue;
|
|
}
|
|
$langs = array($lang);
|
|
}
|
|
foreach ($langs as $locale) {
|
|
$this->writeln(sprintf('Status for locale %s... ', $this->cli->bold($locale)));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds or updates a compendium.
|
|
*
|
|
* @param array $options Command line arguments.
|
|
*/
|
|
public function compendium($options)
|
|
{
|
|
$dir = HORDE_BASE . DS . 'locale' . DS;
|
|
$add = '';
|
|
foreach ($options as $option) {
|
|
switch ($option[0]) {
|
|
case 'h':
|
|
$this->usage();
|
|
$this->footer();
|
|
case 'l':
|
|
case '--locale':
|
|
$lang = $option[1];
|
|
break;
|
|
case 'd':
|
|
case '--directory':
|
|
$dir = $option[1];
|
|
break;
|
|
case 'a':
|
|
case '--add':
|
|
$add .= ' ' . $option[1];
|
|
break;
|
|
}
|
|
}
|
|
if (!isset($lang)) {
|
|
$this->cli->message('No locale specified.', 'cli.error');
|
|
$this->writeln();
|
|
$this->usage();
|
|
$this->footer();
|
|
}
|
|
printf('Merging all %s.po files to the compendium... ', $lang);
|
|
$pofiles = array();
|
|
for ($i = 0; $i < count($this->dirs); $i++) {
|
|
$pofile = $this->dirs[$i] . DS . 'locale' . DS . $lang . DS . 'LC_MESSAGES' . DS . $this->apps[$i] . '.po';
|
|
if (file_exists($pofile)) {
|
|
$pofiles[] = $pofile;
|
|
}
|
|
}
|
|
if (!empty($dir) && substr($dir, -1) != DS) {
|
|
$dir .= DS;
|
|
}
|
|
$sh = $this->msgcat . ' --sort-output ' . implode(' ', $pofiles) . $add . ' > ' . $dir . 'compendium.po ' . ($this->debug ? '' : $this->silence);
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln();
|
|
$this->writeln('Executing:');
|
|
$this->writeln($sh);
|
|
}
|
|
if ($this->test) {
|
|
$ret = 0;
|
|
} else {
|
|
exec($sh, $out, $ret);
|
|
}
|
|
if ($ret == 0) {
|
|
$this->writeln($this->cli->green('done'));
|
|
} else {
|
|
$this->writeln($this->cli->red('failed'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates initial translations.
|
|
*
|
|
* @param array $options Command line arguments.
|
|
*/
|
|
public function init($options)
|
|
{
|
|
foreach ($options as $option) {
|
|
switch ($option[0]) {
|
|
case 'h':
|
|
$this->usage();
|
|
$this->footer();
|
|
case 'l':
|
|
case '--locale':
|
|
$lang = $option[1];
|
|
break;
|
|
case 'm':
|
|
case '--module':
|
|
$module = $option[1];
|
|
break;
|
|
}
|
|
}
|
|
if (empty($lang)) {
|
|
$lang = getenv('LANG');
|
|
}
|
|
|
|
for ($i = 0; $i < count($this->dirs); $i++) {
|
|
if (!empty($module) && $module != $this->apps[$i]) {
|
|
continue;
|
|
}
|
|
printf('Initializing module %s... ', $this->apps[$i]);
|
|
$dir = $this->dirs[$i] . DS . 'locale' . DS;
|
|
$targetdir = $dir . $lang . DS . 'LC_MESSAGES';
|
|
$pot = $dir . $this->apps[$i] . '.pot';
|
|
$po = $targetdir . DS . $this->apps[$i] . '.po';
|
|
if (!file_exists($pot)) {
|
|
$this->writeln();
|
|
$this->cli->message(sprintf('%s not found. Run \'translation extract\' first.', $pot), 'cli.warning');
|
|
continue;
|
|
}
|
|
if (!is_dir($targetdir)) {
|
|
if ($this->debug) {
|
|
$this->writeln(sprintf('Making directory %s', $targetdir));
|
|
}
|
|
if (!$this->test && !System::mkdir("-p $targetdir")) {
|
|
$this->cli->message(sprintf('Could not create locale directory for locale %s:', $locale), 'cli.warning');
|
|
$this->writeln($targetdir);
|
|
$this->writeln();
|
|
continue;
|
|
}
|
|
}
|
|
$sh = $this->msginit . ' --no-translator -i ' . $pot;
|
|
if (!empty($lang)) {
|
|
$lcdir = $dir . $lang . DS . 'LC_MESSAGES';
|
|
$pofile = $lcdir . DS . $this->apps[$i] . '.po';
|
|
$sh .= ' --output-file ' . $po . ' --locale=' . $lang;
|
|
if (!is_dir($lcdir) && !System::mkdir('-p ' . $lcdir)) {
|
|
$this->cli->message(sprintf('Could not create locale directory for locale %s:', $locale), 'cli.warning');
|
|
$this->writeln($lcdir);
|
|
$this->writeln();
|
|
continue;
|
|
}
|
|
}
|
|
if (!$this->debug) {
|
|
$sh .= $this->silence;
|
|
}
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln();
|
|
$this->writeln('Executing:');
|
|
$this->writeln($sh);
|
|
}
|
|
if ($this->test) {
|
|
$ret = 0;
|
|
} else {
|
|
exec($sh, $out, $ret);
|
|
}
|
|
$app = $this->apps[$i] == 'imp' ? 'IMP' : ucfirst($this->apps[$i]);
|
|
file_put_contents($po,
|
|
str_replace(array('Language-Team: none',
|
|
'PACKAGE package.',
|
|
'Content-Type: text/plain; charset=ASCII'),
|
|
array('Language-Team: i18n@lists.horde.org',
|
|
$app . ' package.',
|
|
'Content-Type: text/plain; charset=UTF-8'),
|
|
file_get_contents($po)));
|
|
if ($ret == 0) {
|
|
$this->writeln($this->cli->green('done'));
|
|
} else {
|
|
$this->writeln($this->cli->red('failed'));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compiles translations to .mo files.
|
|
*
|
|
* @param array $options Command line arguments.
|
|
*/
|
|
public function make($options)
|
|
{
|
|
$compendium = HORDE_BASE . DS . 'locale' . DS . 'compendium.po';
|
|
$save_stats = false;
|
|
foreach ($options as $option) {
|
|
switch ($option[0]) {
|
|
case 'h':
|
|
$this->usage();
|
|
$this->footer();
|
|
case 'l':
|
|
case '--locale':
|
|
$lang = $option[1];
|
|
break;
|
|
case 'm':
|
|
case '--module':
|
|
$module = $option[1];
|
|
break;
|
|
case 'c':
|
|
case '--compendium':
|
|
$compendium = $option[1];
|
|
break;
|
|
case 'n':
|
|
case '--no-compendium':
|
|
$compendium = '';
|
|
break;
|
|
case 's':
|
|
case '--statistics':
|
|
$save_stats = true;
|
|
break;
|
|
}
|
|
}
|
|
$horde = array_search('horde', $this->apps);
|
|
$horde_msg = array();
|
|
$stats_array = array();
|
|
|
|
$stats = new Console_Table();
|
|
$stats->setHeaders(array('Module', 'Language', 'Translated', 'Fuzzy', 'Untranslated', 'Updated'));
|
|
|
|
for ($i = 0; $i < count($this->dirs); $i++) {
|
|
if (!empty($module) && $module != $this->apps[$i]) {
|
|
continue;
|
|
}
|
|
$this->writeln(sprintf('Building MO files for module %s...', $this->cli->bold($this->apps[$i])));
|
|
$dir = $this->dirs[$i] . DS . 'locale' . DS . '%s' . DS . 'LC_MESSAGES' . DS;
|
|
if (empty($lang)) {
|
|
$langs = $this->get_languages($this->dirs[$i]);
|
|
} else {
|
|
if (!file_exists(sprintf($dir, $lang) . $this->apps[$i] . '.po')) {
|
|
$this->writeln('Skipped...');
|
|
$this->writeln();
|
|
continue;
|
|
}
|
|
$langs = array($lang);
|
|
}
|
|
foreach ($langs as $locale) {
|
|
$this->writeln(sprintf('Building locale %s...', $this->cli->bold($locale)));
|
|
$targetdir = sprintf($dir, $locale);
|
|
$pofile = $targetdir . $this->apps[$i] . '.po';
|
|
|
|
/* Convert to unix linebreaks. */
|
|
$content = str_replace("\r", '', file_get_contents($pofile));
|
|
file_put_contents($pofile, $content);
|
|
|
|
/* Remember update date. */
|
|
$last_update = preg_match(
|
|
'/^"PO-Revision-Date: (\d{4}-\d{2}-\d{2})/m',
|
|
$content, $matches)
|
|
? $matches[1] : '';
|
|
|
|
/* Check PO file sanity. */
|
|
$sh = $this->msgfmt . " --check --output-file=/dev/null \"$pofile\" " . $this->_redirErr;
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln('Executing:');
|
|
$this->writeln($sh);
|
|
}
|
|
if ($this->test) {
|
|
$ret = 0;
|
|
} else {
|
|
exec($sh, $out, $ret);
|
|
}
|
|
if ($ret != 0) {
|
|
$this->cli->message('An error has occured:', 'cli.warning');
|
|
$this->writeln(implode("\n", $out));
|
|
$this->writeln();
|
|
if ($this->apps[$i] == 'horde') {
|
|
continue 2;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Compile MO file. */
|
|
$sh = $this->msgfmt . ' --statistics -o "' . $targetdir . $this->apps[$i] . '.mo" ';
|
|
if ($this->apps[$i] != 'horde' &&
|
|
substr($this->apps[$i], 0, 6) != 'Horde_') {
|
|
$horde_po = $this->dirs[$horde] . DS . 'locale' . DS . $locale . DS . 'LC_MESSAGES/horde.po';
|
|
if (!is_readable($horde_po)) {
|
|
$this->cli->message(sprintf('The Horde PO file for the locale %s does not exist:', $locale), 'cli.warning');
|
|
$this->writeln($horde_po);
|
|
$this->writeln();
|
|
$sh .= '"' . $targetdir . DS . $this->apps[$i] . '.po"';
|
|
} else {
|
|
$sh = $this->msgcomm . " --more-than=0 --sort-output \"$pofile\" \"$horde_po\" | $sh -";
|
|
}
|
|
} else {
|
|
$sh .= '"' . $pofile . '"';
|
|
}
|
|
$sh .= $this->_redirErr;
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln('Executing:');
|
|
$this->writeln($sh);
|
|
}
|
|
$out = '';
|
|
if ($this->test) {
|
|
$ret = 0;
|
|
} else {
|
|
exec($sh, $out, $ret);
|
|
}
|
|
if ($ret == 0) {
|
|
$this->writeln($this->cli->green('done'));
|
|
$messages = array(0, 0, 0, $last_update);
|
|
if (preg_match('/(\d+) translated/', $out[0], $match)) {
|
|
$messages[0] = $match[1];
|
|
if (substr($this->apps[$i], 0, 6) != 'Horde_' &&
|
|
isset($horde_msg[$locale])) {
|
|
$messages[0] -= $horde_msg[$locale][0];
|
|
if ($messages[0] < 0) $messages[0] = 0;
|
|
}
|
|
}
|
|
if (preg_match('/(\d+) fuzzy/', $out[0], $match)) {
|
|
$messages[1] = $match[1];
|
|
if (substr($this->apps[$i], 0, 6) != 'Horde_' &&
|
|
isset($horde_msg[$locale])) {
|
|
$messages[1] -= $horde_msg[$locale][1];
|
|
if ($messages[1] < 0) $messages[1] = 0;
|
|
}
|
|
}
|
|
if (preg_match('/(\d+) untranslated/', $out[0], $match)) {
|
|
$messages[2] = $match[1];
|
|
if (substr($this->apps[$i], 0, 6) != 'Horde_' &&
|
|
isset($horde_msg[$locale])) {
|
|
$messages[2] -= $horde_msg[$locale][2];
|
|
if ($messages[2] < 0) $messages[2] = 0;
|
|
}
|
|
}
|
|
if ($this->apps[$i] == 'horde') {
|
|
$horde_msg[$locale] = $messages;
|
|
}
|
|
$stats_array[$this->apps[$i]][$locale] = $messages;
|
|
$stats->addRow(array($this->apps[$i], $locale, $messages[0], $messages[1], $messages[2], $messages[3]));
|
|
} else {
|
|
$this->writeln($this->cli->red('failed'));
|
|
exec($sh, $out, $ret);
|
|
$this->writeln(implode("\n", $out));
|
|
}
|
|
if (count($langs) > 1) {
|
|
continue;
|
|
}
|
|
|
|
/* Merge translation into compendium. */
|
|
if (!empty($compendium)) {
|
|
printf('Merging the PO file for %s to the compendium... ', $this->cli->bold($this->apps[$i]));
|
|
if (!empty($targetdir) && substr($targetdir, -1) != DS) {
|
|
$targetdir .= DS;
|
|
}
|
|
$sh = $this->msgcat . " --sort-output \"$compendium\" \"$pofile\" > \"$compendium.tmp\"";
|
|
if (!$this->debug) {
|
|
$sh .= $this->silence;
|
|
}
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln();
|
|
$this->writeln('Executing:');
|
|
$this->writeln($sh);
|
|
}
|
|
$out = '';
|
|
if ($this->test) {
|
|
$ret = 0;
|
|
} else {
|
|
exec($sh, $out, $ret);
|
|
}
|
|
unlink($compendium);
|
|
rename($compendium . '.tmp', $compendium);
|
|
if ($ret == 0) {
|
|
$this->writeln($this->cli->green('done'));
|
|
} else {
|
|
$this->writeln($this->cli->red('failed'));
|
|
}
|
|
}
|
|
$this->writeln();
|
|
}
|
|
}
|
|
if (empty($module)) {
|
|
$this->writeln('Results:');
|
|
} else {
|
|
$this->writeln('Results (including Horde):');
|
|
}
|
|
$this->writeln($stats->getTable());
|
|
if ($save_stats) {
|
|
file_put_contents('translation_stats.txt', serialize($stats_array));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleans up .po files, removing obsolete translations and optionally
|
|
* untranslated strings.
|
|
*
|
|
* @param array $options Command line arguments.
|
|
* @param boolean $keep_untranslated Whether to keep untranslated strings.
|
|
*/
|
|
public function cleanup($options, $keep_untranslated = false)
|
|
{
|
|
foreach ($options as $option) {
|
|
switch ($option[0]) {
|
|
case 'h':
|
|
$this->usage();
|
|
$this->footer();
|
|
case 'l':
|
|
case '--locale':
|
|
$lang = $option[1];
|
|
break;
|
|
case 'm':
|
|
case '--module':
|
|
$module = $option[1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
for ($i = 0; $i < count($this->dirs); $i++) {
|
|
if (!empty($module) && $module != $this->apps[$i]) {
|
|
continue;
|
|
}
|
|
$this->writeln(sprintf('Cleaning up PO files for module %s...', $this->cli->bold($this->apps[$i])));
|
|
$po = $this->dirs[$i] . DS . 'locale' . DS . '%s' . DS . 'LC_MESSAGES' .DS . $this->apps[$i] . '.po';
|
|
if (empty($lang)) {
|
|
$langs = $this->get_languages($this->dirs[$i]);
|
|
} else {
|
|
$this->writeln(sprintf($po, $lang));
|
|
if (!file_exists(sprintf($po, $lang))) {
|
|
$this->writeln('Skipped...');
|
|
$this->writeln();
|
|
continue;
|
|
}
|
|
$langs = array($lang);
|
|
}
|
|
foreach ($langs as $locale) {
|
|
$this->writeln(sprintf('Cleaning up locale %s... ', $this->cli->bold($locale)));
|
|
$pofile = sprintf($po, $locale);
|
|
$sh = $this->msgattrib . ($keep_untranslated ? '' : ' --translated') . " --no-obsolete --force-po \"$pofile\" > \"$pofile.tmp\"";
|
|
if (!$this->debug) {
|
|
$sh .= $this->silence;
|
|
}
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln();
|
|
$this->writeln('Executing:');
|
|
$this->writeln($sh);
|
|
}
|
|
$out = '';
|
|
if ($this->test) {
|
|
$ret = 0;
|
|
} else {
|
|
exec($sh, $out, $ret);
|
|
}
|
|
if ($ret == 0) {
|
|
unlink($pofile);
|
|
rename($pofile . '.tmp', $pofile);
|
|
$this->writeln($this->cli->green('done'));
|
|
} else {
|
|
unlink($pofile . '.tmp', $pofile);
|
|
$this->writeln($this->cli->red('failed'));
|
|
}
|
|
$this->writeln();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Commits translations to Git.
|
|
*
|
|
* @param array $options Command line arguments.
|
|
* @param boolean $help_only Whether to only commit help files.
|
|
*/
|
|
public function commit($options, $help_only = false)
|
|
{
|
|
$dirs = $this->dirs;
|
|
$apps = $this->apps;
|
|
$docs = $lang = false;
|
|
foreach ($options as $option) {
|
|
switch ($option[0]) {
|
|
case 'h':
|
|
$this->usage();
|
|
$this->footer();
|
|
case 'l':
|
|
case '--locale':
|
|
$lang = $option[1];
|
|
break;
|
|
case 'm':
|
|
case '--module':
|
|
$module = $option[1];
|
|
break;
|
|
case 'n':
|
|
case '--new':
|
|
$docs = true;
|
|
break;
|
|
case 'M':
|
|
case '--message':
|
|
$msg = $option[1];
|
|
break;
|
|
}
|
|
}
|
|
$files = array();
|
|
for ($i = 0; $i < count($dirs); $i++) {
|
|
if (!empty($module) && $module != $apps[$i]) {
|
|
continue;
|
|
}
|
|
if ($apps[$i] == 'horde') {
|
|
$dirs[] = $dirs[$i] . DS . 'admin';
|
|
$apps[] = 'horde/admin';
|
|
if (!empty($module)) {
|
|
$module = 'horde/admin';
|
|
}
|
|
}
|
|
if (empty($lang)) {
|
|
if ($help_only) {
|
|
$files = array_merge($files, $this->strip_horde($this->search_ext('xml', $dirs[$i] . DS . 'locale')));
|
|
} else {
|
|
$files = array_merge($files, $this->search_file('^[a-z]{2}(_[A-Z]{2})?', $dirs[$i] . DS . 'locale', true));
|
|
}
|
|
} else {
|
|
if (!is_dir($dirs[$i] . DS . 'locale' . DS . $lang)) {
|
|
continue;
|
|
}
|
|
if ($help_only &&
|
|
!file_exists($dirs[$i] . DS . 'locale' . DS . $lang . DS . 'help.xml')) {
|
|
continue;
|
|
}
|
|
$files[] = $dirs[$i] . DS . 'locale' . DS . $lang;
|
|
}
|
|
if ($docs && !$help_only && $apps[$i]) {
|
|
if (is_dir($dirs[$i] . DS . 'docs')) {
|
|
$files[] = $this->strip_horde($dirs[$i] . DS . 'docs');
|
|
}
|
|
if ($apps[$i] == 'horde') {
|
|
$horde_conf = $dirs[array_search('horde', $dirs)] . DS . 'config' . DS;
|
|
$files[] = $this->strip_horde($horde_conf . 'nls.php');
|
|
}
|
|
}
|
|
}
|
|
chdir(BASE);
|
|
if (count($files)) {
|
|
if ($docs) {
|
|
$this->writeln('Adding new files to repository:');
|
|
$add_files = array();
|
|
foreach ($files as $file) {
|
|
if (strstr($file, 'locale')) {
|
|
$add_files[] = $file;
|
|
$this->writeln($file);
|
|
}
|
|
}
|
|
foreach ($files as $file) {
|
|
if (strstr($file, 'locale')) {
|
|
if (glob($file . DS . '*.xml')) {
|
|
$add_files[] = $file . DS . '*.xml';
|
|
$this->writeln($file . DS . '*.xml');
|
|
}
|
|
if (!$help_only) {
|
|
$add_files[] = $file . DS . 'LC_MESSAGES';
|
|
$this->writeln($file . DS . 'LC_MESSAGES');
|
|
}
|
|
}
|
|
}
|
|
if (!$help_only) {
|
|
foreach ($files as $file) {
|
|
if (strstr($file, 'locale')) {
|
|
$this->writeln($add_files[] = $file . DS . 'LC_MESSAGES' . DS . '*.po');
|
|
$this->writeln($add_files[] = $file . DS . 'LC_MESSAGES' . DS . '*.mo');
|
|
}
|
|
}
|
|
}
|
|
$this->writeln();
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln('Executing:');
|
|
$this->writeln('git add ' . implode(' ', $add_files));
|
|
}
|
|
if (!$this->test) {
|
|
system('git add ' . implode(' ', $add_files));
|
|
}
|
|
$this->writeln();
|
|
}
|
|
$this->writeln('Committing:');
|
|
$this->writeln(implode(' ', $files));
|
|
if (!empty($lang)) {
|
|
$lang = ' ' . $lang;
|
|
}
|
|
if (empty($msg)) {
|
|
if ($docs) {
|
|
$msg = "Add$lang translation.";
|
|
} elseif ($help_only) {
|
|
$msg = "Update$lang help file.";
|
|
} else {
|
|
$msg = "Update$lang translation.";
|
|
}
|
|
}
|
|
$sh = 'git add ' . implode(' ', $files) . '; git commit -m "' . $msg . '"';
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln('Executing:');
|
|
$this->writeln($sh);
|
|
}
|
|
if (!$this->test) system($sh);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Merges old help translations with new English versions.
|
|
*
|
|
* @param array $options Command line arguments.
|
|
*/
|
|
public function update_help($options)
|
|
{
|
|
$dirs = $this->dirs;
|
|
$apps = $this->apps;
|
|
foreach ($options as $option) {
|
|
switch ($option[0]) {
|
|
case 'h':
|
|
$this->usage();
|
|
$this->footer();
|
|
case 'l':
|
|
case '--locale':
|
|
$lang = $option[1];
|
|
break;
|
|
case 'm':
|
|
case '--module':
|
|
$module = $option[1];
|
|
break;
|
|
}
|
|
}
|
|
$files = array();
|
|
for ($i = 0; $i < count($dirs); $i++) {
|
|
if (!empty($module) && $module != $apps[$i]) {
|
|
continue;
|
|
}
|
|
if (!is_dir("$dirs[$i]/locale")) {
|
|
continue;
|
|
}
|
|
if ($apps[$i] == 'horde') {
|
|
$dirs[] = $dirs[$i] . DS . 'admin';
|
|
$apps[] = 'horde/admin';
|
|
if (!empty($module)) {
|
|
$module = 'horde/admin';
|
|
}
|
|
}
|
|
if (empty($lang)) {
|
|
$files = $this->search_file('help.xml', $dirs[$i] . DS . 'locale');
|
|
} else {
|
|
$files = array($dirs[$i] . DS . 'locale' . DS . $lang . DS . 'help.xml');
|
|
}
|
|
$file_en = $dirs[$i] . DS . 'locale' . DS . 'en' . DS . 'help.xml';
|
|
if (!file_exists($file_en)) {
|
|
$this->cli->message(sprintf('There doesn\'t yet exist a help file for %s.', $this->cli->bold($apps[$i])), 'cli.warning');
|
|
$this->writeln();
|
|
continue;
|
|
}
|
|
foreach ($files as $file_loc) {
|
|
$locale = substr($file_loc, 0, strrpos($file_loc, DS));
|
|
$locale = substr($locale, strrpos($locale, DS) + 1);
|
|
if ($locale == 'en') {
|
|
continue;
|
|
}
|
|
if (!file_exists($file_loc)) {
|
|
$this->cli->message(sprintf('The %s help file for %s doesn\'t yet exist. Creating a new one.', $this->cli->bold($locale), $this->cli->bold($apps[$i])), 'cli.warning');
|
|
$dir_loc = substr($file_loc, 0, -9);
|
|
if (!is_dir($dir_loc)) {
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln(sprintf('Making directory %s', $dir_loc));
|
|
}
|
|
if (!$this->test && !System::mkdir("-p $dir_loc")) {
|
|
$this->cli->message(sprintf('Could not create locale directory for locale %s:', $locale), 'cli.warning');
|
|
$this->writeln($dir_loc);
|
|
$this->writeln();
|
|
continue;
|
|
}
|
|
}
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln(wordwrap(sprintf('Copying %s to %s', $file_en, $file_loc)));
|
|
}
|
|
if (!$this->test && !copy($file_en, $file_loc)) {
|
|
$this->cli->message(sprintf('Could not copy %s to %s', $file_en, $file_loc), 'cli.warning');
|
|
}
|
|
$this->writeln();
|
|
continue;
|
|
}
|
|
$this->writeln(sprintf('Updating %s help file for %s.', $this->cli->bold($locale), $this->cli->bold($apps[$i])));
|
|
|
|
if (!($doc_en = DOMDocument::load($file_en))) {
|
|
$this->cli->message(sprintf('There was an error opening the file %s. Try running the translation script with the flag -d to see any error messages from the xml parser.', $file_en), 'cli.warning');
|
|
$this->writeln();
|
|
continue 2;
|
|
}
|
|
$doc_en->encoding = 'UTF-8';
|
|
$doc_en->formatOutput = true;
|
|
|
|
if (!($doc_loc = DOMDocument::load($file_loc))) {
|
|
$this->cli->message(sprintf('There was an error opening the file %s. Try running the translation script with the flag -d to see any error messages from the xml parser.', $file_loc), 'cli.warning');
|
|
$this->writeln();
|
|
continue;
|
|
}
|
|
|
|
$count_uptodate = $count_new = $count_changed = $count_unknown = 0;
|
|
$date = date('Y-m-d');
|
|
$xpath = new DOMXPath($doc_loc);
|
|
foreach ($doc_en->getElementsByTagName('entry') as $entry) {
|
|
$view = $entry->parentNode;
|
|
while ($view->tagName != 'view' && $view != $doc_en) {
|
|
$view = $view->parentNode;
|
|
}
|
|
$query = '//entry[@id="' . $entry->getAttribute('id') . '"]';
|
|
if ($view->tagName == 'view') {
|
|
$query = '//view[@id="' . $view->getAttribute('id') . '"]'. $query;
|
|
}
|
|
$list = $xpath->query($query);
|
|
if ($list->length) {
|
|
$entry_loc = $doc_en->importNode($list->item(0), true);
|
|
if ($entry_loc->hasAttribute('md5') &&
|
|
md5($entry->textContent) != $entry_loc->getAttribute('md5')) {
|
|
$comment = $doc_en->createComment(" English entry ($date):\n" . str_replace('--', '--', $doc_en->saveXML($entry)));
|
|
$entry_loc->appendChild($comment);
|
|
$entry_loc->setAttribute('state', 'changed');
|
|
$count_changed++;
|
|
} else {
|
|
if (!$entry_loc->hasAttribute('state')) {
|
|
$comment = $doc_en->createComment(" English entry ($date):\n" . str_replace('--', '--', $doc_en->saveXML($entry)));
|
|
$entry_loc->appendChild($comment);
|
|
$entry_loc->setAttribute('state', 'unknown');
|
|
$count_unknown++;
|
|
} else {
|
|
$count_uptodate++;
|
|
}
|
|
}
|
|
} else {
|
|
$entry_loc = $doc_en->importNode($entry, true);
|
|
$entry_loc->setAttribute('state', 'new');
|
|
$count_new++;
|
|
}
|
|
$entry->parentNode->replaceChild($entry_loc, $entry);
|
|
}
|
|
$this->writeln(wordwrap(sprintf('Entries: %d total, %d up-to-date, %d new, %d changed, %d unknown',
|
|
$count_uptodate + $count_new + $count_changed + $count_unknown,
|
|
$count_uptodate, $count_new, $count_changed, $count_unknown)));
|
|
|
|
if ($this->debug || $this->test) {
|
|
$this->writeln(wordwrap(sprintf('Writing updated help file to %s.', $file_loc)));
|
|
}
|
|
if (!$this->test) {
|
|
$doc_en->save($file_loc);
|
|
}
|
|
$this->writeln();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Marks all entries in help files as translated.
|
|
*
|
|
* @param array $options Command line arguments.
|
|
*/
|
|
public function make_help($options)
|
|
{
|
|
$dirs = $this->dirs;
|
|
$apps = $this->apps;
|
|
foreach ($options as $option) {
|
|
switch ($option[0]) {
|
|
case 'h':
|
|
$this->usage();
|
|
$this->footer();
|
|
case 'l':
|
|
case '--locale':
|
|
$lang = $option[1];
|
|
break;
|
|
case 'm':
|
|
case '--module':
|
|
$module = $option[1];
|
|
break;
|
|
}
|
|
}
|
|
$files = array();
|
|
for ($i = 0; $i < count($dirs); $i++) {
|
|
if (!empty($module) && $module != $apps[$i]) {
|
|
continue;
|
|
}
|
|
if (!is_dir("$dirs[$i]/locale")) continue;
|
|
if ($apps[$i] == 'horde') {
|
|
$dirs[] = $dirs[$i] . DS . 'admin';
|
|
$apps[] = 'horde/admin';
|
|
if (!empty($module)) {
|
|
$module = 'horde/admin';
|
|
}
|
|
}
|
|
if (empty($lang)) {
|
|
$files = $this->search_file('help.xml', $dirs[$i] . DS . 'locale');
|
|
} else {
|
|
$files = array($dirs[$i] . DS . 'locale' . DS . $lang . DS . 'help.xml');
|
|
}
|
|
$file_en = $dirs[$i] . DS . 'locale' . DS . 'en' . DS . 'help.xml';
|
|
if (!file_exists($file_en)) {
|
|
continue;
|
|
}
|
|
|
|
if (!($doc_en = DOMDocument::load($file_en))) {
|
|
$this->cli->message(sprintf('There was an error opening the file %s. Try running the translation script with the flag -d to see any error messages from the xml parser.', $file_en), 'cli.warning');
|
|
$this->writeln();
|
|
continue;
|
|
}
|
|
$xpath = new DOMXPath($doc_en);
|
|
|
|
foreach ($files as $file_loc) {
|
|
if (!file_exists($file_loc)) {
|
|
$this->writeln('Skipped...');
|
|
$this->writeln();
|
|
continue;
|
|
}
|
|
$locale = substr($file_loc, 0, strrpos($file_loc, DS));
|
|
$locale = substr($locale, strrpos($locale, DS) + 1);
|
|
if ($locale == 'en') continue;
|
|
$this->writeln(sprintf('Updating %s help file for %s.', $this->cli->bold($locale), $this->cli->bold($apps[$i])));
|
|
|
|
if (!($doc_loc = DOMDocument::load($file_loc))) {
|
|
$this->cli->message(sprintf('There was an error opening the file %s. Try running the translation script with the flag -d to see any error messages from the xml parser.', $file_loc), 'cli.warning');
|
|
$this->writeln();
|
|
continue;
|
|
}
|
|
$doc_loc->encoding = 'UTF-8';
|
|
$doc_loc->formatOutput = true;
|
|
|
|
$count_all = $count = 0;
|
|
foreach ($doc_loc->getElementsByTagName('entry') as $entry) {
|
|
foreach ($entry->childNodes as $child) {
|
|
if ($child->nodeType == XML_COMMENT_NODE &&
|
|
strstr($child->nodeValue, 'English entry')) {
|
|
$entry->removeChild($child);
|
|
}
|
|
}
|
|
$count_all++;
|
|
$view = $entry->parentNode;
|
|
while ($view->tagName != 'view' && $view != $doc_en) {
|
|
$view = $view->parentNode;
|
|
}
|
|
$query = '//entry[@id="' . $entry->getAttribute('id') . '"]';
|
|
if ($view->tagName == 'view') {
|
|
$query = '//view[@id="' . $view->getAttribute('id') . '"]'. $query;
|
|
}
|
|
$list = $xpath->query($query);
|
|
if ($list->length) {
|
|
$entry->setAttribute('md5', md5($list->item(0)->textContent));
|
|
$entry->setAttribute('state', 'uptodate');
|
|
$count++;
|
|
} else {
|
|
$this->cli->message(sprintf('No entry with the id "%s" exists in the original help file.', $entry->getAttribute('id')), 'cli.warning');
|
|
}
|
|
}
|
|
|
|
if (!$this->test) {
|
|
$doc_loc->save($file_loc);
|
|
}
|
|
$this->writeln(sprintf('%d of %d entries marked as up-to-date', $count, $count_all));
|
|
$this->writeln();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
define('DS', DIRECTORY_SEPARATOR);
|
|
putenv('LANG=en');
|
|
|
|
$baseFile = __DIR__ . '/../lib/core.php';
|
|
if (file_exists($baseFile)) {
|
|
require_once $baseFile;
|
|
} else {
|
|
require_once 'PEAR/Config.php';
|
|
require_once PEAR_Config::singleton()
|
|
->get('horde_dir', null, 'pear.horde.org') . '/lib/core.php';
|
|
}
|
|
|
|
$c = Horde_Cli::init();
|
|
|
|
$c->writeln($c->bold('---------------------------'));
|
|
$c->writeln($c->bold('Horde translation generator'));
|
|
$c->writeln($c->bold('---------------------------'));
|
|
|
|
/* Instantiate main object. */
|
|
$script = new Horde_Translation_Script();
|
|
$script->cli = $c;
|
|
|
|
/* Sanity checks */
|
|
if (!extension_loaded('gettext')) {
|
|
$c->message('Gettext extension not found!', 'cli.error');
|
|
$script->footer();
|
|
}
|
|
|
|
$c->writeln('Loading libraries...');
|
|
$libs_found = true;
|
|
|
|
foreach (array('Console_Getopt', 'Console_Table', 'File_Find') as $class) {
|
|
if (class_exists($class)) {
|
|
$c->message(sprintf('%s found.', $class), 'cli.success');
|
|
} else {
|
|
$c->message(sprintf('%s not found.', $class), 'cli.error');
|
|
$libs_found = false;
|
|
}
|
|
}
|
|
|
|
if (!$libs_found) {
|
|
$c->writeln();
|
|
$c->writeln('Make sure that you have PEAR installed and in your include path.');
|
|
$c->writeln('include_path: ' . ini_get('include_path'));
|
|
$script->footer();
|
|
}
|
|
$c->writeln();
|
|
|
|
/* Ensure E_STRICT is off as we are calling PEAR */
|
|
$old_error_reporting = error_reporting(E_ALL & ~E_STRICT);
|
|
|
|
/* Commandline parameters */
|
|
$args = Console_Getopt::readPHPArgv();
|
|
$options = Console_Getopt::getopt($args, 'b:dht', array('base=', 'debug', 'help', 'test'));
|
|
if (PEAR::isError($options) && $args[0] == $_SERVER['PHP_SELF']) {
|
|
array_shift($args);
|
|
$options = Console_Getopt::getopt($args, 'b:dht', array('base=', 'debug', 'help', 'test'));
|
|
}
|
|
|
|
if (PEAR::isError($options)) {
|
|
$c->message('Argument error: ' . str_replace('Console_Getopt:', '', $options->getMessage()), 'cli.error');
|
|
$c->writeln();
|
|
$script->usage();
|
|
$script->footer();
|
|
}
|
|
|
|
$script->options = $options;
|
|
|
|
/* Back to old error reporting */
|
|
error_reporting($old_error_reporting);
|
|
|
|
if (empty($options[0][0]) && empty($options[1][0])) {
|
|
$c->message('No command specified.', 'cli.error');
|
|
$c->writeln();
|
|
$script->usage();
|
|
$script->footer();
|
|
}
|
|
foreach ($options[0] as $option) {
|
|
switch ($option[0]) {
|
|
case 'b':
|
|
case '--base':
|
|
define('BASE', realpath($option[1]));
|
|
break;
|
|
case 'd':
|
|
case '--debug':
|
|
$script->debug = true;
|
|
break;
|
|
case 't':
|
|
case '--test':
|
|
$script->test = true;
|
|
break;
|
|
case 'h':
|
|
case '--help':
|
|
$script->usage();
|
|
$script->footer();
|
|
}
|
|
}
|
|
if (!$script->debug) {
|
|
ini_set('error_reporting', false);
|
|
}
|
|
if (!defined('BASE')) {
|
|
if (is_dir(HORDE_BASE . '/.git')) {
|
|
define('BASE', HORDE_BASE . '/..');
|
|
} else {
|
|
define('BASE', HORDE_BASE);
|
|
}
|
|
}
|
|
if ($options[1][0] == 'help') {
|
|
$script->usage();
|
|
$script->footer();
|
|
}
|
|
$script->silence = $script->debug || OS_WINDOWS ? '' : ' 2> /dev/null';
|
|
$options_list = array(
|
|
'cleanup' => array('hl:m:', array('module=', 'locale=')),
|
|
'commit' => array('hl:m:nM:s', array('module=', 'locale=', 'new', 'message=')),
|
|
'commit-help'=> array('hl:m:nM:', array('module=', 'locale=', 'new', 'message=')),
|
|
'compendium' => array('hl:d:a:', array('locale=', 'directory=', 'add=')),
|
|
'extract' => array('hm:', array('module=')),
|
|
'init' => array('hl:m:nc:', array('module=', 'locale=', 'no-compendium', 'compendium=')),
|
|
'merge' => array('hl:m:c:n', array('module=', 'locale=', 'compendium=', 'no-compendium')),
|
|
'make' => array('hl:m:c:ns', array('module=', 'locale=', 'compendium=', 'no-compendium', 'statistics')),
|
|
'make-help' => array('hl:m:', array('module=', 'locale=')),
|
|
'update' => array('hl:m:c:n', array('module=', 'locale=', 'compendium=', 'no-compendium')),
|
|
'update-help'=> array('hl:m:', array('module=', 'locale=')),
|
|
'status' => array('hl:m:o:', array('module=', 'locale=', 'output='))
|
|
);
|
|
$options_arr = $options[1];
|
|
$cmd = array_shift($options_arr);
|
|
if (array_key_exists($cmd, $options_list)) {
|
|
$cmd_options = Console_Getopt::getopt($options_arr, $options_list[$cmd][0], $options_list[$cmd][1]);
|
|
if (PEAR::isError($cmd_options)) {
|
|
$c->message(str_replace('Console_Getopt:', '', $cmd_options->getMessage()), 'cli.error');
|
|
$c->writeln();
|
|
$script->usage();
|
|
$script->footer();
|
|
}
|
|
}
|
|
|
|
/* Searching modules */
|
|
$script->ff = new File_Find();
|
|
$script->check_binaries();
|
|
|
|
$c->writeln(sprintf('Searching Horde modules in %s', BASE));
|
|
$script->search_modules();
|
|
|
|
if ($script->debug) {
|
|
$c->writeln('Found directories:');
|
|
$c->writeln(implode("\n", $script->dirs));
|
|
}
|
|
|
|
$c->writeln(wordwrap(sprintf('Found modules: %s', implode(', ', $script->apps))));
|
|
$c->writeln();
|
|
|
|
switch ($cmd) {
|
|
case 'cleanup':
|
|
case 'commit':
|
|
case 'compendium':
|
|
case 'merge':
|
|
$script->$cmd($cmd_options[0]);
|
|
break;
|
|
case 'commit-help':
|
|
$script->commit($cmd_options[0], true);
|
|
break;
|
|
case 'extract':
|
|
$script->xtract($cmd_options[0]);
|
|
break;
|
|
case 'init':
|
|
$script->init($cmd_options[0]);
|
|
$c->writeln();
|
|
$script->merge($cmd_options[0]);
|
|
break;
|
|
case 'make':
|
|
$script->cleanup($cmd_options[0], true);
|
|
$c->writeln();
|
|
$script->make($cmd_options[0]);
|
|
break;
|
|
case 'make-help':
|
|
$script->make_help($cmd_options[0]);
|
|
break;
|
|
case 'update':
|
|
$script->xtract($cmd_options[0]);
|
|
$c->writeln();
|
|
$script->merge($cmd_options[0]);
|
|
break;
|
|
case 'update-help':
|
|
$script->update_help($cmd_options[0]);
|
|
break;
|
|
case 'status':
|
|
$script->merge($cmd_options[0]);
|
|
break;
|
|
default:
|
|
$c->message(sprintf('Unknown command: %s', $cmd), 'cli.error');
|
|
$c->writeln();
|
|
$script->usage();
|
|
$script->footer();
|
|
}
|
|
|
|
$script->footer();
|