<?php
/*
 * $RCSfile: GalleryTranslator.class,v $
 *
 * Gallery - a web based photo album viewer and editor
 * Copyright (C) 2000-2005 Bharat Mediratta
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA  02110-1301, USA.
 */
/**
 * @version $Revision: 1.93 $ $Date: 2005/08/23 03:49:03 $
 * @package GalleryCore
 * @author Bharat Mediratta <bharat@menalto.com>
 */

/**
 * Internationalization and Localization utilities
 *
 * @package GalleryCore
 * @subpackage Classes
 */
class GalleryTranslator {
    /*
     * ****************************************
     *                 Members
     * ****************************************
     */

    /**
     * Keep track of the gettext domains we've already bound
     *
     * @var array $_boundDomains
     * @access private
     */
    var $_boundDomains;

    /**
     * Does the active language read right-to-left?
     *
     * @var array $_isRightToLeft
     * @access private
     */
    var $_isRightToLeft;

    /*
     * ****************************************
     *                 Methods
     * ****************************************
     */

    function GalleryTranslator() {
	$this->_boundDomains = array();
    }

    /**
     * Can we translate?
     *
     * @return boolean
     * @static
     */
    function canTranslate() {
	return function_exists('dgettext');
    }

    /**
     * Can we make plural translations?
     *
     * @return boolean
     * @static
     */
    function canTranslatePlural() {
	return function_exists('dngettext');
    }

    /**
     * Does the active language read right-to-left?
     *
     * @return boolean
     */
    function isRightToLeft() {
	return $this->_isRightToLeft;
    }

