Remove remainders out of po/ directories.
authorJan Schneider <jan@horde.org>
Fri, 20 Aug 2010 15:36:45 +0000 (17:36 +0200)
committerJan Schneider <jan@horde.org>
Fri, 20 Aug 2010 15:37:47 +0000 (17:37 +0200)
60 files changed:
agora/po/.htaccess [deleted file]
agora/po/README [deleted file]
ansel/locale/.htaccess [new file with mode: 0644]
ansel/po/README [deleted file]
babel/locale/.htaccess [new file with mode: 0644]
beatnik/locale/.htaccess [new file with mode: 0644]
chora/po/.htaccess [deleted file]
chora/po/README [deleted file]
crumb/po/README [deleted file]
fima/locale/.htaccess [new file with mode: 0644]
fima/po/README [deleted file]
folks/locale/.htaccess [new file with mode: 0644]
gollem/locale/.htaccess [new file with mode: 0644]
gollem/po/README [deleted file]
hermes/locale/.htaccess [new file with mode: 0644]
hermes/po/README [deleted file]
horde/bin/translation [new file with mode: 0755]
horde/docs/TRANSLATIONS
horde/po/.htaccess [deleted file]
horde/po/README [deleted file]
horde/po/translation.php [deleted file]
imp/po/.htaccess [deleted file]
imp/po/README [deleted file]
ingo/locale/.htaccess [new file with mode: 0644]
ingo/po/README [deleted file]
jeta/locale/.htaccess [new file with mode: 0644]
jeta/po/README [deleted file]
jonah/po/.htaccess [deleted file]
jonah/po/README [deleted file]
kastalia/locale/.htaccess [new file with mode: 0644]
kastalia/po/README [deleted file]
kronolith/locale/.htaccess [new file with mode: 0644]
kronolith/po/README [deleted file]
luxor/locale/.htaccess [new file with mode: 0644]
luxor/po/.htaccess [deleted file]
luxor/po/README [deleted file]
mnemo/locale/.htaccess [new file with mode: 0644]
mnemo/po/README [deleted file]
nag/locale/.htaccess [new file with mode: 0644]
nag/po/README [deleted file]
news/locale/.htaccess [new file with mode: 0644]
operator/po/README [deleted file]
passwd/locale/.htaccess [new file with mode: 0644]
passwd/po/.htaccess [deleted file]
passwd/po/README [deleted file]
pastie/po/README [deleted file]
skeleton/locale/.htaccess [new file with mode: 0644]
skeleton/po/README [deleted file]
skoli/locale/.htaccess [new file with mode: 0644]
skoli/po/README [deleted file]
timeobjects/locale/.htaccess [new file with mode: 0644]
trean/locale/.htaccess [new file with mode: 0644]
turba/po/.htaccess [deleted file]
turba/po/README [deleted file]
vilma/locale/.htaccess [new file with mode: 0644]
vilma/po/README [deleted file]
whups/locale/.htaccess [new file with mode: 0644]
whups/po/README [deleted file]
wicked/locale/.htaccess [new file with mode: 0644]
wicked/po/README [deleted file]

