⚝
One Hat Cyber Team
⚝
Your IP:
216.73.216.50
Server IP:
41.128.143.86
Server:
Linux host.raqmix.cloud 6.8.0-1025-azure #30~22.04.1-Ubuntu SMP Wed Mar 12 15:28:20 UTC 2025 x86_64
Server Software:
Apache
PHP Version:
8.3.23
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
usr
/
share
/
psa-pear
/
pear
/
View File Name :
horde-translation
#!/usr/bin/env php _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 = ';(?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
* 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, '
', $offset)) !== false) { $left += 9; $buffer = ''; $linespan = 0; while (($end = strpos($line, '
', $left)) === false) { $buffer .= substr($line, $left); $left = 0; $line = fgets($fp, 4096); $linespan++; if ($line === false) { $this->climessage(sprintf("
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
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>/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();