<?php
/**
 * @version		0.9
 * @author      Carlos Souza
 * @copyright   Copyright (c) 2005 Carlos Souza <csouza@web-sense.net>
 * @package     PHPGettext
 * @license		MIT License (http://www.opensource.org/licenses/mit-license.php)
 * @link		http://phpgettext.web-sense.net
 * 
 * 
 */
class PHPGettext
{
    var $has_gettext;

    /**
     * The current locale. eg: en-GB
     */
    var $lang;
    /**
     * The current locale. eg: en-GB
     */
    var $locale;

    /**
     * The current domain
     */
    var $domain;

    /**
     * The current character set
     */
    var $charset;

    /**
     * Container for the loaded domains
     */
    var $text_domains = array();

    /**
     * The asssociative array of headers for the current domain
     */
    var $headers = array();
    /**
     * The asssociative array of messages for the current domain
     */
    var $messages = array();

    /**
     * The debugging flag
     */
    var $debug;

    function PHPGettext() {}

    /**
     * 
     * Set and lookup the locale from the environment variables.
     * Priority order for gettext is:
     * 1. LANGUAGE
     * 2. LC_ALL
     * 3. LC_MESSAGE
     * 4. LANG      
     *
     * @return unknown
     */

    function setlocale($lang, $locale=false) {
        #dump(setlocale(LC_ALL, 0));
        putenv("LANGUAGE=$lang");
        putenv("LC_ALL=$lang");
        putenv("LC_MESSAGE=$lang");
        putenv("LANG=$lang");
        $this->lang =  $lang;
        if ($locale) {
            $this->locale =  setlocale(LC_ALL, $locale);
            setlocale(LC_ALL, $locale);
        }
    }

    function getlocale() {
        if (empty($this->locale)) {
            $langs = array( getenv('LANGUAGE'),
            getenv('LC_ALL'),
            getenv('LC_MESSAGE'),
            getenv('LANG')
            );
            foreach ($langs as $lang)
            if ($lang){
                $this->locale = $lang;
                break;
            }
        }
        return $this->locale;
    }

    /**
     * debugging function
     * 
     */
    function output($message, $untranslated = false){
        switch ($this->debug)
        {
            case 2:
            $trace = debug_backtrace();
            $html = '<span style="border-bottom: thin solid %s" title="%s(%d)">T_(%s)</span>';
            $str = sprintf($html, ($untranslated ? 'red' : 'green'), str_replace('\\', '/', $trace[2]['file']), $trace[2]['line'], $message);
            break;
            case 1:
            $str    = sprintf('%sT_(%s)',$untranslated ? '!' : '', $message);
            break;
            case 0:
            default:
            $str    = $message;
            break;
        }
        return $str;
    }

    /**
     * Alias for gettext
     * will also output the result if $output = true
     */
    function _($message, $output = false){
        $return = $this->gettext($message);
        if ($output) {
            echo $return;
            return true;
        }
        return $return;
    }

    /**
     * Lookup a message in the current domain
     * returns translation if it exists or original message
     */
    function gettext($message){
        $translation = $message;
        if ($this->has_gettext){
            $translation = gettext($message);
        }
        elseif (isset($this->messages[$this->domain][$message])) {
            $translation = !empty($this->messages[$this->domain][$message]) ? $this->messages[$this->domain][$message] : $message;
        }
        $untranslated = (strcmp($translation, $message) === 0) ? true : false;
        return $this->output($translation, $untranslated);
    }

    /**
     * Override the current domain
     * The dgettext() function allows you to override the current domain for a single message lookup.
     */
    function dgettext($domain, $message){
        $translation = $message;
        if (array_key_exists($domain, $this->messages)){
            if (isset($this->messages[$domain][$message]))
            $translation = !empty($this->messages[$domain][$message]) ? $this->messages[$domain][$message] : $message;
        }
        $untranslated = (strcmp($translation, $message) === 0) ? true : false;
        return $this->output($translation, $untranslated);
    }