    /**
     * Return our language data
     *
     * @return array array['language code']['country code'] = array('description', 'right-to-left'?)
     *         array array('country' => 'default language code',
     *                     'country' => 'default language code',
     *                     ...)
     * @static
     */
    function getLanguageData() {
	static $supportedLanguages = array();
	static $defaultCountry = array();

	if (empty($supportedLanguages)) {
	    /* XXX: Move this information into a configuration file */
	    /* English */
	    $supportedLanguages['en']['US']['description'] = 'English (US)';
	    $supportedLanguages['en']['GB']['description'] = 'English (UK)';
	    $defaultCountry['en'] = 'US';

	    /* Czech */
	    $supportedLanguages['cs']['CZ']['description'] = '&#x010c;esky';
	    $defaultCountry['cs'] = 'CZ';

	    /* Danish */
	    $supportedLanguages['da']['DK']['description'] = 'Dansk';
	    $defaultCountry['da'] = 'DK';

	    /* German */
	    $supportedLanguages['de']['DE']['description'] = 'Deutsch';
	    $defaultCountry['de'] = 'DE';

	    /* Spanish */
	    $supportedLanguages['es']['MX']['description'] = 'Espa&#241;ol (MX)';
	    $supportedLanguages['es']['AR']['description'] = 'Espa&#241;ol (AR)';
	    $defaultCountry['es'] = 'MX';

	    /* French */
	    $supportedLanguages['fr']['FR']['description'] = 'Fran&#231;ais';
	    $defaultCountry['fr'] = 'FR';

	    /* Irish */
	    $supportedLanguages['ga']['IE']['description'] = 'Gaeilge';
	    $defaultCountry['ga'] = 'IE';

	    /* Greek */
	    $supportedLanguages['el']['GR']['description'] = 'Greek';
	    $defaultCountry['el'] = 'GR';

	    /* Icelandic */
	    $supportedLanguages['is']['IS']['description'] = 'Icelandic';
	    $defaultCountry['is'] = 'IS';

	    /* Italian */
	    $supportedLanguages['it']['IT']['description'] = 'Italiano';
	    $defaultCountry['it'] = 'IT';

	    /* Latvian */
	    $supportedLanguages['lv']['LV']['description'] = 'Latvie&#353;u';
	    $defaultCountry['lv'] = 'LV';

	    /* Hungarian */
	    $supportedLanguages['hu']['HU']['description'] = 'Magyar';
	    $defaultCountry['hu'] = 'HU';

	    /* Dutch */
	    $supportedLanguages['nl']['NL']['description'] = 'Nederlands';
	    $defaultCountry['nl'] = 'NL';

	    /* Norwegian */
	    $supportedLanguages['no']['NO']['description'] = 'Norsk bokm&#229;l';
	    $defaultCountry['no'] = 'NO';

	    /* Polish */
	    $supportedLanguages['pl']['PL']['description'] = 'Polski';
	    $defaultCountry['pl'] = 'PL';

	    /* Portuguese */
	    $supportedLanguages['pt']['BR']['description'] = 'Portugu&#234;s Brasileiro';
	    $supportedLanguages['pt']['PT']['description'] = 'Portugu&#234;s (PT)';
	    $defaultCountry['pt'] = 'BR';

	    /* Slovenian */
	    $supportedLanguages['sl']['SI']['description'] = 'Sloven&#353;&#269;ina';
	    $defaultCountry['sl'] = 'SI';

	    /* Serbian */
	    $supportedLanguages['sr']['YU']['description'] = 'Srpski';
	    $defaultCountry['sr'] = 'YU';

	    /* Finnish */
	    $supportedLanguages['fi']['FI']['description'] = 'Suomi';
	    $defaultCountry['fi'] = 'FI';

	    /* Swedish */
	    $supportedLanguages['sv']['SE']['description'] = 'Svenska';
	    $defaultCountry['sv'] = 'SE';

	    /* Thai */
	    $supportedLanguages['th']['TH']['description'] = 'Thai';
	    $defaultCountry['th'] = 'TH';

	    /* Vietnamese */
	    $supportedLanguages['vi']['VN']['description'] = 'Ti&#7871;ng Vi&#7879;t';
	    $defaultCountry['vi'] = 'VN';

	    /* Turkish */
	    $supportedLanguages['tr']['TR']['description'] = 'T&#252;rk&#231;e';
	    $defaultCountry['tr'] = 'TR';

	    /* Bulgarian */
	    $supportedLanguages['bg']['BG']['description'] =
		'&#x0411;&#x044a;&#x043b;&#x0433;&#x0430;&#x0440;&#x0441;&#x043a;&#x0438;';
	    $defaultCountry['bg'] = 'BG';

	    /* Russian */
	    $supportedLanguages['ru']['RU']['description'] =
		'&#1056;&#1091;&#1089;&#1089;&#1082;&#1080;&#1081;';
	    $defaultCountry['ru'] = 'RU';

	    /* Chinese */
	    $supportedLanguages['zh']['CN']['description'] = '&#31616;&#20307;&#20013;&#25991;';
	    $supportedLanguages['zh']['TW']['description'] = '&#32321;&#39636;&#20013;&#25991;';
	    $defaultCountry['zh'] = 'CN';

	    /* Japanese */
	    $supportedLanguages['ja']['JP']['description'] = '&#x65e5;&#x672c;&#x8a9e;';
	    $defaultCountry['ja'] = 'JP';

	    /* Arabic */
	    $supportedLanguages['ar']['SA']['description'] =
		'&#1575;&#1604;&#1593;&#1585;&#1576;&#1610;&#1577;';
	    $supportedLanguages['ar']['SA']['right-to-left'] = true;
	    $defaultCountry['ar'] = 'SA';

	    /* Hebrew */
	    $supportedLanguages['he']['IL']['description'] = '&#1506;&#1489;&#1512;&#1497;&#1514;';
	    $supportedLanguages['he']['IL']['right-to-left'] = true;
	    $defaultCountry['he'] = 'IL';
	}

	return array($supportedLanguages, $defaultCountry);
    }

    /**
     * Return the list of languages that we support.
     * Return our language data
     *
     * @return array['language code']['country code'] =
     *              array('description', 'right-to-left'?)
     */
    function getSupportedLanguages() {
	/* Get our language data */
	list ($supportedLanguages, $defaultCountry) = GalleryTranslator::getLanguageData();
	return $supportedLanguages;
    }