diff --git a/agora/po/.htaccess b/agora/po/.htaccess
deleted file mode 100755 (executable)
index 3a42882..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Deny from all
diff --git a/agora/po/README b/agora/po/README
deleted file mode 100755 (executable)
index a985e94..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
diff --git a/ansel/locale/.htaccess b/ansel/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/ansel/po/README b/ansel/po/README
deleted file mode 100755 (executable)
index a985e94..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
diff --git a/babel/locale/.htaccess b/babel/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/beatnik/locale/.htaccess b/beatnik/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/chora/po/.htaccess b/chora/po/.htaccess
deleted file mode 100644 (file)
index 3a42882..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Deny from all
diff --git a/chora/po/README b/chora/po/README
deleted file mode 100644 (file)
index a4356a9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
\ No newline at end of file
diff --git a/crumb/po/README b/crumb/po/README
deleted file mode 100644 (file)
index a985e94..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
diff --git a/fima/locale/.htaccess b/fima/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/fima/po/README b/fima/po/README
deleted file mode 100644 (file)
index a985e94..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
diff --git a/folks/locale/.htaccess b/folks/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/gollem/locale/.htaccess b/gollem/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/gollem/po/README b/gollem/po/README
deleted file mode 100644 (file)
index a4356a9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
\ No newline at end of file
diff --git a/hermes/locale/.htaccess b/hermes/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/hermes/po/README b/hermes/po/README
deleted file mode 100644 (file)
index a985e94..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
diff --git a/horde/bin/translation b/horde/bin/translation
new file mode 100755 (executable)
index 0000000..40260b6
--- /dev/null
@@ -0,0 +1,1511 @@
+#!/usr/bin/env php
+<?php
+/**
+ * Translation helper application for the Horde framework.
+ *
+ * For usage information call:
+ * ./translation.php help
+ */
+
+function footer()
+{
+    global $c, $curdir;
+
+    $c->writeln();
+    $c->writeln('Please report any bugs to i18n@lists.horde.org.');
+
+    chdir($curdir);
+    exit;
+}
+
+function usage()
+{
+    global $options, $c;
+
+    if (count($options[1]) &&
+        ($options[1][0] == 'help' && !empty($options[1][1]) ||
+        !empty($options[1][0]) && in_array($options[1][0], array('commit', 'compendium', 'extract', 'init', 'make', 'merge')))) {
+        if ($options[1][0] == 'help') {
+            $cmd = $options[1][1];
+        } else {
+            $cmd = $options[1][0];
+        }
+        $c->writeln('Usage:' . ' translation.php [options] ' . $cmd . ' [command-options]');
+        if (!empty($cmd)) {
+            $c->writeln();
+            $c->writeln('Command options:');
+        }
+        switch ($cmd) {
+        case 'cleanup':
+            $c->writeln('  -l, --locale=ll_CC     Use only this locale.');
+            $c->writeln('  -m, --module=MODULE    Cleanup PO files only for this (Horde) module.');
+            break;
+        case 'commit':
+        case 'commit-help':
+            $c->writeln('  -l, --locale=ll_CC     Use this locale.');
+            $c->writeln('  -m, --module=MODULE    Commit translations only for this (Horde) module.');
+            $c->writeln('  -M, --message=MESSAGE  Use this commit message instead of the default ones.');
+            $c->writeln('  -n, --new              This is a new translation, commit also CREDITS,');
+            $c->writeln('                         CHANGES and nls.php.');
+            $c->writeln('  -s, --skip             Skip all modules that are not maintained in CVS.');
+            break;
+        case 'compendium':
+            $c->writeln('  -a, --add=FILE        Add this PO file to the compendium. Useful to');
+            $c->writeln('                        include a compendium from a different branch to');
+            $c->writeln('                        the generated compendium.');
+            $c->writeln('  -d, --directory=DIR   Create compendium in this directory.');
+            $c->writeln('  -l, --locale=ll_CC    Use this locale.');
+            break;
+        case 'extract':
+            $c->writeln('  -m, --module=MODULE  Generate POT file only for this (Horde) module.');
+            break;
+        case 'init':
+            $c->writeln('  -l, --locale=ll_CC     Use this locale.');
+            $c->writeln('  -m, --module=MODULE    Create a PO file only for this (Horde) module.');
+            $c->writeln('  -c, --compendium=FILE  Use this compendium file instead of the default');
+            $c->writeln('                         one (compendium.po in the horde/po directory).');
+            $c->writeln('  -n, --no-compendium    Don\'t use a compendium.');
+            break;
+        case 'make':
+            $c->writeln('  -l, --locale=ll_CC     Use only this locale.');
+            $c->writeln('  -m, --module=MODULE    Build MO files only for this (Horde) module.');
+            $c->writeln('  -c, --compendium=FILE  Merge new translations to this compendium file');
+            $c->writeln('                         instead of the default one (compendium.po in the');
+            $c->writeln('                         horde/po directory.');
+            $c->writeln('  -n, --no-compendium    Don\'t merge new translations to the compendium.');
+            $c->writeln('  -s, --statistics       Save translation statistics in a local file.');
+            break;
+        case 'make-help':
+        case 'update-help':
+            $c->writeln('  -l, --locale=ll_CC     Use only this locale.');
+            $c->writeln('  -m, --module=MODULE    Update help files only for this (Horde) module.');
+            break;
+        case 'merge':
+            $c->writeln('  -l, --locale=ll_CC     Use this locale.');
+            $c->writeln('  -m, --module=MODULE    Merge PO files only for this (Horde) module.');
+            $c->writeln('  -c, --compendium=FILE  Use this compendium file instead of the default');
+            $c->writeln('                         one (compendium.po in the horde/po directory).');
+            $c->writeln('  -n, --no-compendium    Don\'t use a compendium.');
+            break;
+        case 'update':
+            $c->writeln('  -l, --locale=ll_CC     Use this locale.');
+            $c->writeln('  -m, --module=MODULE    Update only this (Horde) module.');
+            $c->writeln('  -c, --compendium=FILE  Use this compendium file instead of the default');
+            $c->writeln('                         one (compendium.po in the horde/po directory).');
+            $c->writeln('  -n, --no-compendium    Don\'t use a compendium.');
+            break;
+        }
+    } else {
+        $c->writeln('Usage:' . ' translation.php [options] command [command-options]');
+        $c->writeln(str_repeat(' ', Horde_String::length('Usage:')) . ' translation.php [help|-h|--help] [command]');
+        $c->writeln();
+        $c->writeln('Helper application to create and maintain translations for the Horde');
+        $c->writeln('framework and its applications.');
+        $c->writeln('For an introduction read the file README in this directory.');
+        $c->writeln();
+        $c->writeln('Commands:');
+        $c->writeln('  help        Show this help message.');
+        $c->writeln('  compendium  Rebuild the compendium file. Warning: This overwrites the');
+        $c->writeln('              current compendium.');
+        $c->writeln('  extract     Generate PO template (.pot) files.');
+        $c->writeln('  init        Create one or more PO files for a new locale. Warning: This');
+        $c->writeln('              overwrites the existing PO files of this locale.');
+        $c->writeln('  merge       Merge the current PO file with the current PO template file.');
+        $c->writeln('  update      Run extract and merge sequent.');
+        $c->writeln('  update-help Extract all new and changed entries from the English XML help');
+        $c->writeln('              file and merge them with the existing ones.');
+        $c->writeln('  cleanup     Cleans the PO files up from untranslated and obsolete entries.');
+        $c->writeln('  make        Build binary MO files from the specified PO files.');
+        $c->writeln('  make-help   Mark all entries in the XML help file being up-to-date and');
+        $c->writeln('              prepare the file for the next execution of update-help. You');
+        $c->writeln('              should only run make-help AFTER update-help and revising the');
+        $c->writeln('              help file.');
+        $c->writeln('  commit      Commit translations to the CVS server.');
+        $c->writeln('  commit-help Commit help files to the CVS server.');
+    }
+
+    $c->writeln();
+    $c->writeln('Options:');
+    $c->writeln('  -b, --base=/PATH  Full path to the (Horde) base directory that should be');
+    $c->writeln('                    used.');
+    $c->writeln('  -d, --debug       Show error messages from the executed binaries.');
+    $c->writeln('  -h, --help        Show this help message.');
+    $c->writeln('  -t, --test        Show the executed commands but don\'t run anything.');
+}
+
+function check_binaries()
+{
+    global $gettext_version, $c;
+
+    $c->writeln('Searching gettext binaries...');
+    foreach (array('gettext', 'msgattrib', 'msgcat', 'msgcomm', 'msgfmt', 'msginit', 'msgmerge', 'xgettext') as $binary) {
+        $GLOBALS[$binary] = System::which($binary);
+        if ($GLOBALS[$binary]) {
+            $c->message($binary . ' found: ' . $GLOBALS[$binary], 'cli.success');
+        } else {
+            $c->message($binary . ' not found', 'cli.error');
+            footer();
+        }
+    }
+    $c->writeln();
+
+    $out = '';
+    exec($GLOBALS['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) {
+        $GLOBALS['php_support'] = false;
+        $c->writeln();
+        $c->message($version_string, 'cli.warning');
+        $c->message('Your gettext version is too old and does not support PHP natively.', 'cli.warning');
+        $c->writeln('Not all strings will be extracted.');
+    } else {
+        $GLOBALS['php_support'] = true;
+        $c->message($version_string, 'cli.success');
+    }
+    $c->writeln();
+}
+
+function search_file($file, $dir = '.', $local = false)
+{
+    static $ff;
+    if (!isset($ff)) {
+        $ff = new File_Find();
+    }
+
+    if (substr($file, 0, 1) != DS) {
+        $file = "/$file/";
+    }
+
+    if ($local) {
+        $files = $ff->glob($file, $dir, 'perl');
+        $files = array_map(create_function('$file', 'return "' . $dir . DS . '" . $file;'), $files);
+        return $files;
+    } else {
+        return $ff->search($file, $dir, 'perl', false);
+    }
+}
+
+function search_ext($ext, $dir = '.', $local = false)
+{
+    return search_file("^[^.].*\\.$ext\$", $dir, $local);
+}
+
+function get_po_files($dir)
+{
+    $langs = 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;
+}
+
+function get_languages($dir)
+{
+    global $curdir;
+
+    chdir($dir);
+    $langs = get_po_files('locale');
+    $langs = array_map('basename', array_map('dirname', array_map('dirname', $langs)));
+    chdir($curdir);
+    return $langs;
+}
+
+function search_applications()
+{
+    $dirs = array();
+    if (is_dir(HORDE_BASE . DS . 'locale')) {
+        $dirs[] = HORDE_BASE;
+    }
+    $dh = opendir(BASE);
+    if ($dh) {
+        while ($entry = readdir($dh)) {
+            $dir = BASE . DS . $entry;
+            if (is_dir($dir) &&
+                substr($entry, 0, 1) != '.' &&
+                fileinode(HORDE_BASE) != fileinode($dir)) {
+                $sub = opendir($dir);
+                if ($sub) {
+                    while ($subentry = readdir($sub)) {
+                        if ($subentry == 'locale' && is_dir($dir . DS . $subentry)) {
+                            $dirs[] = $dir;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    return $dirs;
+}
+
+function strip_horde($file)
+{
+    if (is_array($file)) {
+        return array_map('strip_horde', $file);
+    } else {
+        return str_replace(BASE . DS, '', $file);
+    }
+}
+
+function xtract()
+{
+    global $cmd_options, $apps, $dirs, $debug, $test, $c, $gettext_version, $silence, $curdir;
+
+    foreach ($cmd_options[0] as $option) {
+        switch ($option[0]) {
+        case 'h':
+            usage();
+            footer();
+        case 'm':
+        case '--module':
+            $module = $option[1];
+            break;
+        }
+    }
+
+    if ($GLOBALS['php_support']) {
+        $language = 'PHP';
+    } else {
+        $language = 'C++';
+    }
+    for ($i = 0; $i < count($dirs); $i++) {
+        if (!empty($module) && $module != $apps[$i]) {
+            continue;
+        }
+        printf('Extracting from %s... ', $apps[$i]);
+        chdir($dirs[$i]);
+        $files = array();
+        if ($apps[$i] == 'horde') {
+            $files = search_ext('(php|inc)', '../framework');
+            $files[] = 'config/nls.php';
+        }
+        $files = array_merge($files, search_ext('(php|inc)'));
+        $files = array_filter($files, create_function('$file', 'return substr($file, 0, 9) != "." . DS . "config" . DS;'));
+        $files = array_merge($files, search_ext('dist', 'config'));
+        $file = 'locale' . DS . $apps[$i] . '.pot';
+        file_put_contents($file . '.list', implode("\n", $files));
+        if (file_exists($file) && !is_writable($file)) {
+            $c->message(sprintf('%s is not writable.', $file), 'cli.error');
+            footer();
+        }
+        $tmp_file = $file . '.tmp.pot';
+        $sh = $GLOBALS['xgettext'] . ' --language=' . $language .
+            ' --from-code=iso-8859-1 --keyword=_ --sort-output --copyright-holder="Horde Project" --msgid-bugs-address="dev@lists.horde.org" --files-from=' . $file . '.list --output=' . $tmp_file;
+        if ($debug) {
+            $sh .= $silence;
+        }
+        if ($debug || $test) {
+            $c->writeln('Executing:');
+            $c->writeln($sh);
+            $c->writeln('In: ' . getcwd());
+        }
+        if (!$test) {
+            exec($sh);
+        }
+        unlink($file . '.list');
+        $diff = array();
+        if (file_exists($tmp_file)) {
+            $files = search_ext('html', 'templates');
+            if (!$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) {
+                                $c->message(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 (!$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);
+            }
+            if (!$test) fclose($tmp);
+            $sh = $GLOBALS['msgcomm'] . " --more-than=0 --sort-output \"$tmp_file\" \"$file.templates\" --output-file \"$tmp_file\"" . $silence;
+            if ($debug || $test) {
+                $c->writeln('Executing:');
+                $c->writeln($sh);
+            }
+            if (!$test) {
+                exec($sh);
+                unlink($file . '.templates');
+            }
+
+            /* Parse conf.xml files for <configphp> tags. */
+            if (file_exists('config/conf.xml')) {
+                if (!$test) $tmp = fopen($file . '.config', 'w');
+                $conf_content = file_get_contents('config/conf.xml');
+                if (preg_match_all('/<configphp .*?>([^<]*_\(".+?"\)[^<]*)<\/configphp>/s',
+                                   $conf_content, $matches)) {
+                    foreach ($matches[1] as $configphp) {
+                        if (preg_match_all('/_\("(.+?)"\)/', $configphp, $strings)) {
+                            if (!$test) {
+                                foreach ($strings[1] as $string) {
+                                    fwrite($tmp, "#: config/conf.xml\n");
+                                    fwrite($tmp, 'msgid "' . $string . "\"\n");
+                                    fwrite($tmp, 'msgstr ""' . "\n\n");
+                                }
+                            }
+                        }
+                    }
+                }
+                if (!$test) fclose($tmp);
+                $sh = $GLOBALS['msgcomm'] . " --more-than=0 --sort-output \"$tmp_file\" \"$file.config\" --output-file \"$tmp_file\"" . $silence;
+                if ($debug || $test) {
+                    $c->writeln('Executing:');
+                    $c->writeln($sh);
+                }
+                if (!$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:/', $diff, PREG_GREP_INVERT);
+            }
+        }
+        if (!file_exists($file) || count($diff)) {
+            if (file_exists($file)) {
+                unlink($file);
+            }
+            rename($tmp_file, $file);
+            $c->writeln($c->green('updated'));
+        } else {
+            if (file_exists($tmp_file)) {
+                unlink($tmp_file);
+            }
+            $c->writeln($c->bold('not changed'));
+        }
+        chdir($curdir);
+    }
+}
+
+function merge()
+{
+    global $cmd_options, $apps, $dirs, $debug, $test, $c;
+
+    $compendium = ' --compendium="' . HORDE_BASE . DS . 'locale' . DS . 'compendium.po"';
+    foreach ($cmd_options[0] as $option) {
+        switch ($option[0]) {
+        case 'h':
+            usage();
+            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;
+        }
+    }
+
+    cleanup();
+
+    for ($i = 0; $i < count($dirs); $i++) {
+        if (!empty($module) && $module != $apps[$i]) {
+            continue;
+        }
+        $c->writeln(sprintf('Merging translation for module %s...', $c->bold($apps[$i])));
+        $dir = $dirs[$i] . DS . 'locale' . DS;
+        $po = $dir . '%s' . DS . 'LC_MESSAGES' . DS . $apps[$i] . '.po';
+        if (empty($lang)) {
+            $langs = get_languages($dirs[$i]);
+        } else {
+            if (!file_exists(sprintf($po, $lang))) {
+                $c->writeln('Skipped...');
+                $c->writeln();
+                continue;
+            }
+            $langs = array($lang);
+        }
+        foreach ($langs as $locale) {
+            $c->writeln(sprintf('Merging locale %s... ', $c->bold($locale)));
+            $sh = $GLOBALS['msgmerge']
+                . sprintf(' --update -v%s "%s" "%s.pot"',
+                          $compendium, sprintf($po, $locale), $dir . $apps[$i]);
+            if ($debug || $test) {
+                $c->writeln('Executing:');
+                $c->writeln($sh);
+            }
+            if (!$test) exec($sh);
+            $c->writeln($c->green('done'));
+        }
+    }
+}
+
+function status()
+{
+    return;
+    global $cmd_options, $apps, $dirs, $debug, $test, $c;
+
+    $output = 'status.html';
+    foreach ($cmd_options[0] as $option) {
+        switch ($option[0]) {
+        case 'h':
+            usage();
+            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($dirs); $i++) {
+        if (!empty($module) && $module != $apps[$i]) {
+            continue;
+        }
+        $c->writeln(sprintf('Generating status for module %s...', $c->bold($apps[$i])));
+        if (empty($lang)) {
+            $langs = get_languages($dirs[$i]);
+        } else {
+            if (!file_exists($dirs[$i] . '/locale/' . $lang . '/LC_MESSAGES/' . $apps[$i] . '.po')) {
+                $c->writeln('Skipped...');
+                $c->writeln();
+                continue;
+            }
+            $langs = array($lang);
+        }
+        foreach ($langs as $locale) {
+            $c->writeln(sprintf('Status for locale %s... ', $c->bold($locale)));
+        }
+    }
+}
+
+function compendium()
+{
+    global $cmd_options, $dirs, $debug, $test, $c, $silence;
+
+    $dir = HORDE_BASE . DS . 'locale' . DS;
+    $add = '';
+    foreach ($cmd_options[0] as $option) {
+        switch ($option[0]) {
+        case 'h':
+            usage();
+            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)) {
+        $c->message('No locale specified.', 'cli.error');
+        $c->writeln();
+        usage();
+        footer();
+    }
+    printf('Merging all %s.po files to the compendium... ', $lang);
+    $pofiles = array();
+    for ($i = 0; $i < count($dirs); $i++) {
+        $pofile = $dirs[$i] . DS . 'locale' . DS . $lang . DS . 'LC_MESSAGES' . DS . $apps[$i] . '.po';
+        if (file_exists($pofile)) {
+            $pofiles[] = $pofile;
+        }
+    }
+    if (!empty($dir) && substr($dir, -1) != DS) {
+        $dir .= DS;
+    }
+    $sh = $GLOBALS['msgcat'] . ' --sort-output ' . implode(' ', $pofiles) . $add . ' > ' . $dir . 'compendium.po ' . ($debug ? '' : $silence);
+    if ($debug || $test) {
+        $c->writeln();
+        $c->writeln('Executing:');
+        $c->writeln($sh);
+    }
+    if ($test) {
+        $ret = 0;
+    } else {
+        exec($sh, $out, $ret);
+    }
+    if ($ret == 0) {
+        $c->writeln($c->green('done'));
+    } else {
+        $c->writeln($c->red('failed'));
+    }
+}
+
+function init()
+{
+    global $cmd_options, $apps, $dirs, $debug, $test, $c, $silence;
+
+    foreach ($cmd_options[0] as $option) {
+        switch ($option[0]) {
+        case 'h':
+            usage();
+            footer();
+        case 'l':
+        case '--locale':
+            $lang = $option[1];
+            break;
+        case 'm':
+        case '--module':
+            $module = $option[1];
+            break;
+        }
+    }
+    if (empty($lang)) {
+        $lang = getenv('LANG');
+    }
+
+    $registry = new Horde_Registry(Horde_Registry::SESSION_NONE);
+
+    for ($i = 0; $i < count($dirs); $i++) {
+        if (!empty($module) && $module != $apps[$i]) {
+            continue;
+        }
+        $package = ucfirst($apps[$i]);
+        $version = $registry->getVersion($apps[$i]);
+        printf('Initializing module %s... ', $apps[$i]);
+        $dir = $dirs[$i] . DS . 'locale' . DS;
+        $pot = $dir . $apps[$i] . '.pot';
+        if (!file_exists($pot)) {
+            $c->writeln();
+            $c->message(sprintf('%s not found. Run \'translation extract\' first.', $pot), 'cli.warning');
+            continue;
+        }
+        $sh = $GLOBALS['msginit'] . ' --no-translator -i ' . $pot;
+        if (!empty($lang)) {
+            $sh .= ' -o ' . $dir . $lang . '.po --locale=' . $lang;
+        }
+        if (!$debug) {
+            $sh .= $silence;
+        }
+        if (!empty($lang) && !OS_WINDOWS) {
+            $pofile = $dirs[$i] . '/po/' . $lang . '.po';
+            $sh .= "; sed 's/PACKAGE package/$package package/' $pofile " .
+                   "| sed 's/PACKAGE VERSION/$package $version/' " .
+                   "| sed 's/messages for PACKAGE/messages for $package/' " .
+                   "| sed 's/Language-Team: none/Language-Team: i18n@lists.horde.org/' " .
+                   "> $pofile.tmp";
+        }
+        if ($debug || $test) {
+            $c->writeln();
+            $c->writeln('Executing:');
+            $c->writeln($sh);
+        }
+        if ($test) {
+            $ret = 0;
+        } else {
+            exec($sh, $out, $ret);
+        }
+        rename($pofile . '.tmp', $pofile);
+        if ($ret == 0) {
+            $c->writeln($c->green('done'));
+        } else {
+            $c->writeln($c->red('failed'));
+        }
+    }
+}
+
+function make()
+{
+    global $cmd_options, $apps, $dirs, $debug, $test, $c, $silence, $redir_err;
+
+    $compendium = HORDE_BASE . DS . 'locale' . DS . 'compendium.po';
+    $save_stats = false;
+    foreach ($cmd_options[0] as $option) {
+        switch ($option[0]) {
+        case 'h':
+            usage();
+            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', $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($dirs); $i++) {
+        if (!empty($module) && $module != $apps[$i]) {
+            continue;
+        }
+        $c->writeln(sprintf('Building MO files for module %s...', $c->bold($apps[$i])));
+        $dir = $dirs[$i] . DS . 'locale' . DS . '%s' . DS . 'LC_MESSAGES' . DS;
+        if (empty($lang)) {
+            $langs = get_languages($dirs[$i]);
+        } else {
+            if (!file_exists(sprintf($dir, $lang) . $apps[$i] . '.po')) {
+                $c->writeln('Skipped...');
+                $c->writeln();
+                continue;
+            }
+            $langs = array($lang);
+        }
+        foreach ($langs as $locale) {
+            $c->writeln(sprintf('Building locale %s...', $c->bold($locale)));
+            $targetdir = sprintf($dir, $locale);
+            $pofile = $targetdir . $apps[$i] . '.po';
+            if (!is_dir($targetdir)) {
+                if ($debug) {
+                    $c->writeln(sprintf('Making directory %s', $targetdir));
+                }
+                if (!$test && !System::mkdir("-p $targetdir")) {
+                    $c->message(sprintf('Could not create locale directory for locale %s:', $locale), 'cli.warning');
+                    $c->writeln($targetdir);
+                    $c->writeln();
+                    continue;
+                }
+            }
+
+            /* 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 = $GLOBALS['msgfmt'] . " --check --output-file=/dev/null \"$pofile\" $redir_err";
+            if ($debug || $test) {
+                $c->writeln('Executing:');
+                $c->writeln($sh);
+            }
+            if ($test) {
+                $ret = 0;
+            } else {
+                exec($sh, $out, $ret);
+            }
+            if ($ret != 0) {
+                $c->message('An error has occured:', 'cli.warning');
+                $c->writeln(implode("\n", $out));
+                $c->writeln();
+                if ($apps[$i] == 'horde') {
+                    continue 2;
+                }
+                continue;
+            }
+
+            /* Compile MO file. */
+            $sh = $GLOBALS['msgfmt'] . ' --statistics -o "' . $targetdir . DS . $apps[$i] . '.mo" ';
+            if ($apps[$i] != 'horde') {
+                $horde_po = $dirs[$horde] . DS . 'po' . DS . $locale . '.po';
+                if (!is_readable($horde_po)) {
+                    $c->message(sprintf('The Horde PO file for the locale %s does not exist:', $locale), 'cli.warning');
+                    $c->writeln($horde_po);
+                    $c->writeln();
+                    $sh .= '"' . $dirs[$i] . DS . 'po' . DS . $locale . '.po"';
+                } else {
+                    $comm = $GLOBALS['msgcomm'] . " --more-than=0 --sort-output \"$pofile\"";
+                    $sh = $comm . " \"$horde_po\" | $sh -";
+                }
+            } else {
+                $sh .= '"' . $pofile . '"';
+            }
+            $sh .= $redir_err;
+            if ($debug || $test) {
+                $c->writeln('Executing:');
+                $c->writeln($sh);
+            }
+            $out = '';
+            if ($test) {
+                $ret = 0;
+            } else {
+                putenv('LANG=en');
+                exec($sh, $out, $ret);
+                putenv('LANG=' . $GLOBALS['language']);
+            }
+            if ($ret == 0) {
+                $c->writeln($c->green('done'));
+                $messages = array(0, 0, 0, $last_update);
+                if (preg_match('/(\d+) translated/', $out[0], $match)) {
+                    $messages[0] = $match[1];
+                    if (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 (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 (isset($horde_msg[$locale])) {
+                        $messages[2] -= $horde_msg[$locale][2];
+                        if ($messages[2] < 0) $messages[2] = 0;
+                    }
+                }
+                if ($apps[$i] == 'horde') {
+                    $horde_msg[$locale] = $messages;
+                }
+                $stats_array[$apps[$i]][$locale] = $messages;
+                $stats->addRow(array($apps[$i], $locale, $messages[0], $messages[1], $messages[2], $messages[3]));
+            } else {
+                $c->writeln($c->red('failed'));
+                exec($sh, $out, $ret);
+                $c->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... ', $c->bold($apps[$i]));
+                if (!empty($targetdir) && substr($targetdir, -1) != DS) {
+                    $targetdir .= DS;
+                }
+                $sh = $GLOBALS['msgcat'] . " --sort-output \"$compendium\" \"$pofile\" > \"$compendium.tmp\"";
+                if (!$debug) {
+                    $sh .= $silence;
+                }
+                if ($debug || $test) {
+                    $c->writeln();
+                    $c->writeln('Executing:');
+                    $c->writeln($sh);
+                }
+                $out = '';
+                if ($test) {
+                    $ret = 0;
+                } else {
+                    exec($sh, $out, $ret);
+                }
+                unlink($compendium);
+                rename($compendium . '.tmp', $compendium);
+                if ($ret == 0) {
+                    $c->writeln($c->green('done'));
+                } else {
+                    $c->writeln($c->red('failed'));
+                }
+            }
+            $c->writeln();
+        }
+    }
+    if (empty($module)) {
+        $c->writeln('Results:');
+    } else {
+        $c->writeln('Results (including Horde):');
+    }
+    $c->writeln($stats->getTable());
+    if ($save_stats) {
+        $fp = fopen('translation_stats.txt', 'w');
+        if ($fp) {
+            fwrite($fp, serialize($stats_array));
+            fclose($fp);
+        }
+    }
+}
+
+function cleanup($keep_untranslated = false)
+{
+    global $cmd_options, $apps, $dirs, $debug, $test, $c, $silence;
+
+    foreach ($cmd_options[0] as $option) {
+        switch ($option[0]) {
+        case 'h':
+            usage();
+            footer();
+        case 'l':
+        case '--locale':
+            $lang = $option[1];
+            break;
+        case 'm':
+        case '--module':
+            $module = $option[1];
+            break;
+        }
+    }
+
+    for ($i = 0; $i < count($dirs); $i++) {
+        if (!empty($module) && $module != $apps[$i]) {
+            continue;
+        }
+        $c->writeln(sprintf('Cleaning up PO files for module %s...', $c->bold($apps[$i])));
+        $po = $dirs[$i] . DS . 'locale' . DS . '%s' . DS . 'LC_MESSAGES' .DS . $apps[$i] . '.po';
+        if (empty($lang)) {
+            $langs = get_languages($dirs[$i]);
+        } else {
+            if (!file_exists(sprintf($po, $lang))) {
+                $c->writeln('Skipped...');
+                $c->writeln();
+                continue;
+            }
+            $langs = array($lang);
+        }
+        foreach ($langs as $locale) {
+            $c->writeln(sprintf('Cleaning up locale %s... ', $c->bold($locale)));
+            $pofile = sprintf($po, $locale);
+            $sh = $GLOBALS['msgattrib'] . ($keep_untranslated ? '' : ' --translated') . " --no-obsolete --force-po \"$pofile\" > \"$pofile.tmp\"";
+            if (!$debug) {
+                $sh .= $silence;
+            }
+            if ($debug || $test) {
+                $c->writeln();
+                $c->writeln('Executing:');
+                $c->writeln($sh);
+            }
+            $out = '';
+            if ($test) {
+                $ret = 0;
+            } else {
+                exec($sh, $out, $ret);
+            }
+            if ($ret == 0) {
+                unlink($pofile);
+                rename($pofile . '.tmp', $pofile);
+                $c->writeln($c->green('done'));
+            } else {
+                unlink($pofile . '.tmp', $pofile);
+                $c->writeln($c->red('failed'));
+            }
+            $c->writeln();
+        }
+    }
+}
+
+function commit($help_only = false)
+{
+    global $cmd_options, $apps, $dirs, $debug, $test, $c;
+
+    $docs = $skip = $lang = false;
+    $git = is_dir(BASE . '/.git');
+    foreach ($cmd_options[0] as $option) {
+        switch ($option[0]) {
+        case 'h':
+            usage();
+            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;
+        case 's':
+        case '--skip':
+            $skip = true;
+            break;
+        }
+    }
+    $files = array();
+    for ($i = 0; $i < count($dirs); $i++) {
+        if ((!empty($module) && $module != $apps[$i]) ||
+            (!$git && $skip && !is_dir($dirs[$i] . DS . 'CVS'))) {
+            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, strip_horde(search_ext('xml', $dirs[$i] . DS . 'locale')));
+            } else {
+                $files = array_merge($files, strip_horde(get_po_files($dirs[$i] . DS . 'po')));
+                $files = array_merge($files, strip_horde(search_file('^[a-z]{2}_[A-Z]{2}', $dirs[$i] . DS . 'locale', true)));
+            }
+        } else {
+            if ($help_only &&
+                !file_exists($dirs[$i] . DS . 'locale' . DS . $lang . DS . 'help.xml')) {
+                continue;
+            }
+            $files[] = strip_horde($dirs[$i] . DS . 'locale' . DS . $lang);
+        }
+        if ($docs && !$help_only && $apps[$i]) {
+            $files[] = strip_horde($dirs[$i] . DS . 'docs');
+            if ($apps[$i] == 'horde') {
+                $horde_conf = $dirs[array_search('horde', $dirs)] . DS . 'config' . DS;
+                $files[] = strip_horde($horde_conf . 'nls.php');
+            }
+        }
+    }
+    chdir(BASE);
+    if (count($files)) {
+        if ($docs) {
+            $c->writeln('Adding new files to repository:');
+            $add_files = array();
+            foreach ($files as $file) {
+                if (strstr($file, 'locale')) {
+                    $add_files[] = $file;
+                    $c->writeln($file);
+                }
+            }
+            foreach ($files as $file) {
+                if (strstr($file, 'locale')) {
+                    if ($help_only) {
+                        $add_files[] = $file . DS . '*.xml';
+                        $c->writeln($file . DS . '*.xml');
+                    } else {
+                        $add_files[] = $file . DS . '*.xml ' . $file . DS . 'LC_MESSAGES';
+                        $c->writeln($file . DS . "*.xml\n$file" . DS . 'LC_MESSAGES');
+                    }
+                }
+            }
+            if (!$help_only) {
+                foreach ($files as $file) {
+                    if (strstr($file, 'locale')) {
+                        $c->writeln($add_files[] = $file . DS . 'LC_MESSAGES' . DS . '*.po');
+                        $c->writeln($add_files[] = $file . DS . 'LC_MESSAGES' . DS . '*.mo');
+                    }
+                }
+            }
+            $c->writeln();
+            if ($git) {
+                if ($debug || $test) {
+                    $c->writeln('Executing:');
+                    $c->writeln('git add ' . implode(' ', $add_files));
+                }
+                if (!$test) {
+                    system('git add ' . implode(' ', $add_files));
+                }
+            } else {
+                foreach ($add_files as $add_file) {
+                    if ($debug || $test) {
+                        $c->writeln('Executing:');
+                        $c->writeln('cvs add ' . $add_file);
+                    }
+                    if (!$test) {
+                        system('cvs add ' . $add_file);
+                    }
+                }
+            }
+            $c->writeln();
+        }
+        $c->writeln('Committing:');
+        $c->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.";
+            }
+        }
+        if ($git) {
+            $sh = 'git add ' . implode(' ', $files) . '; git commit -m "' . $msg . '"';
+        } else {
+            $sh = 'cvs commit -m "' . $msg . '" ' . implode(' ', $files);
+        }
+        if ($debug || $test) {
+            $c->writeln('Executing:');
+            $c->writeln($sh);
+        }
+        if (!$test) system($sh);
+        if ($git) {
+            $c->message('You have to push the commit manually!', 'cli.warning');
+        }
+    }
+}
+
+function update_help()
+{
+    global $cmd_options, $dirs, $apps, $debug, $test, $last_error_msg, $c;
+
+    foreach ($cmd_options[0] as $option) {
+        switch ($option[0]) {
+        case 'h':
+            usage();
+            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 = 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_US' . DS . 'help.xml';
+        if (!file_exists($file_en)) {
+            $c->message(sprintf('There doesn\'t yet exist a help file for %s.', $c->bold($apps[$i])), 'cli.warning');
+            $c->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_US') {
+                continue;
+            }
+            if (!file_exists($file_loc)) {
+                $c->message(sprintf('The %s help file for %s doesn\'t yet exist. Creating a new one.', $c->bold($locale), $c->bold($apps[$i])), 'cli.warning');
+                $dir_loc = substr($file_loc, 0, -9);
+                if (!is_dir($dir_loc)) {
+                    if ($debug || $test) {
+                        $c->writeln(sprintf('Making directory %s', $dir_loc));
+                    }
+                    if (!$test && !System::mkdir("-p $dir_loc")) {
+                        $c->message(sprintf('Could not create locale directory for locale %s:', $locale), 'cli.warning');
+                        $c->writeln($dir_loc);
+                        $c->writeln();
+                        continue;
+                    }
+                }
+                if ($debug || $test) {
+                    $c->writeln(wordwrap(sprintf('Copying %s to %s', $file_en, $file_loc)));
+                }
+                if (!$test && !copy($file_en, $file_loc)) {
+                    $c->message(sprintf('Could not copy %s to %s', $file_en, $file_loc), 'cli.warning');
+                }
+                $c->writeln();
+                continue;
+            }
+            $c->writeln(sprintf('Updating %s help file for %s.', $c->bold($locale), $c->bold($apps[$i])));
+
+            if (!($doc_en = DOMDocument::load($file_en))) {
+                $c->message(sprintf('There was an error opening the file %s. Try running translation.php with the flag -d to see any error messages from the xml parser.', $file_en), 'cli.warning');
+                $c->writeln();
+                continue 2;
+            }
+            $doc_en->encoding = 'UTF-8';
+            $doc_en->formatOutput = true;
+
+            if (!($doc_loc = DOMDocument::load($file_loc))) {
+                $c->message(sprintf('There was an error opening the file %s. Try running translation.php with the flag -d to see any error messages from the xml parser.', $file_loc), 'cli.warning');
+                $c->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) {
+                $list = $xpath->query('//entry[@id="' . $entry->getAttribute('id') . '"]');
+                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('--', '&#45;&#45;', $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('--', '&#45;&#45;', $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);
+            }
+            $c->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 ($debug || $test) {
+                $c->writeln(wordwrap(sprintf('Writing updated help file to %s.', $file_loc)));
+            }
+            if (!$test) {
+                $doc_en->save($file_loc);
+            }
+            $c->writeln();
+        }
+    }
+}
+
+function make_help()
+{
+    global $cmd_options, $dirs, $apps, $debug, $test, $c;
+
+    foreach ($cmd_options[0] as $option) {
+        switch ($option[0]) {
+        case 'h':
+            usage();
+            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 = 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_US' . DS . 'help.xml';
+        if (!file_exists($file_en)) {
+            continue;
+        }
+
+        if (!($doc_en = DOMDocument::load($file_en))) {
+            $c->message(sprintf('There was an error opening the file %s. Try running translation.php with the flag -d to see any error messages from the xml parser.', $file_en), 'cli.warning');
+            $c->writeln();
+            continue;
+        }
+        $xpath = new DOMXPath($doc_en);
+
+        foreach ($files as $file_loc) {
+            if (!file_exists($file_loc)) {
+                $c->writeln('Skipped...');
+                $c->writeln();
+                continue;
+            }
+            $locale = substr($file_loc, 0, strrpos($file_loc, DS));
+            $locale = substr($locale, strrpos($locale, DS) + 1);
+            if ($locale == 'en_US') continue;
+            $c->writeln(sprintf('Updating %s help file for %s.', $c->bold($locale), $c->bold($apps[$i])));
+
+            if (!($doc_loc = DOMDocument::load($file_loc))) {
+                $c->message(sprintf('There was an error opening the file %s. Try running translation.php with the flag -d to see any error messages from the xml parser.', $file_loc), 'cli.warning');
+                $c->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++;
+                $list = $xpath->query('//entry[@id="' . $entry->getAttribute('id') . '"]');
+                if ($list->length) {
+                    $entry->setAttribute('md5', md5($list->item(0)->textContent));
+                    $entry->setAttribute('state', 'uptodate');
+                    $count++;
+                } else {
+                    $c->message(sprintf('No entry with the id "%s" exists in the original help file.', $entry->getAttribute('id')), 'cli.warning');
+                }
+            }
+
+            if (!$test) {
+                $doc_loc->save($file_loc);
+            }
+            $c->writeln(sprintf('%d of %d entries marked as up-to-date', $count, $count_all));
+            $c->writeln();
+        }
+    }
+}
+
+$curdir = getcwd();
+define('DS', DIRECTORY_SEPARATOR);
+
+$language = getenv('LANG');
+if (empty($language)) {
+    $language = getenv('LANGUAGE');
+}
+
+require_once dirname(__FILE__) . '/../lib/Application.php';
+
+$c = new Horde_Cli();
+if (!$c->runningFromCLI()) {
+    $c->fatal('This script must be run from the command line.');
+}
+$c->init();
+
+$c->writeln($c->bold('---------------------------'));
+$c->writeln($c->bold('Horde translation generator'));
+$c->writeln($c->bold('---------------------------'));
+
+/* Sanity checks */
+if (!extension_loaded('gettext')) {
+    $c->message('Gettext extension not found!', 'cli.error');
+    footer();
+}
+
+$c->writeln('Loading libraries...');
+$libs_found = true;
+
+foreach (array('Console_Getopt' => 'Console/Getopt.php',
+               'Console_Table'  => 'Console/Table.php',
+               'File_Find'      => 'File/Find.php')
+         as $class => $file) {
+    include $file;
+    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'));
+    footer();
+}
+$c->writeln();
+
+/* 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();
+    usage();
+    footer();
+}
+if (empty($options[0][0]) && empty($options[1][0])) {
+    $c->message('No command specified.', 'cli.error');
+    $c->writeln();
+    usage();
+    footer();
+}
+$debug = false;
+$test  = false;
+foreach ($options[0] as $option) {
+    switch ($option[0]) {
+    case 'b':
+    case '--base':
+        define('BASE', realpath($option[1]));
+        break;
+    case 'd':
+    case '--debug':
+        $debug = true;
+        break;
+    case 't':
+    case '--test':
+        $test = true;
+        break;
+    case 'h':
+    case '--help':
+        usage();
+        footer();
+    }
+}
+if (!$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') {
+    usage();
+    footer();
+}
+$silence = $debug || OS_WINDOWS ? '' : ' 2> /dev/null';
+$redir_err = OS_WINDOWS ? '' : ' 2>&1';
+$options_list = array(
+    'cleanup'    => array('hl:m:', array('module=', 'locale=')),
+    'commit'     => array('hl:m:nM:s', array('module=', 'locale=', 'new', 'message=', 'skip')),
+    '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();
+        usage();
+        footer();
+    }
+}
+
+/* Searching applications */
+check_binaries();
+
+$c->writeln(sprintf('Searching Horde applications in %s', BASE));
+$dirs = search_applications();
+
+if ($debug) {
+    $c->writeln('Found directories:');
+    $c->writeln(implode("\n", $dirs));
+}
+
+$apps = strip_horde($dirs);
+$apps[0] = 'horde';
+$c->writeln(wordwrap(sprintf('Found applications: %s', implode(', ', $apps))));
+$c->writeln();
+
+switch ($cmd) {
+case 'cleanup':
+case 'commit':
+case 'compendium':
+case 'merge':
+    $cmd();
+    break;
+case 'commit-help':
+    commit(true);
+    break;
+case 'extract':
+    xtract();
+    break;
+case 'init':
+    init();
+    $c->writeln();
+    merge();
+    break;
+case 'make':
+    cleanup(true);
+    $c->writeln();
+    make();
+    break;
+case 'make-help':
+    make_help();
+    break;
+case 'update':
+    xtract();
+    $c->writeln();
+    merge();
+    break;
+case 'update-help':
+    update_help();
+    break;
+case 'status':
+    merge();
+    break;
+default:
+    $c->message(sprintf('Unknown command: %s', $cmd), 'cli.error');
+    $c->writeln();
+    usage();
+    footer();
+}
+
+footer();
index 2e58608..f822498 100644 (file)
@@ -2,12 +2,21 @@
  Horde Translation Guide
 =========================
 
-:Author:        Joris Braakman <jbraakman@yahoo.com>
-:Author:        Chuck Hagenbuch <chuck@horde.org>
-:Author:        Jan Schneider <jan@horde.org>
-:Contact:       horde@lists.horde.org
+:Author:  Jan Schneider <jan@horde.org>
+:Author:  Chuck Hagenbuch <chuck@horde.org>
+:Author:  Joris Braakman <jbraakman@yahoo.com>
+:Contact: horde@lists.horde.org (for administrators)
+:Contact: i18n@lists.horde.org (for translators)
 
 .. contents:: Contents
+.. section-numbering::
+
+
+---------------------
+ Administrator Guide
+---------------------
+
+For translator documentation see the `Translator Guide`_ below.
 
 
 GNU gettext, PHP and Horde
@@ -172,3 +181,496 @@ FreeBSD
 To enable UTF-8 support in Horde, you also need UTF-8 support in
 FreeBSD.  This is not installed by default, you need to install the
 ``utf8locale-without-swidth-040319`` package or port.
+
+
+------------------
+ Translator Guide
+------------------
+
+
+Translation Tool
+================
+
+``translation`` is a small PHP script that should help translators doing
+their work.
+
+Any feedback, bug reports and feature requests should be send to the `i18n
+mailing list`_. This is also the right place for new translations and general
+discussions of i18n and l10n questions and problems.
+
+``translation`` is located in ``horde/bin``, so you will have to make
+``horde/bin`` your working directory in order to run the commands quoted
+below. But you can run the script from any directory, just adapt the directory
+to the script. See _Prerequisites if your PHP binary is not your binary search
+path.
+
+For a list of available commands run::
+
+  ./translation help
+
+For detailed help on a certain command run::
+
+  ./translation help command
+
+Additional information about creating translations and fixing problems can be
+found in the `Administrator Guide`_.
+
+.. _i18n mailing list: http://www.horde.org/mail/
+
+Prerequisites
+~~~~~~~~~~~~~
+
+To run this script you'll need a PHP command line executable with `gettext
+support`_ compiled in, and the basic `PEAR`_ libraries. The script expects
+your PHP executable to be in the binary search path. If your PHP executable
+cannot be found, either edit the first line of ``translation`` to reflect your
+location or call the script like::
+
+  /usr/local/bin/php translation.
+
+You'll need the `gettext`_ package version 0.12 or greater.
+
+You'll need the PEAR packages `Console_Getopt`_ 0.11 or greater,
+`Console_Table`_ and `File_Find`_. To install all needed packages, run::
+
+  pear upgrade PEAR Console_Getopt
+  pear install Console_Table File_Find
+
+or download the newest package from the `PEAR`_ server and install them
+manually in your PEAR directory.
+
+.. _gettext support: http://www.php.net/gettext/
+.. _gettext: http://www.gnu.org/software/gettext/
+.. _PEAR: http://pear.php.net/
+.. _Console_Getopt: http://pear.php.net/package/Console_Getopt/
+.. _Console_Table: http://pear.php.net/package/Console_Table/
+.. _File_Find: http://pear.php.net/package/File_Find/
+
+Creating a new translation
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To create a new translation you first have to extract all gettext messages
+from the PHP sources. There are already template files with the ``.pot``
+suffix in the ``locale`` directories that you can use if you have troubles
+extracting the messages, though these templates might be slightly
+outdated. Run::
+
+  ./translation extract
+
+You now have to create a new PO file for your locale. A locale has the form
+``ll_CC`` where ``ll`` is the two letter `ISO 639`_ code of the language and
+``CC`` the two letter `ISO 3166`_ code of the country, e.g. ``de_DE``,
+``en_US`` or ``pt_BR``.
+``translation`` is able to guess the locale from the ``LANG`` environment
+variable but it is safer to specify the locale with the ``-l`` parameter.
+To create the PO file run::
+
+  ./translation init -l ll_CC
+
+Now you can start the translation by editing the created ``ll_CC.po``
+files. It is important to set the charset for the locale in the
+``Content-Type:`` header to ``UTF-8``.  You should fill out the the complete
+header of the created ``ll_CC.po`` file, e.g.::
+
+  # Dutch translation for Horde.
+  # Copyright 2004-2010 The Horde Project (http://www.horde.org/)
+  # This file is distributed under the same license as the Horde package.
+  # Joris Braakman <jbraakman@yahoo.com>, 2004.
+  #
+  msgid ""
+  msgstr ""
+  "Project-Id-Version: Horde 2.3\n"
+  "Report-Msgid-Bugs-To: dev@lists.horde.org\n"
+  "POT-Creation-Date: 2004-04-14 10:30+0200\n"
+  "PO-Revision-Date: 2004-04-14 17:17+02:00\n"
+  "Last-Translator: Joris Braakman <jbraakman@yahoo.com>\n"
+  "Language-Team: i18n@lists.horde.org\n"
+  "MIME-Version: 1.0\n"
+  "Content-Type: text/plain; charset=ISO-8859-1\n"
+  "Content-Transfer-Encoding: 8-bit\n"
+
+To compile the translations to a binary format run::
+
+  ./translation make -l ll_CC
+
+After you created the new translation you have to add entries for this locale
+in the configuration file horde/config/nls.php.
+
+If you create a new translation for a certain module but there are already
+translations for the same language for other modules, then you should use a
+compendium. This will save you a lot of time and it will make translations
+much more consistent.
+
+.. _ISO 639: http://www.loc.gov/standards/iso639-2/
+.. _ISO 3166: http://www.iso.ch/iso/en/prods-services/iso3166ma/index.html
+
+Compendiums
+~~~~~~~~~~~
+
+This tool utilizes a nice feature of gettext: compendium files. A compendium
+is a special PO file containing a set of translations recurring in many
+different packages. These compendiums will be used in the background without
+much intervention required. But you have to create and edit a compendium
+before you can use it.
+
+To create a compendium of all existing translations for a certain locale run::
+
+  ./translation compendium -l ll_CC
+
+The ``compendium.po`` being created will contain all modules' translations of
+this locale merged into a single file. You should take a closer look at this
+file because you may find a lot of special marked lines where you translated
+certain strings differently in the various modules. It's a good idea to fix
+the modules' translations now so that all modules use the same translations for
+the same strings. You can always recreate your compendium with the above
+command.
+
+If you're maintaining translations for different `branches`_ and assumed that
+you have all modules of the development branch in one directory and all of the
+stalbe branch in another, you probably want to share a compendium between
+these directories.
+
+To do this, you should first create a compendium in the stable branch, review
+it and fix all translations until you're happy with the results. Then create a
+second compendium in the head branch and include your first one with the --add
+option.  Now fix the translations in this branch. If you're ready you can
+remove the first compendium and for now on use the compendium in the
+development branch for both branches. To do so, use the ``-c`` or
+``--compendium`` option to specify a path to your compendium.
+
+If you translate directly from a git checkout, this is usually not necessary
+because you work with all branches in the same directory and simply switch
+between them with ``git checkout``.
+
+.. _branches: http://www.horde.org/source/
+
+Updating translations
+~~~~~~~~~~~~~~~~~~~~~
+
+The process of updating translations is a cycle where you extract new gettext
+strings from the sources, translate those new strings or update the already
+translated strings and compile them after.
+
+To update the translation for a module, run::
+
+  ./translation update -m modulename -l ll_CC
+
+This extracts the new strings from the sources and tries to update them from
+already existing translations in the compendium. You just have to translate
+all untranslated strings in the ``modulename.po`` file in the
+``locale/ll_CC/LC_MESSAGES`` directory of the module you updated.
+
+If extracting new strings fails for some reason, you can use the provided
+``.pot`` file instead to update your translation::
+
+  ./translation merge -m modulename -l ll_CC
+
+If your compendium is in a different directory than the ``translation``
+script, you can specify the path to the compendium::
+
+  ./translation update -m modulename -l ll_CC -c /path/to/compendium
+
+Once this is done, you can compile the translation by calling::
+
+  ./translation make -m modulename -l ll_CC
+
+Extending existent translations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To have your own string (e.g. that you added to config files) displayed in
+several languages, you have to
+
+  1. specify your texts as gettext arguments, in English, e.g. by using the
+     _() function.
+  2. edit the translation files for the required national languages,
+  3. compile those translation files.
+
+Be sure to keep records of your extensions, as you will probably have to
+repeat steps 2 and 3 after the next update.
+
+E.g., you plan to offer two IMAP servers to select from in the Webmail login
+screen:
+
+  1. In ``horde/imp/config/servers.php``, you specify::
+
+       $servers['Central'] = array(
+          'name' => _("Central Mail Service"),
+       ...
+       $servers['CompSci'] = array(
+          'name' => _("Mail Service of Computer Science Dpt.")
+       ...
+
+  2. In ``horde/imp/po/de_DE.po`` you add two entries::
+
+       msgid "Central Mail Service"
+       msgstr "Zentraler Mailserver"
+
+       msgid "Mail Service of Computer Science Dpt."
+       msgstr "Mailserver Informatik"
+
+     Likewise, you amend the translation files for other languages, as needed.
+
+  3. You compile the translations using the commands::
+
+       ./translation make --module imp --no-compendium
+
+Access Keys
+===========
+
+Access keys, also known as shortcut keys, allow easy access to important
+functions, normally by hitting the Alt/Meta key in combination with another
+key. This key is marked in most user interfaces by being underlined.
+
+As the access key is part of the word representing the action being executed,
+it is in the translators responsibility to select an access key when he
+translates these words. The action is always a link in Horde. The access key
+of a link is selected by prefixing it with an underscore.
+
+The help link in the menu for example is always "_Help". This means that the
+"H" of the link will be underlined and the help can be opened by hitting
+Alt+H. In the PO file this string will appear as::
+
+  #: templates/menu/menu.inc:53
+  msgid "_Help"
+  msgstr ""
+
+A Spanish translator might want to translate this to::
+
+  #: templates/menu/menu.inc:53
+  msgid "_Help"
+  msgstr "_Ayuda"
+
+Translators of multibyte languages need to do this a bit differently as only
+ASCII characters are allowed for access keys. A Traditional Chinese translator
+might want to use::
+
+  #: templates/menu/menu.inc:53
+  msgid "_Help"
+  msgstr "_H說明"
+
+This renders to "說明(H)" in the interface and you can
+access this link with "H" as the access key.
+
+
+Right-to-Left Languages
+=======================
+
+Translations for languages that are written from right to left might cause
+unexpected behavior if parenthesis or similar characters appear inside a
+translated string. To fix this broken string rendering you have to insert
+special Unicode codepoints into the translated string.
+
+Before such a string in parenthesis, add the U+202D codepoint. If there is
+more right-to-left text to come after the closing parenthesis, add the U+202E
+codepoint after it. If using the PO mode of the Emacs editor you can add
+codepoints with the "ucs-insert" command.
+
+
+Help Texts
+==========
+
+Organization of the help files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The help texts are available in the ``horde/locale/``, and
+``horde/APP/locale/``, directories, where APP is any Horde application. Every
+available translation is kept in a file called ``help.xml``, in a subdirectory
+named according to RFC 3066. Examples:
+
+- Horde's original help texts are in the ``horde/locale/en_US/help.xml`` file.
+- IMP's Brazilian help texts are in the ``horde/imp/locale/pt_BR/help.xml``
+  file.
+
+When, for any desired application and locale, there is no ``help.xml`` file
+available, Horde's help system will use the application's
+``locale/en_US/help.xml`` file, instead.
+
+The help files must be encoded in the UTF-8 character set.
+
+There is no compilation step involved: Every modification to, or addition of,
+a ``help.xml`` file takes immediate effect.
+
+Syntax of the help files
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Each help file must consist of syntactically valid XML code.
+
+There are no predefined entities beyond the XML standard entities:
+
+- &lt;   (less than)
+- &gt;   (greater than)
+- &amp;  (ampersand)
+- &apos; (ASCII apostrophe)
+- &quot; (ASCII quotation mark)
+
+Any character available in the language's preferred character set can be
+entered as a numerical character reference (based on its Unicode scalar
+value), such as ``&#160;`` for the No-Break Space character.
+
+The general structure can be learned from the existing examples; of course,
+the XML tags must be syntactically valid, and properly nested.
+
+.. Note:: A PHP error message like ``Undefined index: url in
+          /var/www/horde/lib/Horde/Help.php`` means that you have mis-spelled,
+          or omitted, an attribute (``url``, in this example).
+
+The following tags are available:
+
++---------+--------+---------------+-------------------------------+
+| XML-Tag | Parent | Attributes    | Purpose                       |
++=========+========+===============+===============================+
+| help    |   â€”    |       â€”       | List of help texts            |
++---------+--------+---------------+-------------------------------+
+| entry   | help   | id            | Help text                     |
++---------+--------+---------------+-------------------------------+
+| title   | entry  |       â€”       | Entry in the help index       |
++---------+--------+---------------+-------------------------------+
+| heading | entry  |       â€”       | Level 2 heading               |
++---------+--------+---------------+-------------------------------+
+| para    | entry  |       â€”       | Paragraph                     |
++---------+--------+---------------+-------------------------------+
+| ref     | para   | module, entry | Link into the help system     |
++---------+--------+---------------+-------------------------------+
+| eref    | para   | url           | External link                 |
++---------+--------+---------------+-------------------------------+
+| href    | para   | app, url      | Link into a Horde application |
++---------+--------+---------------+-------------------------------+
+| b       | para   |       â€”       | Bold text                     |
++---------+--------+---------------+-------------------------------+
+| i       | para   |       â€”       | Italic text on new line       |
++---------+--------+---------------+-------------------------------+
+| pre     | entry  |       â€”       | Example box                   |
++---------+--------+---------------+-------------------------------+
+| tip     | entry  |       â€”       | Tip/hint box                  |
++---------+--------+---------------+-------------------------------+
+| warn    | entry  |       â€”       | Warning box                   |
++---------+--------+---------------+-------------------------------+
+
+The elements marked ``para``, in the Parent column, may also be used within
+``pre``, ``tip``, and ``warn`` elements.
+
+The following attributes are available:
+
++---------+-----------+----------------------------------+
+| XML-Tag | Attribute | Purpose/Syntax                   |
++=========+===========+==================================+
+| entry   | id        | Anchor                           |
++---------+-----------+----------------------------------+
+| ref     | module    | Index of Horde application, see  |
+|         |           | ``horde/config/registry.php``    |
+|         +-----------+----------------------------------+
+|         | entry     | Id of help file entry, see above |
++---------+-----------+----------------------------------+
+| eref    | url       | arbitrary URL                    |
++---------+-----------+----------------------------------+
+| href    | app       | Index of Horde application, see  |
+|         |           | ``horde/config/registry.php``    |
+|         +-----------+----------------------------------+
+|         | url       | URL within Horde application     |
++---------+-----------+----------------------------------+
+
+Examples::
+
+  <ref module="imp" entry="compose-attachments">selecting attachments</ref>
+  <eref url="http://wiki.horde.org/FAQ/User/IMP?referer=FAQ/User#toc17">
+        UW-IMAP quirk</eref>
+  <href app="turba" url="search.php">Address search</href>
+
+The `Translation Tool`_ will introduce additional attributes:
+
++---------+-----------+--------------------------------+
+| XML-Tag | Attribute | Purpose/Syntax                 |
++=========+===========+================================+
+| entry   | state     | See `Finishing a translation`_ |
+|         +-----------+--------------------------------+
+|         | md5       | Used internally                |
++---------+-----------+--------------------------------+
+
+Reference from the Horde applications
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The typical incantation, e.g.::
+
+  <?php echo Horde_Help::link('imp', 'compose-buttons') ?>
+
+will yield a link, adorned with the ``help.png`` icon, to the entry marked
+``id="compose-buttons"`` in the IMP help file pertaining to the user's current
+locale. Usually, these links are placed in the template files, next to the
+item to be explained by the pertinent help entry.
+
+The Horde menu will usually contain a general help item, which is generated in
+``lib/Horde/Menu.php``.
+
+Creating a new translation
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If necessary, define a suitable locale id ``ll_CC``, complying with RFC
+3066. E.g., if you plan to add an Austrian translation, you would use
+``de_AT`` as your locale id.
+
+For Horde, and for all relevant applications, copy the ``help.xml`` files from
+the ``locale/en_US/`` subdirectories to the ``locale/ll_CC/`` subdirectories,
+creating them if necessary.
+
+Translate each new ``locale/ll_CC/help.xml`` file, leaving all tags and
+attributes unchanged. Just translate the text between the tags.
+
+.. Important:: The ``id`` attributes must be kept unchanged, under any
+               circumstances.
+
+If the application is already translated and only the help files are missing,
+then be sure to use the same terminology as the existing translation. In any
+case, try to use a lucid, coherent terminology.
+
+Finishing a translation
+~~~~~~~~~~~~~~~~~~~~~~~
+
+If you want to submit a translation to be included in the Horde code base,
+make sure that you have completed all translations. Then run::
+
+  ./translation make-help
+
+This will mark all entries as being up-to-date so that you or other
+translators can later see which entries have been changed since then.
+
+Updating an existing translation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Run::
+
+  ./translation update-help
+
+This will merge your existing help file with all changes from the original,
+English help file since your last translation. Changed entries are marked with
+the attribute ``state="changed"``, new entries with ``state="new"``. The
+original entry from the English help file is added in a comment below a
+changed entry, so that you can easily compare them.
+
+Translate the remaining English phrases, like discussed above. When your
+finished, remove any English entries and follow the steps at `Finishing a
+translation`_.
+
+Extending existent translations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you feel that some feature should be better explained to the end user, then
+proceed as following:
+
+1. Determine, whether you will have to extend an existing entry, or add a new
+   one.
+
+2. In the latter case, choose a suitable ``id`` for your new help entry.
+
+3. Edit the existing entry in, or add a new entry to, the application's
+   ``en_US/help.xml`` file. Change all translations needed in your site in the
+   same way. Be sure to use the same ``id`` everywhere.
+
+4. If you have added a new entry, you might want to add a link to it from a
+   template, as explained above in `Reference from the Horde applications`_.
+
+5. If you feel that your enhancement may be useful to other users, file an
+   enhancement request at http://bugs.horde.org/ with a patch including your
+   changes to the template and the ``help.xml`` files.
+
+6. In any case, keep notes of your changes, so you can apply them to later
+   versions if necessary.
diff --git a/horde/po/.htaccess b/horde/po/.htaccess
deleted file mode 100644 (file)
index 3a42882..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Deny from all
diff --git a/horde/po/README b/horde/po/README
deleted file mode 100644 (file)
index 1f43253..0000000
+++ /dev/null
@@ -1,492 +0,0 @@
-===================
- Translation Guide
-===================
-
-:Authors:  Jan Schneider
-:Contact:  i18n@lists.horde.org
-
-.. contents::
-.. section-numbering::
-
-Translation Tool
-================
-
-``translation.php`` is a small PHP script that should help translators doing
-their work.
-
-Any feedback, bug reports and feature requests should be send to the `i18n
-mailing list`_. This is also the right place for new translations and general
-discussions of i18n and l10n questions and problems.
-
-``translation.php`` is located in ``horde/po``, so you will have to make
-``horde/po`` your working directory in order to run the commands quoted
-below. But you can run the script from any directory, just adapt the directory
-to the script. See _Prerequisites if your PHP binary is not located in
-``/usr/bin/php``.
-
-For a list of available commands run::
-
-  ./translation.php help
-
-For detailed help on a certain command run::
-
-  ./translation.php help command
-
-Additional information about creating translations and fixing problems can be
-found in the file ``horde/docs/TRANSLATION``.
-
-.. _i18n mailing list: http://www.horde.org/mail/
-
-Prerequisites
-~~~~~~~~~~~~~
-
-To run this script you'll need a PHP command line executable with `gettext
-support`_ compiled in, and the basic `PEAR`_ libraries. The script expects your
-PHP executable to be at ``/usr/bin/php``. If your executable is at another
-place, either edit the first line of ``translation.php`` to reflect your
-location or call the script like::
-
-  /usr/local/bin/php translation.php.
-
-You'll need the `gettext`_ package version 0.12 or greater.
-
-You'll need the PEAR packages `Console_Getopt`_ 0.11 or greater,
-`Console_Table`_ and `File_Find`_. To install all needed packages, run::
-
-  pear upgrade PEAR Console_Getopt
-  pear install Console_Table File_Find
-
-or download the newest package from the `PEAR`_ server and install them
-manually in your PEAR directory.
-
-.. _gettext support: http://www.php.net/gettext/
-.. _gettext: http://www.gnu.org/software/gettext/
-.. _PEAR: http://pear.php.net/
-.. _Console_Getopt: http://pear.php.net/package/Console_Getopt/
-.. _Console_Table: http://pear.php.net/package/Console_Table/
-.. _File_Find: http://pear.php.net/package/File_Find/
-
-Creating a new translation
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To create a new translation you first have to extract all gettext messages
-from the PHP sources. There are already template files with the ``.pot``
-suffix in the ``po`` directories that you can use if you have troubles
-extracting the messages, though these templates might be slightly
-outdated. Run::
-
-  ./translation.php extract
-
-You now have to create a new PO file for your locale. A locale has the form
-``ll_CC`` where ``ll`` is the two letter `ISO 639`_ code of the language and
-``CC`` the two letter `ISO 3166`_ code of the country, e.g. ``de_DE``,
-``en_US`` or ``pt_BR``.
-``translation.php`` is able to guess the locale from the ``LANG`` environment
-variable but it is safer to specify the locale with the ``-l`` parameter.
-To create the PO file run::
-
-  ./translation.php init -l ll_CC
-
-Now you can start the translation by editing the created ``ll_CC.po``
-files. It is important to set the correct charset for the locale in the
-``Content-Type:`` header.
-You should fill out the the complete header of the created ``ll_CC.po`` file,
-e.g.::
-
-  # Dutch translation for Horde.
-  # Copyright 2004-2010 The Horde Project (http://www.horde.org/)
-  # This file is distributed under the same license as the Horde package.
-  # Joris Braakman <jbraakman@yahoo.com>, 2004.
-  #
-  msgid ""
-  msgstr ""
-  "Project-Id-Version: Horde 2.3\n"
-  "Report-Msgid-Bugs-To: dev@lists.horde.org\n"
-  "POT-Creation-Date: 2004-04-14 10:30+0200\n"
-  "PO-Revision-Date: 2004-04-14 17:17+02:00\n"
-  "Last-Translator: Joris Braakman <jbraakman@yahoo.com>\n"
-  "Language-Team: i18n@lists.horde.org\n"
-  "MIME-Version: 1.0\n"
-  "Content-Type: text/plain; charset=ISO-8859-1\n"
-  "Content-Transfer-Encoding: 8-bit\n"
-
-To compile the translations to a binary format run::
-
-  ./translation.php make -l ll_CC
-
-After you created the new translation you have to add entries for this locale
-in the configuration file horde/config/nls.php.
-
-If you create a new translation for a certain module but there are already
-translations for the same language for other modules, then you should use a
-compendium. This will save you a lot of time and it will make translations
-much more consistent.
-
-.. _ISO 639: http://www.loc.gov/standards/iso639-2/
-.. _ISO 3166: http://www.iso.ch/iso/en/prods-services/iso3166ma/index.html
-
-Compendiums
-~~~~~~~~~~~
-
-This tool utilizes a nice feature of gettext: compendium files. A compendium
-is a special PO file containing a set of translations recurring in many
-different packages. These compendiums will be used in the background without
-much intervention required. But you have to create and edit a compendium
-before you can use it.
-
-To create a compendium of all existing translations for a certain locale run::
-
-  ./translation.php compendium -l ll_CC
-
-The ``compendium.po`` being created will contain all modules' translations of
-this locale merged into a single file. You should take a closer look at this
-file because you may find a lot of special marked lines where you translated
-certain strings differently in the various modules. It's a good idea to fix
-the modules' translations now so that all modules use the same translations for
-the same strings. You can always recreate your compendium with the above
-command.
-
-If you're maintaining translations for different `branches`_ and assumed that
-you have all modules of the ``HEAD`` branches in one directory and all of the
-``FRAMEWORK_3`` branches in another, you probably want to share a compendium
-between these directories.
-
-To do this, you should first create a compendium in the ``FRAMEWORK_3``
-branch, review it and fix all translations until you're happy with the
-results. Then create a second compendium in the ``HEAD`` branch and include
-your first one with the --add option.  Now fix the translations in this
-branch. If you're ready you can remove the first compendium and for now on use
-the compendium in the ``HEAD`` branch for both branches. To do so, use the
-``-c`` or ``--compendium`` option to specify a path to your compendium.
-
-.. _branches: http://www.horde.org/source/
-
-Updating translations
-~~~~~~~~~~~~~~~~~~~~~
-
-The process of updating translations is a cycle where you extract new gettext
-strings from the sources, translate those new strings or update the already
-translated strings and compile them after.
-
-To update the translation for a module, run::
-
-  ./translation.php update -m modulename -l ll_CC
-
-This extracts the new strings from the sources and tries to update them from
-already existing translations in the compendium. You just have to translate
-all untranslated strings in the ``ll_CC.po`` file in the ``po`` directory of
-the module you updated.
-
-If extracting new strings fails for some reason, you can use the provided
-``.pot`` file to update your translation::
-
-  ./translation.php merge -m modulename -l ll_CC
-
-If your compendium is in a different directory than the ``translation.php``
-script, you can specify the path to the compendium::
-
-  ./translation.php update -m modulename -l ll_CC -c /path/to/compendium
-
-Once this is done, you can compile the translation by calling::
-
-  ./translation.php make -m modulename -l ll_CC
-
-Extending existent translations
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To have your own string (e.g. that you added to config files) displayed in
-several languages, you have to
-
-  1. specify your texts as gettext arguments, in English,
-  2. edit the translation files for the required national languages,
-  3. compile those translation files.
-
-Be sure to keep records of your extensions, as you will probably have to
-repeat steps 2 and 3 after the next update.
-
-E.g., you plan to offer two IMAP servers to select from in the Webmail login
-screen:
-
-  1. In ``horde/imp/config/servers.php``, you specify::
-
-       $servers['Central'] = array(
-          'name' => _("Central Mail Service"),
-       ...
-       $servers['CompSci'] = array(
-          'name' => _("Mail Service of Computer Science Dpt.")
-       ...
-
-  2. In ``horde/imp/po/de_DE.po`` you add two entries::
-
-       msgid "Central Mail Service"
-       msgstr "Zentraler Mailserver"
-
-       msgid "Mail Service of Computer Science Dpt."
-       msgstr "Mailserver Informatik"
-
-     Likewise, you amend the translation files for other languages, as needed.
-
-  3. You compile the translations using the commands::
-
-       ./po/translation.php make --module imp --no-compendium
-
-Access Keys
-===========
-
-Access keys, also known as shortcut keys, allow easy access to important
-functions, normally by hitting the Alt/Meta key in combination with another
-key. This key is marked in most user interfaces by being underlined.
-
-As the access key is part of the word representing the action being executed,
-it is in the translators responsibility to select an access key when he
-translates these words. The action is always a link in Horde. The access key
-of a link is selected by prefixing it with an underscore.
-
-The help link in the menu for example is always "_Help". This means that the
-"H" of the link will be underlined and the help can be opened by hitting
-Alt+H. In the PO file this string will appear as::
-
-  #: templates/menu/menu.inc:53
-  msgid "_Help"
-  msgstr ""
-
-A Spanish translator might want to translate this to::
-
-  #: templates/menu/menu.inc:53
-  msgid "_Help"
-  msgstr "_Ayuda"
-
-Translators of multibyte languages need to do this a bit differently as only
-ASCII characters are allowed for access keys. A Traditional Chinese translator
-might want to use::
-
-  #: templates/menu/menu.inc:53
-  msgid "_Help"
-  msgstr "_H說明"
-
-This renders to "說明(H)" in the interface and you can
-access this link with "H" as the access key.
-
-
-Right-to-Left Languages
-=======================
-
-Translations for languages that are written from right to left might cause
-unexpected behavior if parenthesis or similar characters appear inside a
-translated string. To fix this broken string rendering you have to insert
-special Unicode codepoints into the translated string.
-
-Before such a string in parenthesis, add the U+202D codepoint. If there is
-more right-to-left text to come after the closing parenthesis, add the U+202E
-codepoint after it. If using the PO mode of the Emacs editor you can add
-codepoints with the "ucs-insert" command.
-
-
-Help Texts
-==========
-
-Organization of the help files
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The help texts are available in the ``horde/locale/``, and
-``horde/APP/locale/``, directories, where APP is any Horde application. Every
-available translation is kept in a file called ``help.xml``, in a subdirectory
-named according to RFC 3066. Examples:
-
-- Horde's original help texts are in the ``horde/locale/en_US/help.xml`` file.
-- IMP's Brazilian help texts are in the ``horde/imp/locale/pt_BR/help.xml``
-  file.
-
-When, for any desired application and locale, there is no ``help.xml`` file
-available, Horde's help system will use the application's
-``locale/en_US/help.xml`` file, instead.
-
-The help files must be encoded in the language's preferred character set.
-
-There is no compilation step involved: Every modification to, or addition of,
-a ``help.xml`` file takes immediate effect.
-
-Syntax of the help files
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-Each help file must consist of syntactically valid XML code.
-
-There are no predefined entities beyond the XML standard entities:
-
-- &lt;   (less than)
-- &gt;   (greater than)
-- &amp;  (ampersand)
-- &apos; (ASCII apostrophe)
-- &quot; (ASCII quotation mark)
-
-Any character available in the language's preferred character set can be
-entered as a numerical character reference (based on its Unicode scalar
-value), such as ``&#160;`` for the No-Break Space character.
-
-The general structure can be learned from the existing examples; of course,
-the XML tags must be syntactically valid, and properly nested.
-
-.. Note:: A PHP error message like ``Undefined index: url in
-          /opt/test-webmail/horde/lib/Horde/Help.php`` means that you have
-          mis-spelled, or omitted, an attribute (``url``, in this example).
-
-The following tags are available:
-
-+---------+--------+---------------+-------------------------------+
-| XML-Tag | Parent | Attributes    | Purpose                       |
-+=========+========+===============+===============================+
-| help    |   â€”    |       â€”       | List of help texts            |
-+---------+--------+---------------+-------------------------------+
-| entry   | help   | id            | Help text                     |
-+---------+--------+---------------+-------------------------------+
-| title   | entry  |       â€”       | Entry in the help index       |
-+---------+--------+---------------+-------------------------------+
-| heading | entry  |       â€”       | Level 2 heading               |
-+---------+--------+---------------+-------------------------------+
-| para    | entry  |       â€”       | Paragraph                     |
-+---------+--------+---------------+-------------------------------+
-| ref     | para   | module, entry | Link into the help system     |
-+---------+--------+---------------+-------------------------------+
-| eref    | para   | url           | External link                 |
-+---------+--------+---------------+-------------------------------+
-| href    | para   | app, url      | Link into a Horde application |
-+---------+--------+---------------+-------------------------------+
-| b       | para   |       â€”       | Bold text                     |
-+---------+--------+---------------+-------------------------------+
-| i       | para   |       â€”       | Italic text on new line       |
-+---------+--------+---------------+-------------------------------+
-| pre     | entry  |       â€”       | Example box                   |
-+---------+--------+---------------+-------------------------------+
-| tip     | entry  |       â€”       | Tip/hint box                  |
-+---------+--------+---------------+-------------------------------+
-| warn    | entry  |       â€”       | Warning box                   |
-+---------+--------+---------------+-------------------------------+
-
-The elements marked ``para``, in the Parent column, may also be used within
-``pre``, ``tip``, and ``warn`` elements.
-
-The following attributes are available:
-
-+---------+-----------+----------------------------------+
-| XML-Tag | Attribute | Purpose/Syntax                   |
-+=========+===========+==================================+
-| entry   | id        | Anchor                           |
-+---------+-----------+----------------------------------+
-| ref     | module    | Index of Horde application, see  |
-|         |           | ``horde/config/registry.php``    |
-|         +-----------+----------------------------------+
-|         | entry     | Id of help file entry, see above |
-+---------+-----------+----------------------------------+
-| eref    | url       | arbitrary URL                    |
-+---------+-----------+----------------------------------+
-| href    | app       | Index of Horde application, see  |
-|         |           | ``horde/config/registry.php``    |
-|         +-----------+----------------------------------+
-|         | url       | URL within Horde application     |
-+---------+-----------+----------------------------------+
-
-Examples::
-
-  <ref module="imp" entry="compose-attachments">selecting attachments</ref>
-  <eref url="http://wiki.horde.org/FAQ/User/IMP?referer=FAQ/User#toc17">
-        UW-IMAP quirk</eref>
-  <href app="turba" url="search.php">Address search</href>
-
-The `Translation Tool`_ will introduce additional attributes:
-
-+---------+-----------+--------------------------------+
-| XML-Tag | Attribute | Purpose/Syntax                 |
-+=========+===========+================================+
-| entry   | state     | See `Finishing a translation`_ |
-|         +-----------+--------------------------------+
-|         | md5       | Used internally                |
-+---------+-----------+--------------------------------+
-
-Reference from the Horde applications
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The typical incantation, e.g.::
-
-  <?php echo Horde_Help::link('imp', 'compose-buttons') ?>
-
-will yield a link, adorned with the ``help.png`` icon, to the entry marked
-``id="compose-buttons"`` in the IMP help file pertaining to the user's current
-locale. Usually, these links are placed in the template files, next to the
-item to be explained by the pertinent help entry.
-
-The Horde menu will usually contain a general help item, which is generated in
-``lib/Horde/Menu.php``.
-
-Creating a new translation
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If necessary, define a suitable locale id ``ll_CC``, complying with RFC
-3066. E.g., if you plan to add an Austrian translation, you would use
-``de_AT`` as your locale id.
-
-For Horde, and for all relevant applications, copy the ``help.xml`` files from
-the ``locale/en_US/`` subdirectories to the ``locale/ll_CC/`` subdirectories,
-creating them if necessary.
-
-Translate each new ``locale/ll_CC/help.xml`` file, leaving all tags and
-attributes unchanged. Just translate the text between the tags.
-
-.. Important:: The ``id`` attributes must be kept unchanged, under any
-               circumstances.
-
-If the application is already translated and only the help files are missing,
-then be sure to use the same terminology as the existing translation. In any
-case, try to use a lucid, coherent terminology.
-
-Finishing a translation
-~~~~~~~~~~~~~~~~~~~~~~~
-
-If you want to submit a translation to be included in the Horde code base,
-make sure that you have completed all translations. Then run::
-
-  ./translation.php make-help
-
-This will mark all entries as being up-to-date so that you or other
-translators can later see which entries have been changed since then.
-
-Updating an existing translation
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Run::
-
-  ./translation.php update-help
-
-This will merge your existing help file with all changes from the original,
-English help file since your last translation. Changed entries are marked with
-the attribute ``state="changed"``, new entries with ``state="new"``. The
-original entry from the English help file is added in a comment below a
-changed entry, so that you can easily compare them.
-
-Translate the remaining English phrases, like discussed above. When your
-finished, remove any English entries and follow the steps at `Finishing a
-translation`_.
-
-Extending existent translations
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you feel that some feature should be better explained to the end user, then
-proceed as following:
-
-1. Determine, whether you will have to extend an existing entry, or add a new
-   one.
-
-2. In the latter case, choose a suitable ``id`` for your new help entry.
-
-3. Edit the existing entry in, or add a new entry to, the application's
-   ``en_US/help.xml`` file. Change all translations needed in your site in the
-   same way. Be sure to use the same ``id`` everywhere.
-
-4. If you have added a new entry, you might want to add a link to it from a
-   template, as explained above in `Reference from the Horde applications`_.
-
-5. If you feel that your enhancement may be useful to other users, file an
-   enhancement request at http://bugs.horde.org/ with a patch including your
-   changes to the template and the ``help.xml`` files.
-
-6. In any case, keep notes of your changes, so you can apply them to later
-   versions if necessary.
diff --git a/horde/po/translation.php b/horde/po/translation.php
deleted file mode 100755 (executable)
index 857f47a..0000000
+++ /dev/null
@@ -1,1511 +0,0 @@
-#!/usr/bin/php -q
-<?php
-/**
- * Translation helper application for the Horde framework.
- *
- * For usage information call:
- * ./translation.php help
- */
-
-function footer()
-{
-    global $c, $curdir;
-
-    $c->writeln();
-    $c->writeln('Please report any bugs to i18n@lists.horde.org.');
-
-    chdir($curdir);
-    exit;
-}
-
-function usage()
-{
-    global $options, $c;
-
-    if (count($options[1]) &&
-        ($options[1][0] == 'help' && !empty($options[1][1]) ||
-        !empty($options[1][0]) && in_array($options[1][0], array('commit', 'compendium', 'extract', 'init', 'make', 'merge')))) {
-        if ($options[1][0] == 'help') {
-            $cmd = $options[1][1];
-        } else {
-            $cmd = $options[1][0];
-        }
-        $c->writeln('Usage:' . ' translation.php [options] ' . $cmd . ' [command-options]');
-        if (!empty($cmd)) {
-            $c->writeln();
-            $c->writeln('Command options:');
-        }
-        switch ($cmd) {
-        case 'cleanup':
-            $c->writeln('  -l, --locale=ll_CC     Use only this locale.');
-            $c->writeln('  -m, --module=MODULE    Cleanup PO files only for this (Horde) module.');
-            break;
-        case 'commit':
-        case 'commit-help':
-            $c->writeln('  -l, --locale=ll_CC     Use this locale.');
-            $c->writeln('  -m, --module=MODULE    Commit translations only for this (Horde) module.');
-            $c->writeln('  -M, --message=MESSAGE  Use this commit message instead of the default ones.');
-            $c->writeln('  -n, --new              This is a new translation, commit also CREDITS,');
-            $c->writeln('                         CHANGES and nls.php.');
-            $c->writeln('  -s, --skip             Skip all modules that are not maintained in CVS.');
-            break;
-        case 'compendium':
-            $c->writeln('  -a, --add=FILE        Add this PO file to the compendium. Useful to');
-            $c->writeln('                        include a compendium from a different branch to');
-            $c->writeln('                        the generated compendium.');
-            $c->writeln('  -d, --directory=DIR   Create compendium in this directory.');
-            $c->writeln('  -l, --locale=ll_CC    Use this locale.');
-            break;
-        case 'extract':
-            $c->writeln('  -m, --module=MODULE  Generate POT file only for this (Horde) module.');
-            break;
-        case 'init':
-            $c->writeln('  -l, --locale=ll_CC     Use this locale.');
-            $c->writeln('  -m, --module=MODULE    Create a PO file only for this (Horde) module.');
-            $c->writeln('  -c, --compendium=FILE  Use this compendium file instead of the default');
-            $c->writeln('                         one (compendium.po in the horde/po directory).');
-            $c->writeln('  -n, --no-compendium    Don\'t use a compendium.');
-            break;
-        case 'make':
-            $c->writeln('  -l, --locale=ll_CC     Use only this locale.');
-            $c->writeln('  -m, --module=MODULE    Build MO files only for this (Horde) module.');
-            $c->writeln('  -c, --compendium=FILE  Merge new translations to this compendium file');
-            $c->writeln('                         instead of the default one (compendium.po in the');
-            $c->writeln('                         horde/po directory.');
-            $c->writeln('  -n, --no-compendium    Don\'t merge new translations to the compendium.');
-            $c->writeln('  -s, --statistics       Save translation statistics in a local file.');
-            break;
-        case 'make-help':
-        case 'update-help':
-            $c->writeln('  -l, --locale=ll_CC     Use only this locale.');
-            $c->writeln('  -m, --module=MODULE    Update help files only for this (Horde) module.');
-            break;
-        case 'merge':
-            $c->writeln('  -l, --locale=ll_CC     Use this locale.');
-            $c->writeln('  -m, --module=MODULE    Merge PO files only for this (Horde) module.');
-            $c->writeln('  -c, --compendium=FILE  Use this compendium file instead of the default');
-            $c->writeln('                         one (compendium.po in the horde/po directory).');
-            $c->writeln('  -n, --no-compendium    Don\'t use a compendium.');
-            break;
-        case 'update':
-            $c->writeln('  -l, --locale=ll_CC     Use this locale.');
-            $c->writeln('  -m, --module=MODULE    Update only this (Horde) module.');
-            $c->writeln('  -c, --compendium=FILE  Use this compendium file instead of the default');
-            $c->writeln('                         one (compendium.po in the horde/po directory).');
-            $c->writeln('  -n, --no-compendium    Don\'t use a compendium.');
-            break;
-        }
-    } else {
-        $c->writeln('Usage:' . ' translation.php [options] command [command-options]');
-        $c->writeln(str_repeat(' ', Horde_String::length('Usage:')) . ' translation.php [help|-h|--help] [command]');
-        $c->writeln();
-        $c->writeln('Helper application to create and maintain translations for the Horde');
-        $c->writeln('framework and its applications.');
-        $c->writeln('For an introduction read the file README in this directory.');
-        $c->writeln();
-        $c->writeln('Commands:');
-        $c->writeln('  help        Show this help message.');
-        $c->writeln('  compendium  Rebuild the compendium file. Warning: This overwrites the');
-        $c->writeln('              current compendium.');
-        $c->writeln('  extract     Generate PO template (.pot) files.');
-        $c->writeln('  init        Create one or more PO files for a new locale. Warning: This');
-        $c->writeln('              overwrites the existing PO files of this locale.');
-        $c->writeln('  merge       Merge the current PO file with the current PO template file.');
-        $c->writeln('  update      Run extract and merge sequent.');
-        $c->writeln('  update-help Extract all new and changed entries from the English XML help');
-        $c->writeln('              file and merge them with the existing ones.');
-        $c->writeln('  cleanup     Cleans the PO files up from untranslated and obsolete entries.');
-        $c->writeln('  make        Build binary MO files from the specified PO files.');
-        $c->writeln('  make-help   Mark all entries in the XML help file being up-to-date and');
-        $c->writeln('              prepare the file for the next execution of update-help. You');
-        $c->writeln('              should only run make-help AFTER update-help and revising the');
-        $c->writeln('              help file.');
-        $c->writeln('  commit      Commit translations to the CVS server.');
-        $c->writeln('  commit-help Commit help files to the CVS server.');
-    }
-
-    $c->writeln();
-    $c->writeln('Options:');
-    $c->writeln('  -b, --base=/PATH  Full path to the (Horde) base directory that should be');
-    $c->writeln('                    used.');
-    $c->writeln('  -d, --debug       Show error messages from the executed binaries.');
-    $c->writeln('  -h, --help        Show this help message.');
-    $c->writeln('  -t, --test        Show the executed commands but don\'t run anything.');
-}
-
-function check_binaries()
-{
-    global $gettext_version, $c;
-
-    $c->writeln('Searching gettext binaries...');
-    foreach (array('gettext', 'msgattrib', 'msgcat', 'msgcomm', 'msgfmt', 'msginit', 'msgmerge', 'xgettext') as $binary) {
-        $GLOBALS[$binary] = System::which($binary);
-        if ($GLOBALS[$binary]) {
-            $c->message($binary . ' found: ' . $GLOBALS[$binary], 'cli.success');
-        } else {
-            $c->message($binary . ' not found', 'cli.error');
-            footer();
-        }
-    }
-    $c->writeln();
-
-    $out = '';
-    exec($GLOBALS['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) {
-        $GLOBALS['php_support'] = false;
-        $c->writeln();
-        $c->message($version_string, 'cli.warning');
-        $c->message('Your gettext version is too old and does not support PHP natively.', 'cli.warning');
-        $c->writeln('Not all strings will be extracted.');
-    } else {
-        $GLOBALS['php_support'] = true;
-        $c->message($version_string, 'cli.success');
-    }
-    $c->writeln();
-}
-
-function search_file($file, $dir = '.', $local = false)
-{
-    static $ff;
-    if (!isset($ff)) {
-        $ff = new File_Find();
-    }
-
-    if (substr($file, 0, 1) != DS) {
-        $file = "/$file/";
-    }
-
-    if ($local) {
-        $files = $ff->glob($file, $dir, 'perl');
-        $files = array_map(create_function('$file', 'return "' . $dir . DS . '" . $file;'), $files);
-        return $files;
-    } else {
-        return $ff->search($file, $dir, 'perl', false);
-    }
-}
-
-function search_ext($ext, $dir = '.', $local = false)
-{
-    return search_file("^[^.].*\\.$ext\$", $dir, $local);
-}
-
-function get_po_files($dir)
-{
-    $langs = 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;
-}
-
-function get_languages($dir)
-{
-    global $curdir;
-
-    chdir($dir);
-    $langs = get_po_files('locale');
-    $langs = array_map('basename', array_map('dirname', array_map('dirname', $langs)));
-    chdir($curdir);
-    return $langs;
-}
-
-function search_applications()
-{
-    $dirs = array();
-    if (is_dir(HORDE_BASE . DS . 'locale')) {
-        $dirs[] = HORDE_BASE;
-    }
-    $dh = opendir(BASE);
-    if ($dh) {
-        while ($entry = readdir($dh)) {
-            $dir = BASE . DS . $entry;
-            if (is_dir($dir) &&
-                substr($entry, 0, 1) != '.' &&
-                fileinode(HORDE_BASE) != fileinode($dir)) {
-                $sub = opendir($dir);
-                if ($sub) {
-                    while ($subentry = readdir($sub)) {
-                        if ($subentry == 'locale' && is_dir($dir . DS . $subentry)) {
-                            $dirs[] = $dir;
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    return $dirs;
-}
-
-function strip_horde($file)
-{
-    if (is_array($file)) {
-        return array_map('strip_horde', $file);
-    } else {
-        return str_replace(BASE . DS, '', $file);
-    }
-}
-
-function xtract()
-{
-    global $cmd_options, $apps, $dirs, $debug, $test, $c, $gettext_version, $silence, $curdir;
-
-    foreach ($cmd_options[0] as $option) {
-        switch ($option[0]) {
-        case 'h':
-            usage();
-            footer();
-        case 'm':
-        case '--module':
-            $module = $option[1];
-            break;
-        }
-    }
-
-    if ($GLOBALS['php_support']) {
-        $language = 'PHP';
-    } else {
-        $language = 'C++';
-    }
-    for ($i = 0; $i < count($dirs); $i++) {
-        if (!empty($module) && $module != $apps[$i]) {
-            continue;
-        }
-        printf('Extracting from %s... ', $apps[$i]);
-        chdir($dirs[$i]);
-        $files = array();
-        if ($apps[$i] == 'horde') {
-            $files = search_ext('(php|inc)', '../framework');
-            $files[] = 'config/nls.php';
-        }
-        $files = array_merge($files, search_ext('(php|inc)'));
-        $files = array_filter($files, create_function('$file', 'return substr($file, 0, 9) != "." . DS . "config" . DS;'));
-        $files = array_merge($files, search_ext('dist', 'config'));
-        $file = 'locale' . DS . $apps[$i] . '.pot';
-        file_put_contents($file . '.list', implode("\n", $files));
-        if (file_exists($file) && !is_writable($file)) {
-            $c->message(sprintf('%s is not writable.', $file), 'cli.error');
-            footer();
-        }
-        $tmp_file = $file . '.tmp.pot';
-        $sh = $GLOBALS['xgettext'] . ' --language=' . $language .
-            ' --from-code=iso-8859-1 --keyword=_ --sort-output --copyright-holder="Horde Project" --msgid-bugs-address="dev@lists.horde.org" --files-from=' . $file . '.list --output=' . $tmp_file;
-        if ($debug) {
-            $sh .= $silence;
-        }
-        if ($debug || $test) {
-            $c->writeln('Executing:');
-            $c->writeln($sh);
-            $c->writeln('In: ' . getcwd());
-        }
-        if (!$test) {
-            exec($sh);
-        }
-        unlink($file . '.list');
-        $diff = array();
-        if (file_exists($tmp_file)) {
-            $files = search_ext('html', 'templates');
-            if (!$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) {
-                                $c->message(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 (!$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);
-            }
-            if (!$test) fclose($tmp);
-            $sh = $GLOBALS['msgcomm'] . " --more-than=0 --sort-output \"$tmp_file\" \"$file.templates\" --output-file \"$tmp_file\"" . $silence;
-            if ($debug || $test) {
-                $c->writeln('Executing:');
-                $c->writeln($sh);
-            }
-            if (!$test) {
-                exec($sh);
-                unlink($file . '.templates');
-            }
-
-            /* Parse conf.xml files for <configphp> tags. */
-            if (file_exists('config/conf.xml')) {
-                if (!$test) $tmp = fopen($file . '.config', 'w');
-                $conf_content = file_get_contents('config/conf.xml');
-                if (preg_match_all('/<configphp .*?>([^<]*_\(".+?"\)[^<]*)<\/configphp>/s',
-                                   $conf_content, $matches)) {
-                    foreach ($matches[1] as $configphp) {
-                        if (preg_match_all('/_\("(.+?)"\)/', $configphp, $strings)) {
-                            if (!$test) {
-                                foreach ($strings[1] as $string) {
-                                    fwrite($tmp, "#: config/conf.xml\n");
-                                    fwrite($tmp, 'msgid "' . $string . "\"\n");
-                                    fwrite($tmp, 'msgstr ""' . "\n\n");
-                                }
-                            }
-                        }
-                    }
-                }
-                if (!$test) fclose($tmp);
-                $sh = $GLOBALS['msgcomm'] . " --more-than=0 --sort-output \"$tmp_file\" \"$file.config\" --output-file \"$tmp_file\"" . $silence;
-                if ($debug || $test) {
-                    $c->writeln('Executing:');
-                    $c->writeln($sh);
-                }
-                if (!$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:/', $diff, PREG_GREP_INVERT);
-            }
-        }
-        if (!file_exists($file) || count($diff)) {
-            if (file_exists($file)) {
-                unlink($file);
-            }
-            rename($tmp_file, $file);
-            $c->writeln($c->green('updated'));
-        } else {
-            if (file_exists($tmp_file)) {
-                unlink($tmp_file);
-            }
-            $c->writeln($c->bold('not changed'));
-        }
-        chdir($curdir);
-    }
-}
-
-function merge()
-{
-    global $cmd_options, $apps, $dirs, $debug, $test, $c;
-
-    $compendium = ' --compendium="' . HORDE_BASE . DS . 'locale' . DS . 'compendium.po"';
-    foreach ($cmd_options[0] as $option) {
-        switch ($option[0]) {
-        case 'h':
-            usage();
-            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;
-        }
-    }
-
-    cleanup();
-
-    for ($i = 0; $i < count($dirs); $i++) {
-        if (!empty($module) && $module != $apps[$i]) {
-            continue;
-        }
-        $c->writeln(sprintf('Merging translation for module %s...', $c->bold($apps[$i])));
-        $dir = $dirs[$i] . DS . 'locale' . DS;
-        $po = $dir . '%s' . DS . 'LC_MESSAGES' . DS . $apps[$i] . '.po';
-        if (empty($lang)) {
-            $langs = get_languages($dirs[$i]);
-        } else {
-            if (!file_exists(sprintf($po, $lang))) {
-                $c->writeln('Skipped...');
-                $c->writeln();
-                continue;
-            }
-            $langs = array($lang);
-        }
-        foreach ($langs as $locale) {
-            $c->writeln(sprintf('Merging locale %s... ', $c->bold($locale)));
-            $sh = $GLOBALS['msgmerge']
-                . sprintf(' --update -v%s "%s" "%s.pot"',
-                          $compendium, sprintf($po, $locale), $dir . $apps[$i]);
-            if ($debug || $test) {
-                $c->writeln('Executing:');
-                $c->writeln($sh);
-            }
-            if (!$test) exec($sh);
-            $c->writeln($c->green('done'));
-        }
-    }
-}
-
-function status()
-{
-    return;
-    global $cmd_options, $apps, $dirs, $debug, $test, $c;
-
-    $output = 'status.html';
-    foreach ($cmd_options[0] as $option) {
-        switch ($option[0]) {
-        case 'h':
-            usage();
-            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($dirs); $i++) {
-        if (!empty($module) && $module != $apps[$i]) {
-            continue;
-        }
-        $c->writeln(sprintf('Generating status for module %s...', $c->bold($apps[$i])));
-        if (empty($lang)) {
-            $langs = get_languages($dirs[$i]);
-        } else {
-            if (!file_exists($dirs[$i] . '/locale/' . $lang . '/LC_MESSAGES/' . $apps[$i] . '.po')) {
-                $c->writeln('Skipped...');
-                $c->writeln();
-                continue;
-            }
-            $langs = array($lang);
-        }
-        foreach ($langs as $locale) {
-            $c->writeln(sprintf('Status for locale %s... ', $c->bold($locale)));
-        }
-    }
-}
-
-function compendium()
-{
-    global $cmd_options, $dirs, $debug, $test, $c, $silence;
-
-    $dir = HORDE_BASE . DS . 'locale' . DS;
-    $add = '';
-    foreach ($cmd_options[0] as $option) {
-        switch ($option[0]) {
-        case 'h':
-            usage();
-            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)) {
-        $c->message('No locale specified.', 'cli.error');
-        $c->writeln();
-        usage();
-        footer();
-    }
-    printf('Merging all %s.po files to the compendium... ', $lang);
-    $pofiles = array();
-    for ($i = 0; $i < count($dirs); $i++) {
-        $pofile = $dirs[$i] . DS . 'locale' . DS . $lang . DS . 'LC_MESSAGES' . DS . $apps[$i] . '.po';
-        if (file_exists($pofile)) {
-            $pofiles[] = $pofile;
-        }
-    }
-    if (!empty($dir) && substr($dir, -1) != DS) {
-        $dir .= DS;
-    }
-    $sh = $GLOBALS['msgcat'] . ' --sort-output ' . implode(' ', $pofiles) . $add . ' > ' . $dir . 'compendium.po ' . ($debug ? '' : $silence);
-    if ($debug || $test) {
-        $c->writeln();
-        $c->writeln('Executing:');
-        $c->writeln($sh);
-    }
-    if ($test) {
-        $ret = 0;
-    } else {
-        exec($sh, $out, $ret);
-    }
-    if ($ret == 0) {
-        $c->writeln($c->green('done'));
-    } else {
-        $c->writeln($c->red('failed'));
-    }
-}
-
-function init()
-{
-    global $cmd_options, $apps, $dirs, $debug, $test, $c, $silence;
-
-    foreach ($cmd_options[0] as $option) {
-        switch ($option[0]) {
-        case 'h':
-            usage();
-            footer();
-        case 'l':
-        case '--locale':
-            $lang = $option[1];
-            break;
-        case 'm':
-        case '--module':
-            $module = $option[1];
-            break;
-        }
-    }
-    if (empty($lang)) {
-        $lang = getenv('LANG');
-    }
-
-    $registry = new Horde_Registry(Horde_Registry::SESSION_NONE);
-
-    for ($i = 0; $i < count($dirs); $i++) {
-        if (!empty($module) && $module != $apps[$i]) {
-            continue;
-        }
-        $package = ucfirst($apps[$i]);
-        $version = $registry->getVersion($apps[$i]);
-        printf('Initializing module %s... ', $apps[$i]);
-        $dir = $dirs[$i] . DS . 'locale' . DS;
-        $pot = $dir . $apps[$i] . '.pot';
-        if (!file_exists($pot)) {
-            $c->writeln();
-            $c->message(sprintf('%s not found. Run \'translation extract\' first.', $pot), 'cli.warning');
-            continue;
-        }
-        $sh = $GLOBALS['msginit'] . ' --no-translator -i ' . $pot;
-        if (!empty($lang)) {
-            $sh .= ' -o ' . $dir . $lang . '.po --locale=' . $lang;
-        }
-        if (!$debug) {
-            $sh .= $silence;
-        }
-        if (!empty($lang) && !OS_WINDOWS) {
-            $pofile = $dirs[$i] . '/po/' . $lang . '.po';
-            $sh .= "; sed 's/PACKAGE package/$package package/' $pofile " .
-                   "| sed 's/PACKAGE VERSION/$package $version/' " .
-                   "| sed 's/messages for PACKAGE/messages for $package/' " .
-                   "| sed 's/Language-Team: none/Language-Team: i18n@lists.horde.org/' " .
-                   "> $pofile.tmp";
-        }
-        if ($debug || $test) {
-            $c->writeln();
-            $c->writeln('Executing:');
-            $c->writeln($sh);
-        }
-        if ($test) {
-            $ret = 0;
-        } else {
-            exec($sh, $out, $ret);
-        }
-        rename($pofile . '.tmp', $pofile);
-        if ($ret == 0) {
-            $c->writeln($c->green('done'));
-        } else {
-            $c->writeln($c->red('failed'));
-        }
-    }
-}
-
-function make()
-{
-    global $cmd_options, $apps, $dirs, $debug, $test, $c, $silence, $redir_err;
-
-    $compendium = HORDE_BASE . DS . 'locale' . DS . 'compendium.po';
-    $save_stats = false;
-    foreach ($cmd_options[0] as $option) {
-        switch ($option[0]) {
-        case 'h':
-            usage();
-            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', $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($dirs); $i++) {
-        if (!empty($module) && $module != $apps[$i]) {
-            continue;
-        }
-        $c->writeln(sprintf('Building MO files for module %s...', $c->bold($apps[$i])));
-        $dir = $dirs[$i] . DS . 'locale' . DS . '%s' . DS . 'LC_MESSAGES' . DS;
-        if (empty($lang)) {
-            $langs = get_languages($dirs[$i]);
-        } else {
-            if (!file_exists(sprintf($dir, $lang) . $apps[$i] . '.po')) {
-                $c->writeln('Skipped...');
-                $c->writeln();
-                continue;
-            }
-            $langs = array($lang);
-        }
-        foreach ($langs as $locale) {
-            $c->writeln(sprintf('Building locale %s...', $c->bold($locale)));
-            $targetdir = sprintf($dir, $locale);
-            $pofile = $targetdir . $apps[$i] . '.po';
-            if (!is_dir($targetdir)) {
-                if ($debug) {
-                    $c->writeln(sprintf('Making directory %s', $targetdir));
-                }
-                if (!$test && !System::mkdir("-p $targetdir")) {
-                    $c->message(sprintf('Could not create locale directory for locale %s:', $locale), 'cli.warning');
-                    $c->writeln($targetdir);
-                    $c->writeln();
-                    continue;
-                }
-            }
-
-            /* 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 = $GLOBALS['msgfmt'] . " --check --output-file=/dev/null \"$pofile\" $redir_err";
-            if ($debug || $test) {
-                $c->writeln('Executing:');
-                $c->writeln($sh);
-            }
-            if ($test) {
-                $ret = 0;
-            } else {
-                exec($sh, $out, $ret);
-            }
-            if ($ret != 0) {
-                $c->message('An error has occured:', 'cli.warning');
-                $c->writeln(implode("\n", $out));
-                $c->writeln();
-                if ($apps[$i] == 'horde') {
-                    continue 2;
-                }
-                continue;
-            }
-
-            /* Compile MO file. */
-            $sh = $GLOBALS['msgfmt'] . ' --statistics -o "' . $targetdir . DS . $apps[$i] . '.mo" ';
-            if ($apps[$i] != 'horde') {
-                $horde_po = $dirs[$horde] . DS . 'po' . DS . $locale . '.po';
-                if (!is_readable($horde_po)) {
-                    $c->message(sprintf('The Horde PO file for the locale %s does not exist:', $locale), 'cli.warning');
-                    $c->writeln($horde_po);
-                    $c->writeln();
-                    $sh .= '"' . $dirs[$i] . DS . 'po' . DS . $locale . '.po"';
-                } else {
-                    $comm = $GLOBALS['msgcomm'] . " --more-than=0 --sort-output \"$pofile\"";
-                    $sh = $comm . " \"$horde_po\" | $sh -";
-                }
-            } else {
-                $sh .= '"' . $pofile . '"';
-            }
-            $sh .= $redir_err;
-            if ($debug || $test) {
-                $c->writeln('Executing:');
-                $c->writeln($sh);
-            }
-            $out = '';
-            if ($test) {
-                $ret = 0;
-            } else {
-                putenv('LANG=en');
-                exec($sh, $out, $ret);
-                putenv('LANG=' . $GLOBALS['language']);
-            }
-            if ($ret == 0) {
-                $c->writeln($c->green('done'));
-                $messages = array(0, 0, 0, $last_update);
-                if (preg_match('/(\d+) translated/', $out[0], $match)) {
-                    $messages[0] = $match[1];
-                    if (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 (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 (isset($horde_msg[$locale])) {
-                        $messages[2] -= $horde_msg[$locale][2];
-                        if ($messages[2] < 0) $messages[2] = 0;
-                    }
-                }
-                if ($apps[$i] == 'horde') {
-                    $horde_msg[$locale] = $messages;
-                }
-                $stats_array[$apps[$i]][$locale] = $messages;
-                $stats->addRow(array($apps[$i], $locale, $messages[0], $messages[1], $messages[2], $messages[3]));
-            } else {
-                $c->writeln($c->red('failed'));
-                exec($sh, $out, $ret);
-                $c->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... ', $c->bold($apps[$i]));
-                if (!empty($targetdir) && substr($targetdir, -1) != DS) {
-                    $targetdir .= DS;
-                }
-                $sh = $GLOBALS['msgcat'] . " --sort-output \"$compendium\" \"$pofile\" > \"$compendium.tmp\"";
-                if (!$debug) {
-                    $sh .= $silence;
-                }
-                if ($debug || $test) {
-                    $c->writeln();
-                    $c->writeln('Executing:');
-                    $c->writeln($sh);
-                }
-                $out = '';
-                if ($test) {
-                    $ret = 0;
-                } else {
-                    exec($sh, $out, $ret);
-                }
-                unlink($compendium);
-                rename($compendium . '.tmp', $compendium);
-                if ($ret == 0) {
-                    $c->writeln($c->green('done'));
-                } else {
-                    $c->writeln($c->red('failed'));
-                }
-            }
-            $c->writeln();
-        }
-    }
-    if (empty($module)) {
-        $c->writeln('Results:');
-    } else {
-        $c->writeln('Results (including Horde):');
-    }
-    $c->writeln($stats->getTable());
-    if ($save_stats) {
-        $fp = fopen('translation_stats.txt', 'w');
-        if ($fp) {
-            fwrite($fp, serialize($stats_array));
-            fclose($fp);
-        }
-    }
-}
-
-function cleanup($keep_untranslated = false)
-{
-    global $cmd_options, $apps, $dirs, $debug, $test, $c, $silence;
-
-    foreach ($cmd_options[0] as $option) {
-        switch ($option[0]) {
-        case 'h':
-            usage();
-            footer();
-        case 'l':
-        case '--locale':
-            $lang = $option[1];
-            break;
-        case 'm':
-        case '--module':
-            $module = $option[1];
-            break;
-        }
-    }
-
-    for ($i = 0; $i < count($dirs); $i++) {
-        if (!empty($module) && $module != $apps[$i]) {
-            continue;
-        }
-        $c->writeln(sprintf('Cleaning up PO files for module %s...', $c->bold($apps[$i])));
-        $po = $dirs[$i] . DS . 'locale' . DS . '%s' . DS . 'LC_MESSAGES' .DS . $apps[$i] . '.po';
-        if (empty($lang)) {
-            $langs = get_languages($dirs[$i]);
-        } else {
-            if (!file_exists(sprintf($po, $lang))) {
-                $c->writeln('Skipped...');
-                $c->writeln();
-                continue;
-            }
-            $langs = array($lang);
-        }
-        foreach ($langs as $locale) {
-            $c->writeln(sprintf('Cleaning up locale %s... ', $c->bold($locale)));
-            $pofile = sprintf($po, $locale);
-            $sh = $GLOBALS['msgattrib'] . ($keep_untranslated ? '' : ' --translated') . " --no-obsolete --force-po \"$pofile\" > \"$pofile.tmp\"";
-            if (!$debug) {
-                $sh .= $silence;
-            }
-            if ($debug || $test) {
-                $c->writeln();
-                $c->writeln('Executing:');
-                $c->writeln($sh);
-            }
-            $out = '';
-            if ($test) {
-                $ret = 0;
-            } else {
-                exec($sh, $out, $ret);
-            }
-            if ($ret == 0) {
-                unlink($pofile);
-                rename($pofile . '.tmp', $pofile);
-                $c->writeln($c->green('done'));
-            } else {
-                unlink($pofile . '.tmp', $pofile);
-                $c->writeln($c->red('failed'));
-            }
-            $c->writeln();
-        }
-    }
-}
-
-function commit($help_only = false)
-{
-    global $cmd_options, $apps, $dirs, $debug, $test, $c;
-
-    $docs = $skip = $lang = false;
-    $git = is_dir(BASE . '/.git');
-    foreach ($cmd_options[0] as $option) {
-        switch ($option[0]) {
-        case 'h':
-            usage();
-            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;
-        case 's':
-        case '--skip':
-            $skip = true;
-            break;
-        }
-    }
-    $files = array();
-    for ($i = 0; $i < count($dirs); $i++) {
-        if ((!empty($module) && $module != $apps[$i]) ||
-            (!$git && $skip && !is_dir($dirs[$i] . DS . 'CVS'))) {
-            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, strip_horde(search_ext('xml', $dirs[$i] . DS . 'locale')));
-            } else {
-                $files = array_merge($files, strip_horde(get_po_files($dirs[$i] . DS . 'po')));
-                $files = array_merge($files, strip_horde(search_file('^[a-z]{2}_[A-Z]{2}', $dirs[$i] . DS . 'locale', true)));
-            }
-        } else {
-            if ($help_only &&
-                !file_exists($dirs[$i] . DS . 'locale' . DS . $lang . DS . 'help.xml')) {
-                continue;
-            }
-            $files[] = strip_horde($dirs[$i] . DS . 'locale' . DS . $lang);
-        }
-        if ($docs && !$help_only && $apps[$i]) {
-            $files[] = strip_horde($dirs[$i] . DS . 'docs');
-            if ($apps[$i] == 'horde') {
-                $horde_conf = $dirs[array_search('horde', $dirs)] . DS . 'config' . DS;
-                $files[] = strip_horde($horde_conf . 'nls.php');
-            }
-        }
-    }
-    chdir(BASE);
-    if (count($files)) {
-        if ($docs) {
-            $c->writeln('Adding new files to repository:');
-            $add_files = array();
-            foreach ($files as $file) {
-                if (strstr($file, 'locale')) {
-                    $add_files[] = $file;
-                    $c->writeln($file);
-                }
-            }
-            foreach ($files as $file) {
-                if (strstr($file, 'locale')) {
-                    if ($help_only) {
-                        $add_files[] = $file . DS . '*.xml';
-                        $c->writeln($file . DS . '*.xml');
-                    } else {
-                        $add_files[] = $file . DS . '*.xml ' . $file . DS . 'LC_MESSAGES';
-                        $c->writeln($file . DS . "*.xml\n$file" . DS . 'LC_MESSAGES');
-                    }
-                }
-            }
-            if (!$help_only) {
-                foreach ($files as $file) {
-                    if (strstr($file, 'locale')) {
-                        $c->writeln($add_files[] = $file . DS . 'LC_MESSAGES' . DS . '*.po');
-                        $c->writeln($add_files[] = $file . DS . 'LC_MESSAGES' . DS . '*.mo');
-                    }
-                }
-            }
-            $c->writeln();
-            if ($git) {
-                if ($debug || $test) {
-                    $c->writeln('Executing:');
-                    $c->writeln('git add ' . implode(' ', $add_files));
-                }
-                if (!$test) {
-                    system('git add ' . implode(' ', $add_files));
-                }
-            } else {
-                foreach ($add_files as $add_file) {
-                    if ($debug || $test) {
-                        $c->writeln('Executing:');
-                        $c->writeln('cvs add ' . $add_file);
-                    }
-                    if (!$test) {
-                        system('cvs add ' . $add_file);
-                    }
-                }
-            }
-            $c->writeln();
-        }
-        $c->writeln('Committing:');
-        $c->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.";
-            }
-        }
-        if ($git) {
-            $sh = 'git add ' . implode(' ', $files) . '; git commit -m "' . $msg . '"';
-        } else {
-            $sh = 'cvs commit -m "' . $msg . '" ' . implode(' ', $files);
-        }
-        if ($debug || $test) {
-            $c->writeln('Executing:');
-            $c->writeln($sh);
-        }
-        if (!$test) system($sh);
-        if ($git) {
-            $c->message('You have to push the commit manually!', 'cli.warning');
-        }
-    }
-}
-
-function update_help()
-{
-    global $cmd_options, $dirs, $apps, $debug, $test, $last_error_msg, $c;
-
-    foreach ($cmd_options[0] as $option) {
-        switch ($option[0]) {
-        case 'h':
-            usage();
-            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 = 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_US' . DS . 'help.xml';
-        if (!file_exists($file_en)) {
-            $c->message(sprintf('There doesn\'t yet exist a help file for %s.', $c->bold($apps[$i])), 'cli.warning');
-            $c->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_US') {
-                continue;
-            }
-            if (!file_exists($file_loc)) {
-                $c->message(sprintf('The %s help file for %s doesn\'t yet exist. Creating a new one.', $c->bold($locale), $c->bold($apps[$i])), 'cli.warning');
-                $dir_loc = substr($file_loc, 0, -9);
-                if (!is_dir($dir_loc)) {
-                    if ($debug || $test) {
-                        $c->writeln(sprintf('Making directory %s', $dir_loc));
-                    }
-                    if (!$test && !System::mkdir("-p $dir_loc")) {
-                        $c->message(sprintf('Could not create locale directory for locale %s:', $locale), 'cli.warning');
-                        $c->writeln($dir_loc);
-                        $c->writeln();
-                        continue;
-                    }
-                }
-                if ($debug || $test) {
-                    $c->writeln(wordwrap(sprintf('Copying %s to %s', $file_en, $file_loc)));
-                }
-                if (!$test && !copy($file_en, $file_loc)) {
-                    $c->message(sprintf('Could not copy %s to %s', $file_en, $file_loc), 'cli.warning');
-                }
-                $c->writeln();
-                continue;
-            }
-            $c->writeln(sprintf('Updating %s help file for %s.', $c->bold($locale), $c->bold($apps[$i])));
-
-            if (!($doc_en = DOMDocument::load($file_en))) {
-                $c->message(sprintf('There was an error opening the file %s. Try running translation.php with the flag -d to see any error messages from the xml parser.', $file_en), 'cli.warning');
-                $c->writeln();
-                continue 2;
-            }
-            $doc_en->encoding = 'UTF-8';
-            $doc_en->formatOutput = true;
-
-            if (!($doc_loc = DOMDocument::load($file_loc))) {
-                $c->message(sprintf('There was an error opening the file %s. Try running translation.php with the flag -d to see any error messages from the xml parser.', $file_loc), 'cli.warning');
-                $c->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) {
-                $list = $xpath->query('//entry[@id="' . $entry->getAttribute('id') . '"]');
-                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('--', '&#45;&#45;', $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('--', '&#45;&#45;', $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);
-            }
-            $c->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 ($debug || $test) {
-                $c->writeln(wordwrap(sprintf('Writing updated help file to %s.', $file_loc)));
-            }
-            if (!$test) {
-                $doc_en->save($file_loc);
-            }
-            $c->writeln();
-        }
-    }
-}
-
-function make_help()
-{
-    global $cmd_options, $dirs, $apps, $debug, $test, $c;
-
-    foreach ($cmd_options[0] as $option) {
-        switch ($option[0]) {
-        case 'h':
-            usage();
-            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 = 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_US' . DS . 'help.xml';
-        if (!file_exists($file_en)) {
-            continue;
-        }
-
-        if (!($doc_en = DOMDocument::load($file_en))) {
-            $c->message(sprintf('There was an error opening the file %s. Try running translation.php with the flag -d to see any error messages from the xml parser.', $file_en), 'cli.warning');
-            $c->writeln();
-            continue;
-        }
-        $xpath = new DOMXPath($doc_en);
-
-        foreach ($files as $file_loc) {
-            if (!file_exists($file_loc)) {
-                $c->writeln('Skipped...');
-                $c->writeln();
-                continue;
-            }
-            $locale = substr($file_loc, 0, strrpos($file_loc, DS));
-            $locale = substr($locale, strrpos($locale, DS) + 1);
-            if ($locale == 'en_US') continue;
-            $c->writeln(sprintf('Updating %s help file for %s.', $c->bold($locale), $c->bold($apps[$i])));
-
-            if (!($doc_loc = DOMDocument::load($file_loc))) {
-                $c->message(sprintf('There was an error opening the file %s. Try running translation.php with the flag -d to see any error messages from the xml parser.', $file_loc), 'cli.warning');
-                $c->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++;
-                $list = $xpath->query('//entry[@id="' . $entry->getAttribute('id') . '"]');
-                if ($list->length) {
-                    $entry->setAttribute('md5', md5($list->item(0)->textContent));
-                    $entry->setAttribute('state', 'uptodate');
-                    $count++;
-                } else {
-                    $c->message(sprintf('No entry with the id "%s" exists in the original help file.', $entry->getAttribute('id')), 'cli.warning');
-                }
-            }
-
-            if (!$test) {
-                $doc_loc->save($file_loc);
-            }
-            $c->writeln(sprintf('%d of %d entries marked as up-to-date', $count, $count_all));
-            $c->writeln();
-        }
-    }
-}
-
-$curdir = getcwd();
-define('DS', DIRECTORY_SEPARATOR);
-
-$language = getenv('LANG');
-if (empty($language)) {
-    $language = getenv('LANGUAGE');
-}
-
-require_once dirname(__FILE__) . '/../lib/Application.php';
-
-$c = new Horde_Cli();
-if (!$c->runningFromCLI()) {
-    $c->fatal('This script must be run from the command line.');
-}
-$c->init();
-
-$c->writeln($c->bold('---------------------------'));
-$c->writeln($c->bold('Horde translation generator'));
-$c->writeln($c->bold('---------------------------'));
-
-/* Sanity checks */
-if (!extension_loaded('gettext')) {
-    $c->message('Gettext extension not found!', 'cli.error');
-    footer();
-}
-
-$c->writeln('Loading libraries...');
-$libs_found = true;
-
-foreach (array('Console_Getopt' => 'Console/Getopt.php',
-               'Console_Table'  => 'Console/Table.php',
-               'File_Find'      => 'File/Find.php')
-         as $class => $file) {
-    include $file;
-    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'));
-    footer();
-}
-$c->writeln();
-
-/* 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();
-    usage();
-    footer();
-}
-if (empty($options[0][0]) && empty($options[1][0])) {
-    $c->message('No command specified.', 'cli.error');
-    $c->writeln();
-    usage();
-    footer();
-}
-$debug = false;
-$test  = false;
-foreach ($options[0] as $option) {
-    switch ($option[0]) {
-    case 'b':
-    case '--base':
-        define('BASE', realpath($option[1]));
-        break;
-    case 'd':
-    case '--debug':
-        $debug = true;
-        break;
-    case 't':
-    case '--test':
-        $test = true;
-        break;
-    case 'h':
-    case '--help':
-        usage();
-        footer();
-    }
-}
-if (!$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') {
-    usage();
-    footer();
-}
-$silence = $debug || OS_WINDOWS ? '' : ' 2> /dev/null';
-$redir_err = OS_WINDOWS ? '' : ' 2>&1';
-$options_list = array(
-    'cleanup'    => array('hl:m:', array('module=', 'locale=')),
-    'commit'     => array('hl:m:nM:s', array('module=', 'locale=', 'new', 'message=', 'skip')),
-    '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();
-        usage();
-        footer();
-    }
-}
-
-/* Searching applications */
-check_binaries();
-
-$c->writeln(sprintf('Searching Horde applications in %s', BASE));
-$dirs = search_applications();
-
-if ($debug) {
-    $c->writeln('Found directories:');
-    $c->writeln(implode("\n", $dirs));
-}
-
-$apps = strip_horde($dirs);
-$apps[0] = 'horde';
-$c->writeln(wordwrap(sprintf('Found applications: %s', implode(', ', $apps))));
-$c->writeln();
-
-switch ($cmd) {
-case 'cleanup':
-case 'commit':
-case 'compendium':
-case 'merge':
-    $cmd();
-    break;
-case 'commit-help':
-    commit(true);
-    break;
-case 'extract':
-    xtract();
-    break;
-case 'init':
-    init();
-    $c->writeln();
-    merge();
-    break;
-case 'make':
-    cleanup(true);
-    $c->writeln();
-    make();
-    break;
-case 'make-help':
-    make_help();
-    break;
-case 'update':
-    xtract();
-    $c->writeln();
-    merge();
-    break;
-case 'update-help':
-    update_help();
-    break;
-case 'status':
-    merge();
-    break;
-default:
-    $c->message(sprintf('Unknown command: %s', $cmd), 'cli.error');
-    $c->writeln();
-    usage();
-    footer();
-}
-
-footer();
diff --git a/imp/po/.htaccess b/imp/po/.htaccess
deleted file mode 100644 (file)
index 3a42882..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Deny from all
diff --git a/imp/po/README b/imp/po/README
deleted file mode 100644 (file)
index a4356a9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
\ No newline at end of file
diff --git a/ingo/locale/.htaccess b/ingo/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/ingo/po/README b/ingo/po/README
deleted file mode 100644 (file)
index a985e94..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
diff --git a/jeta/locale/.htaccess b/jeta/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/jeta/po/README b/jeta/po/README
deleted file mode 100644 (file)
index a4356a9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
\ No newline at end of file
diff --git a/jonah/po/.htaccess b/jonah/po/.htaccess
deleted file mode 100644 (file)
index 3a42882..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Deny from all
diff --git a/jonah/po/README b/jonah/po/README
deleted file mode 100644 (file)
index a4356a9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
\ No newline at end of file
diff --git a/kastalia/locale/.htaccess b/kastalia/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/kastalia/po/README b/kastalia/po/README
deleted file mode 100644 (file)
index a985e94..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
diff --git a/kronolith/locale/.htaccess b/kronolith/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/kronolith/po/README b/kronolith/po/README
deleted file mode 100644 (file)
index a4356a9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
\ No newline at end of file
diff --git a/luxor/locale/.htaccess b/luxor/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/luxor/po/.htaccess b/luxor/po/.htaccess
deleted file mode 100644 (file)
index 3a42882..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Deny from all
diff --git a/luxor/po/README b/luxor/po/README
deleted file mode 100644 (file)
index a985e94..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
diff --git a/mnemo/locale/.htaccess b/mnemo/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/mnemo/po/README b/mnemo/po/README
deleted file mode 100644 (file)
index a4356a9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
\ No newline at end of file
diff --git a/nag/locale/.htaccess b/nag/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/nag/po/README b/nag/po/README
deleted file mode 100644 (file)
index a4356a9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
\ No newline at end of file
diff --git a/news/locale/.htaccess b/news/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/operator/po/README b/operator/po/README
deleted file mode 100644 (file)
index a985e94..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
diff --git a/passwd/locale/.htaccess b/passwd/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/passwd/po/.htaccess b/passwd/po/.htaccess
deleted file mode 100644 (file)
index 3a42882..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Deny from all
diff --git a/passwd/po/README b/passwd/po/README
deleted file mode 100644 (file)
index a4356a9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
\ No newline at end of file
diff --git a/pastie/po/README b/pastie/po/README
deleted file mode 100644 (file)
index a985e94..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
diff --git a/skeleton/locale/.htaccess b/skeleton/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/skeleton/po/README b/skeleton/po/README
deleted file mode 100644 (file)
index a985e94..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
diff --git a/skoli/locale/.htaccess b/skoli/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/skoli/po/README b/skoli/po/README
deleted file mode 100644 (file)
index a985e94..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
diff --git a/timeobjects/locale/.htaccess b/timeobjects/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/trean/locale/.htaccess b/trean/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/turba/po/.htaccess b/turba/po/.htaccess
deleted file mode 100644 (file)
index 3a42882..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Deny from all
diff --git a/turba/po/README b/turba/po/README
deleted file mode 100644 (file)
index a4356a9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
\ No newline at end of file
diff --git a/vilma/locale/.htaccess b/vilma/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/vilma/po/README b/vilma/po/README
deleted file mode 100644 (file)
index a985e94..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
diff --git a/whups/locale/.htaccess b/whups/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/whups/po/README b/whups/po/README
deleted file mode 100644 (file)
index a4356a9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README
\ No newline at end of file
diff --git a/wicked/locale/.htaccess b/wicked/locale/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/wicked/po/README b/wicked/po/README
deleted file mode 100644 (file)
index a985e94..0000000
+++ /dev/null
@@ -1 +0,0 @@
-see horde/po/README