    /**
     * Plural version of gettext      
     */
    function ngettext($msgid, $msgid_plural, $count){
        if ($this->has_gettext){
            $translation = ngettext($msgid, $msgid_plural, $count);
        }
        $plural = $this->getplural($count, $this->domain);
        $original = array($msgid, $msgid_plural);
        if (isset($this->messages[$this->domain][$msgid][$plural])) {
            $translation  = $this->messages[$this->domain][$msgid][$plural];
        } else {
            $original   = array($msgid, $msgid_plural);
            $translation  = $original[$plural];
        }
        $untranslated = isset($original[$plural]) && (strcmp($translation, $original[$plural]) === 0) ? true : false;
        return $this->output($translation, $untranslated);
    }
    /**
     * Plural version of dgettext 
     */
    function dngettext($domain, $msgid, $msgid_plural, $count){
        $original   = array($msgid, $msgid_plural);
        $plural = $this->getplural($count, $domain);
        if ($this->has_gettext){
            $translation = dngettext($domain, $msgid, $msgid_plural, $count);
        } else {
            if (isset($this->messages[$domain][$msgid][$plural])) {
                $translation  = $this->messages[$domain][$msgid][$plural];
            } else {
                $translation  = $original[$plural];
            }
        }
        $untranslated = (strcmp($translation, $original[$plural]) === 0) ? true : false;
        return $this->output($translation, $untranslated);
    }

    /**
     * Specify the character encoding in which the messages 
     * from the DOMAIN message catalog will be returned 
     *
     */
    function bind_textdomain_codeset($domain, $charset){
        if ($this->has_gettext){
            bind_textdomain_codeset($domain, $charset);
        }
        return $this->text_domains[$domain]["charset"] = $charset;
    }

    /**
     * Sets the path for a domain
     * if gettext is unavailable, translation files will be loaded here
     * 
     */
    function bindtextdomain($domain, $path){
        if ($this->has_gettext){
            bindtextdomain($domain, $path);
        } else {
            $this->load($domain, $path);
        }
        return $this->text_domains[$domain]["path"] = $path;
    }

    /**
     * Sets the default domain textdomain
     */
    function textdomain($domain = null){
        if ($this->has_gettext) {
            $domain = textdomain($domain);
        }
        elseif (!is_null($domain)) {
            $this->domain = $domain;
            $this->load($domain, $this->text_domains[$this->domain]['path']);
        }
        return $this->domain;
    }

    /**
     * Overrides the domain for a single lookup
     * This function allows you to override the current domain for a single message lookup. 
     * It also allows you to specify a category. 
     * Categories are folders within the languages directory  .
     * currently, only LC_MESSAGES is implemented     
     *
     *   The values for categories are:
     *   LC_CTYPE        0
     *   LC_NUMERIC      1
     *   LC_TIME         2
     *   LC_COLLATE      3
     *   LC_MONETARY     4
     *   LC_MESSAGES     5
     *   LC_ALL          6
     *
     *   not yet implemented
     */
    function dcgettext($domain, $message, $category){
        return $message;
    }

    /**
     * dcngettext -- Plural version of dcgettext
     * not yet implemented
     */
    function dcngettext($domain, $msg1, $msg2, $count, $category){
        return $msg1;
    }