    /**
     * Initialize the translator with the specified language code hint
     *
     * @param string the language code hint (eg. 'en_US' or 'zh_CN')
     * @return object GalleryStatus a status code
     */
    function init($languageCode=null) {
	if (empty($languageCode)) {
	    $languageCode = GalleryTranslator::getLanguageCodeFromRequest();
	}

	list ($languageCode, $data) = GalleryTranslator::getSupportedLanguageCode($languageCode);

	/* If we're using gettext, try to bind to a language */
	if (function_exists('dgettext')) {
	    $this->_isRightToLeft = isset($data['right-to-left']);
	    /*
	     * Some systems only require LANG, others (like Mandrake) seem to require
	     * LANGUAGE also.
	     */
	    putenv("LANG=${languageCode}");
	    putenv("LANGUAGE=${languageCode}");

	    GalleryTranslator::_setlocale(LC_ALL, $languageCode);
	}

	/* Set the appropriate charset in our HTTP header */
	if (!headers_sent()) {
	    header('Content-Type: text/html; charset=UTF-8');
	}

	return GalleryStatus::success();
    }

    /**
     * Attempt to set the requested locale.
     * Try fallbacks and character sets if needed to find a valid locale.
     *
     * @param mixed category
     * @param string locale
     * @return string locale selected or boolean false if none
     * @static
     * @access private
     */
    function _setlocale($category, $locale) {
	global $gallery;

	if (($ret = setlocale($category, $locale)) !== false) {
	    return $ret;
	}
	/* Try just selecting the language */
	if (($i = strpos($locale, '_')) !== false
		&& ($ret = setlocale($category, substr($locale, 0, $i))) !== false) {
	    return $ret;
	}
	/*
	 * Try appending some character set names; some systems (like FreeBSD) need this.
	 * Some require a format with hyphen (e.g. gentoo) and others without (e.g. FreeBSD).
	 */
	foreach (array('UTF-8', 'UTF8', 'utf8',
		       'ISO8859-1', 'ISO8859-2', 'ISO8859-5', 'ISO8859-7', 'ISO8859-9',
		       'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-5', 'ISO-8859-7', 'ISO-8859-9',
		       'EUC', 'Big5') as $charset) {
	    if (($ret = setlocale($category, $locale . '.' . $charset)) !== false) {
		return $ret;
	    }
	}
	$gallery->debug("Warning: Unable to select locale $locale");
	return false;
    }

    /**
     * Find a supported locale from given string.
     *
     * @param string the language code hint
     * @param boolean (optional) if false, return array(null,null) for no match instead of en_US
     * @return array (string a language code in <language>_<COUNTRY> format,
     *                array data about this language code (description,right-to-left?))
     * @static
     */
    function getSupportedLanguageCode($languageCode, $fallback=true) {
	static $supportedLanguages;
	static $defaultCountry;
	if (!isset($supportedLanguages)) {
	    list ($supportedLanguages, $defaultCountry) = GalleryTranslator::getLanguageData();
	}

	list ($language, $country) = preg_split('/[-_]/', "${languageCode}_");
	$country = strtoupper($country);
	if ((empty($country) || !isset($supportedLanguages[$language][$country]))
		&& isset($defaultCountry[$language])) {
	    /* Use default country if none specified or particular country not supported */
	    $country = $defaultCountry[$language];
	}
	if (isset($supportedLanguages[$language][$country])) {
	    return array("${language}_${country}", $supportedLanguages[$language][$country]);
	}

	if ($fallback) {
	    return array('en_US', $supportedLanguages['en']['US']);
	} else {
	    return array(null, null);
	}
    }

    /**
     * Examine the incoming request and try to figure out what languages the
     * browser will accept.  Take the first one that we can support.
     *
     * @return a language code in the <language>_<COUNTRY> format, eg: en_US
     * @static
     */
    function getLanguageCodeFromRequest() {
	/* Take the first thing the browser accepts that we can use */
	$httpAcceptLanguage = GalleryUtilities::getServerVar('HTTP_ACCEPT_LANGUAGE');
	if (!empty($httpAcceptLanguage)) {
	    foreach (explode(',', $httpAcceptLanguage)
		     as $code) {

		list ($languageCode) = GalleryTranslator::getSupportedLanguageCode($code, false);
		if (isset($languageCode)) {
		    return $languageCode;
		}
	    }
	}

	/* Fall back to English (US) */
	return 'en_US';
    }