    /**
     * Plural-Forms: nplurals=2; plural=n == 1 ? 0 : 1; 
     * 
     * nplurals - total number of plurals
     * plural   - the plural index
     * 
     * Plural-Forms: nplurals=1; plural=0;
     * 1 form only      
     * 
     * Plural-Forms: nplurals=2; plural=n == 1 ? 0 : 1; 
     * Plural-Forms: nplurals=2; plural=n != 1;
     * 2 forms, singular used for one only
     * 
     * Plural-Forms: nplurals=2; plural=n>1;
     * 2 forms, singular used for zero and one
     * 
     * Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;
     * 3 forms, special case for zero
     * 
     * Plural-Forms: nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2;
     * 3 forms, special cases for one and two
     * 
     * Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;
     * 4 forms, special case for one and all numbers ending in 02, 03, or 04
     */
    function getplural($count, $domain) {
        if (isset($this->headers[$domain]['Plural-Forms'])) {
            $plural_forms = $this->headers[$domain]['Plural-Forms'];
            preg_match('/nplurals[\s]*[=]{1}[\s]*([\d]+);[\s]*plural[\s]*[=]{1}[\s]*(.*);$/', $plural_forms, $matches);
            $nplurals   = $matches[1];
            $plural_exp = $matches[2];
            if ($nplurals > 1 && strpos($plural_exp, ':') === false) {
                $plural =  'return ('.preg_replace('/n/', $count, $plural_exp).') ? 1 : 0;';
            } else {
                $plural = 'return '.preg_replace('/n/', $count, $plural_exp);
            }
        } else {
            $plural = 'return '.preg_replace('/n/', $count, 'n != 1 ? 1 : 0;');
        }
        return eval($plural);
    }

    function load($domain, $path) {
        require_once('phpgettext.catalog.php');
        $catalog = new PHPGettext_catalog($domain, $path);
        $catalog->setproperty('mode', _MODE_MO_);
        $catalog->setproperty('lang', $this->lang);
        $catalog->load();
        $this->headers[$domain] = $catalog->headers;
        foreach ($catalog->strings as $string)
        $this->messages[$domain][$string->msgid] = $string->msgstr;
    }
}


class PHPGettextAdmin
{
    var $has_gettext    = false;
    var $gettext_path   = '';
    var $debug          = false;

    function PHPGettextAdmin($debug = false)
    {
        if (ini_get('open_basedir') || ini_get('safe_mode')) {
            $this->has_gettext = false;
            return false;
        }
        $cmd = 'which gettext';
        if (substr(strtoupper(PHP_OS), 0, 3) == 'WIN'){
            $cmd = dirname(__FILE__).DIRECTORY_SEPARATOR.$cmd;
        }
        exec($cmd, $output, $return);
        if ($output) {
            $path = dirname($output[0]);
            if (is_dir($path)) {
                $this->has_gettext  = true;
                $this->gettext_path = $path;
            }
        }
        if ($debug) {
            $this->debug = $debug;
        }
    }

    /**
     * Enter description here...
     *
     * @param unknown_type $domain
     * @param unknown_type $langdir
     */
    function message_format ($domain, $textdomain, $lang, $enc='utf-8')
    {
        $path   = "$textdomain/$lang";
        if ($this->has_gettext) {
            $cmd = "msgfmt -o $path/LC_MESSAGES/$domain.mo $path/$domain.po";
            return $this->execute($cmd);
        }
        $catalog = new PHPGettext_catalog($domain, $textdomain);
        $catalog->setproperty('mode', _MODE_PO_);
        $catalog->setproperty('lang', $lang);
        $catalog->load();
        $catalog->setproperty('mode', _MODE_MO_);
        $catalog->save();
        return true;
    }
    /**
     * Enter description here...
     *
     * @param unknown_type $domain
     * @param unknown_type $langdir
     */
    function compile($lang, $textdomain, $enc='utf-8') {
        
        if ($this->has_gettext) {
            $cmd = "msgfmt -o $textdomain/$lang/LC_MESSAGES/$lang.mo $textdomain/$lang/$lang.po";
            return $this->execute($cmd);
        }
        $catalog = new PHPGettext_catalog($lang, $textdomain);
        $catalog->setproperty('mode', _MODE_PO_);
        $catalog->setproperty('lang', $lang);
        $catalog->load();
        $catalog->setproperty('mode', _MODE_MO_);
        $catalog->save();
        return true;
    }