    /**
     * Localize the given content
     *
     * Expected inputs are of the form:
     *
     * Example 1:
     *  $data['text'] = 'Some text to localize with %d arguments'
     *  $data['arg1'] = 5;
     *
     *  localized:  'Some text to localize with 5 arguments'
     *
     * Example 2:
     *  $data['one'] =   'You have %d orange'
     *  $data['many'] =  'You have %d oranges'
     *  $data['count'] = 3 (or 1);
     *
     * localized:  'You have 3 oranges'  (or 'You have 1 orange')
     *
     * @param string the domain (eg, a module id)
     * @param string a directory
     * @param mixed a single string, or an array of parameters
     * @return array object GalleryStatus a status code
     *               string the localized value
     */
    function translateDomain($domain, $data) {
	global $gallery;

	/* Validate our parameters */
	if (!(isset($data['text']) ||
	      (isset($data['one']) && isset($data['many']) && isset($data['count'])))) {
	    return array(GalleryStatus::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__),
			 null);
	}

	$platform = $gallery->getPlatform();
	if (function_exists('dgettext')) {
	    if (isset($domain) && !isset($this->_boundDomains[$domain])) {
		list ($componentType, $componentName) = split('_', $domain);
		$basePath = sprintf('%s/%s/%s/locale',
				    $platform->realpath(dirname(__FILE__) . '/../../..'),
				    $componentType,
				    $componentName);
		if ($gallery->getDebug()) {
		    $gallery->debug("Binding text domain: $domain -> $basePath");
		}
		bindtextdomain($domain, $basePath);
		textdomain($domain);
		if (function_exists('bind_textdomain_codeset')) {
		    bind_textdomain_codeset($domain, 'UTF-8');
		}
		$this->_boundDomains[$domain] = $basePath;
	    }

	    /*
	     * We have to have dngettext (which is only available > PHP 4.2.0,
	     * according to the PHP manual) in order to do pluralization
	     * translations.  If we don't have have dngettext() we try to
	     * gracefully degrade to using dgettext().
	     */
	    if (isset($data['one'])) {
		if (function_exists('dngettext')) {
		    $localized = dngettext($domain, $data['one'], $data['many'], $data['count']);
		} else {
		    /*
		     * It would make more sense to fall back to $data['many']
		     * here, since the odds are better that it will be more
		     * applicable.  However, due to the way that we do the
		     * pluralization, the keys will be organized by the
		     * $data['one'] entry so there won't be a $data['many'] key
		     * for dgettext() to use.  :-(
		     */
		    $localized = dgettext($domain, $data['one']);
		}
	    } else {
		$localized = dgettext($domain, $data['text']);
	    }

	    /*
	     * In PHP 4.1.x (before bind_textdomain_codeset) the string will be returned from
	     * gettext in the default encoding for that language. Here we convert that encoding
	     * to the active charset.  However, it seems newer versions of PHP that are missing
	     * bind_textdomain_charset do return the output in UTF-8.
	     */
	    if (!function_exists('bind_textdomain_codeset') &&
		    version_compare(phpversion(), '4.2.0', '<')) {
		$localized = GalleryCoreApi::convertToUtf8($localized);
	    }
	} else {
	    /*
	     * The server doesn't have gettext, so fake it using the source of
	     * the message (which is almost definitely in US English)
	     */
	    if (isset($data['text'])) {
		$localized = $data['text'];
	    } else if ($data['count'] == 1) {
		$localized = $data['one'];
	    } else {
		$localized = $data['many'];
	    }
	}

	$i = 1;
	$args = array();
	while (isset($data['arg' . $i])) {
	    $args[] = $data['arg' . $i];
	    $i++;

	    /* Catch runaways */
	    if ($i > 100) {
		return array(GalleryStatus::error(ERROR_UNKNOWN, __FILE__, __LINE__), null);
	    }
	}

	/*
	 * If we have arguments, then feed the localized string and the
	 * arguments into sprintf.
	 */
	if (sizeof($args) > 0) {
	    array_unshift($args, $localized);
	    $localized = call_user_func_array('sprintf', $args);
	}

	/*
	 * This is a useful debug routine.  Uncomment it to have every
	 * string prefixed with the domain it was localized in.
	 */
	//$localized = "<font size=1 color=#C33>$domain:</font>$localized";

	return array(GalleryStatus::success(), $localized);
    }
}
?>