    /**
     * Enter description here...
     *
     * @param unknown_type $domain
     * @param unknown_type $langdir
     */
    function initialize_translation ($domain, $textdomain, $lang, $enc='utf-8')
    {
        if (!$this->has_gettext) {
            return false;
        }
        set_time_limit(120);
        $path   = "$textdomain/$lang";
        copy("$textdomain/untranslated/$domain.pot", "$path/ref.po");
        $cmd = "msgmerge --width=80 --compendium $textdomain/glossary/$lang.$enc.po -o $path/$domain.po $path/ref.po $textdomain/untranslated/$domain.pot";
        $this->execute($cmd);
        unlink("$textdomain/$lang/ref.po");
    }
    /**
     * Enter description here...
     *
     * @param unknown_type $domain
     * @param unknown_type $langdir
     */
    function update_translation($domain, $textdomain, $lang, $enc='utf-8')
    {
        $path   = "$textdomain/$lang";
        if (!file_exists("$path/$domain.po")) {
            copy($textdomain."/untranslated/$domain.pot", "$path/$domain.po");
        }
        copy("$path/$domain.po", "$path/$domain.pot");
        $cmd = "msgcat --use-first -o $path/update.po $path/$domain.po $textdomain/glossary/$lang.$enc.po";
        $this->execute($cmd);
        $cmd = "msgmerge --width=80 --compendium $textdomain/glossary/$lang.$enc.po -o $textdomain/$lang/$domain.po $textdomain/$lang/update.po $textdomain/untranslated/$domain.pot";
        $this->execute($cmd);
        unlink("$textdomain/$lang/update.po");
        unlink("$textdomain/$lang/$domain.pot");
    }
    /**
     * Enter description here...
     *
     * @param unknown_type $domain
     * @param unknown_type $langdir
     */
    function add_to_dict($domain, $textdomain, $lang, $enc='utf-8') {

        if (!$this->has_gettext) return false;

        $textdomain = rtrim($textdomain, '\/');
        $path = "$textdomain/$lang";
        if (!is_dir("$textdomain/glossary/")) {
            mkdir("$textdomain/glossary/");
        }
        if (!file_exists("$textdomain/glossary/$lang.$enc.po")) {
            set_time_limit(360);
            $create_dict = true;
            $cmd = "msgcat --use-first -o $textdomain/glossary/$lang.$enc.po $path/$domain.po";
            $this->execute($cmd);
        } else {
            copy("$textdomain/glossary/$lang.$enc.po", "$textdomain/glossary/$lang.$enc.pot");
            $cmd = "msgcat --use-first -o $textdomain/glossary/$lang.$enc.po $textdomain/$lang/$domain.po $textdomain/glossary/$lang.$enc.po";
            $this->execute($cmd);
            unlink("$textdomain/glossary/$lang.$enc.pot");
        }
    }
    /**
     * Enter description here...
     *
     * @param unknown_type $domain
     * @param unknown_type $langdir
     */
    function convert_charset($domain, $textdomain, $lang, $from_charset, $to_charset) {

        $path = "$textdomain/$lang";
        if ($this->has_gettext) {
            $cmd = "msgconv --to-code=$to_charset -o $path/$domain.po $path/$domain.po";
            $ret = $this->execute($cmd);
            return $ret;
        }

        if (!function_exists('iconv')) {
            return false;
        }

        $catalog = new PHPGettext_catalog($domain, $textdomain);
        $catalog->setproperty('mode', _MODE_PO_);
        $catalog->setproperty('lang', $lang);
        $catalog->load();
        $catalog->headers['Content-Type'] = "text/plain; charset=$to_charset\n";
        foreach ($catalog->strings as $index => $message) {
            if (empty($message->msgid_plural)) {
                $catalog->strings[$index]->msgstr = iconv($from_charset, $to_charset, $message->msgstr);
            }
        }
        $catalog->save();
        return true;
    }


    /**
     * Invoke the xgettext utility with $args
     * the xgettext executable must be in PATH
     *
     * @param string the commandline arguments to gettext
     * @return unknown
     */
    function xgettext($domain, $textdomain, $php_sources, $lang='untranslated') {

        $cmd  = "xgettext ";
        if (file_exists("$textdomain/$lang/$domain.pot")) $cmd  .= '-j ';
        $cmd  .= "-n -c --sort-by-file --keyword=T_ --keyword=Tn_:1,2 --keyword=Td_:2 --keyword=Tdn_:2,3 --output-dir=$textdomain/$lang -o $domain.pot";
        if (count($php_sources) > 10) {
            $tmpfile = $textdomain.'/xgettext.txt';
            $fp = fopen($tmpfile, "w");
            fwrite($fp, implode("\r\n", $php_sources));
            fclose($fp);
            $cmd = $cmd." --files-from=$tmpfile";
        } else {
            $cmd = $cmd." ".implode(" ", $php_sources);
        }       
        $ret = $this->execute($cmd);
        @unlink($tmpfile);
        return $ret;
    }


    /**
     * Enter description here...
     *
     * @param unknown_type $domain
     * @param unknown_type $langdir
     */
    function execute($cmd) {

        if (!$this || !$this->has_gettext) return false;
        #$cmd = $this->gettext_path.DIRECTORY_SEPARATOR.escapeshellcmd($cmd);
        $cmd = escapeshellcmd($cmd);
        $return = 0;
        if (substr(strtoupper(PHP_OS), 0, 3) == 'WIN'){
            $fp = fopen("execute.bat", 'w+');
            fwrite($fp, "@echo on\r\n $cmd");
            fclose($fp);
            exec("execute.bat", $output, $retval);
            unlink("execute.bat");
        } else {
            $lastline = exec($cmd, $output, $retval);
        }
        if ($this->debug || $retval > 0) {
            $trace = debug_backtrace();
            $msg = "<div style=\"border: thin solid silver; text-align:left;margin-left:10px\">";
            $msg .= "<p><span style=\"font-weight:bold\">PHPGettextAdmin->execute('</span><i>$cmd</i><span style=\"font-weight:bold\">')</span><br/>in ".str_replace($_SERVER['DOCUMENT_ROOT'], '', $trace[1]['file']).":{$trace[1]['line']}</p>";
            $msg .= "<p><i><b>trace:</b></i></p><ul>";
            for ($a=1; $a < 3; $a++)  {
                $called = isset($trace[$a]['class']) ? $trace[$a]['class'] : '';
                $called .= $trace[$a]['type'].$trace[$a]['function']."(".@implode(', ',$trace[$a]['args']).")";
                $msg .= "<li>$called in ".str_replace($_SERVER['DOCUMENT_ROOT'], '', $trace[$a]['file'])." : {$trace[$a]['line']}</li>";
            }
            $msg .= "</ul>";
            $msg .= "<p><i><b>return value:</b></i>  $retval</p>";
            $msg .= "<p><i><b>output:</b></i> </p><pre>{$output[1]}</pre>";
            $msg .= "</div>";
            echo $msg;
        }
        return $output;
    }
}

/**
 * Enter description here...
 *
 * @return unknown
 */
function &phpgettext(){
    static $gettext;
    if (is_null($gettext)) {
        require_once('phpgettext.class.php');
        $gettext = new PHPGettext();
        $gettext->has_gettext = true;
        if (!function_exists("gettext") || !function_exists("_")) {
            $gettext->has_gettext = false;
            require_once('phpgettext.compat.php');
        }
    }
    return $gettext;
}
function T_($message) {
    $gettext =& phpgettext();
    $trans = $gettext->gettext($message);
    return $trans;
}
function Tn_($msg1, $msg2, $count) {
    $gettext =& phpgettext();
    return $gettext->ngettext($msg1, $msg2, $count);
}
function Td_($domain, $message) {
    $gettext =& phpgettext();
    return $gettext->dgettext($domain, $message);
}
function Tdn_($domain, $msg1, $msg2, $count) {
    $gettext =& phpgettext();
    return $gettext->dngettext($domain, $msg1, $msg2, $count);
}
?>