ConLite/conlite/classes/class.i18n.php

376 Zeilen
12 KiB
PHP

<?php
/**
* This file contains the the I18N class.
*
* @package Core
* @subpackage I18N
* @author Murat Purc <murat@purc.de>
* @copyright four for business AG <www.4fb.de>
* @license http://www.contenido.org/license/LIZENZ.txt
* @link http://www.4fb.de
* @link http://www.contenido.org
*/
defined('CON_FRAMEWORK') || die('Illegal call: Missing framework initialization - request aborted.');
/**
* Internationalization (i18n) class.
*
* @package Core
* @subpackage I18N
*/
class cI18n {
const i18n_DEFAULT_DOMAIN = "conlite";
/**
* i18n related assoziative data cache.
*
* @var array
*/
protected static $_i18nData = array(
'language' => NULL,
'domains' => array(),
'files' => array(),
'cache' => array()
);
/**
* Initializes the i18n.
*
* @param string $localePath
* Path to the locales
* @param string $langCode
* Language code to set
* @param string $domain [optional]
* Language domain
*/
public static function init($localePath, $langCode, $domain = self::i18n_DEFAULT_DOMAIN) {
if (function_exists('bindtextdomain')) {
// Bind the domain 'contenido' to our locale path
bindtextdomain($domain, $localePath);
// Set the default text domain to 'contenido'
textdomain($domain);
// Half brute-force to set the locale.
if (!ini_get('safe_mode')) {
putenv("LANG=$langCode");
}
if (defined('LC_MESSAGES')) {
setlocale(LC_MESSAGES, $langCode);
}
if (false === empty($langCode)) {
setlocale(LC_CTYPE, $langCode);
}
}
self::$_i18nData['domains'][$domain] = $localePath;
self::$_i18nData['language'] = $langCode;
}
/**
* Returns translation of a specific text, wrapper for translate().
*
* @param string $string
* The string to translate
* @param string $domain [optional]
* The domain to look up
* @return string
* Returns the translation
*/
public static function __($string, $domain = self::i18n_DEFAULT_DOMAIN) {
return self::translate($string, $domain);
}
/**
* Returns translation of a specific text
*
* @param string $string
* The string to translate
* @param string $domain [optional]
* The domain to look up
* @throws cException
* if this is the backend mode and the $belang is not set
* @return string
* Returns the translation
*/
public static function translate($string, $domain = self::i18n_DEFAULT_DOMAIN) {
global $cfg, $belang, $contenido;
// Auto initialization
if (!self::$_i18nData['language']) {
if (!isset($belang)) {
if ($contenido) {
throw new Exception('init $belang is not set');
}
// Needed - otherwise this won't work
$belang = false;
}
// CON-2165
// initialise localisation of plugins correctly in frontend
if (!empty($cfg['path']['contenido_locale']) && $domain === self::i18n_DEFAULT_DOMAIN) {
self::init($cfg['path']['contenido_locale'], $belang, $domain);
} else {
if (empty($belang)) {
$oApiLang = new cApiLanguage(cRegistry::getLanguageId());
$language = $oApiLang->getProperty('language', 'code');
$country = $oApiLang->getProperty('country', 'code');
$locale = $language . '_' . strtoupper($country);
self::init($cfg['path']['contenido'] . $cfg['path']['plugins'] . $domain . '/locale/', $locale, $domain);
} else {
self::init($cfg['path']['contenido'] . $cfg['path']['plugins'] . $domain . '/locale/', $belang, $domain);
}
}
}
// Is emulator to use?
if (!$cfg['native_i18n']) {
$ret = self::emulateGettext($string, $domain);
// hopefully a proper replacement for
// mb_convert_encoding($string, 'HTML-ENTITIES', 'utf-8');
// see http://stackoverflow.com/q/11974008
$ret = htmlspecialchars_decode(utf8_decode(conHtmlentities($ret, ENT_COMPAT, 'utf-8', false)));
return $ret;
}
// Try to use native gettext implementation
if (extension_loaded('gettext')) {
if (function_exists('dgettext')) {
if ($domain != self::i18n_DEFAULT_DOMAIN) {
$translation = dgettext($domain, $string);
return $translation;
} else {
return gettext($string);
}
}
}
// Emulator as fallback
$ret = self::emulateGettext($string, $domain);
if (self::_isUtf8($ret)) {
$ret = utf8_decode($ret);
}
return $ret;
}
/**
* Returns the current language (if already defined)
*
* @return string|false
*/
public static function getLanguage() {
return (self::$_i18nData['language']) ? self::$_i18nData['language'] : false;
}
/**
* Returns list of registered domains
*
* @return array
*/
public static function getDomains() {
return self::$_i18nData['domains'];
}
/**
* Returns list of cached tranlation files
*
* @return array
*/
public static function getFiles() {
return self::$_i18nData['files'];
}
/**
* Returns list of cached tranlations
*
* @return array
*/
public static function getCache() {
return self::$_i18nData['cache'];
}
/**
* Resets cached translation data (language, domains, files, and cache)
*/
public static function reset() {
self::$_i18nData['language'] = NULL;
self::$_i18nData['domains'] = array();
self::$_i18nData['files'] = array();
self::$_i18nData['cache'] = array();
}
/**
* Emulates GNU gettext
*
* @param string $string
* The string to translate
* @param string $domain [optional]
* The domain to look up
* @return string
* Returns the translation
*/
public static function emulateGettext($string, $domain = self::i18n_DEFAULT_DOMAIN) {
if ($string == '') {
return '';
}
if (!isset(self::$_i18nData['cache'][$domain])) {
self::$_i18nData['cache'][$domain] = array();
}
if (isset(self::$_i18nData['cache'][$domain][$string])) {
return self::$_i18nData['cache'][$domain][$string];
}
$translationFile = self::$_i18nData['domains'][$domain] . self::$_i18nData['language'] . '/LC_MESSAGES/' . $domain . '.po';
if (!cFileHandler::exists($translationFile)) {
return $string;
}
if (!isset(self::$_i18nData['files'][$domain])) {
self::$_i18nData['files'][$domain] = self::_loadTranslationFile($translationFile);
}
$stringStart = strpos(self::$_i18nData['files'][$domain], '"' . str_replace(array(
"\n",
"\r",
"\t"
), array(
'\n',
'\r',
'\t'
), $string) . '"');
if ($stringStart === false) {
return $string;
}
$matches = array();
$quotedString = preg_quote(str_replace(array(
"\n",
"\r",
"\t"
), array(
'\n',
'\r',
'\t'
), $string), '/');
$result = preg_match("/msgid.*\"(" . $quotedString . ")\"(?:\s*)?\nmsgstr(?:\s*)\"(.*)\"/", self::$_i18nData['files'][$domain], $matches);
// Old:
// preg_match("/msgid.*\"".preg_quote($string,"/")."\".*\nmsgstr(\s*)\"(.*)\"/",
// self::$_i18nData['files'][$domain], $matches);
if ($result && !empty($matches[2])) {
// Translation found, cache it
self::$_i18nData['cache'][$domain][$string] = stripslashes(str_replace(array(
'\n',
'\r',
'\t'
), array(
"\n",
"\r",
"\t"
), $matches[2]));
} else {
// Translation not found, cache original string
self::$_i18nData['cache'][$domain][$string] = $string;
}
return self::$_i18nData['cache'][$domain][$string];
}
/**
* Registers a new i18n domain.
*
* @param string $localePath
* Path to the locales
* @param string $domain
* Domain to bind to
*/
public static function registerDomain($domain, $localePath) {
if (function_exists('bindtextdomain')) {
// Bind the domain 'contenido' to our locale path
bindtextdomain($domain, $localePath);
}
self::$_i18nData['domains'][$domain] = $localePath;
}
/**
* Loads gettext translation and file does some operations like stripping
* comments on the content.
*
* @param string $translationFile
* @return string
* The preparend translation file content
*/
protected static function _loadTranslationFile($translationFile) {
$content = cFileHandler::read($translationFile);
// Normalize eol chars
$content = str_replace("\n\r", "\n", $content);
$content = str_replace("\r\n", "\n", $content);
// Remove comment lines
$content = preg_replace('/^#.+\n/m', '', $content);
// Prepare for special po edit format
/*
* Something like: #, php-format msgid "" "Hello %s,\n" "\n" "you've got
* a new reminder for the client '%s' at\n" "%s:\n" "\n" "%s" msgstr ""
* "Hallo %s,\n" "\n" "du hast eine Wiedervorlage erhalten für den
* Mandanten '%s' at\n" "%s:\n" "\n" "%s" has to be converted to: msgid
* "Hello %s,\n\nyou've got a new reminder for the client '%s'
* at\n%s:\n\n%s" msgstr "Hallo %s,\n\ndu hast eine Wiedervorlage
* erhalten für den Mandanten '%s' at\n%s:\n\n%s"
*/
// assemble broken long message lines (remove double quotes with a line
// break in between, e.g. "\n")
$content = preg_replace('/(""\\s+")/m', '"', $content);
// replace line breaks followed by a whitespace character against a line
// break
$content = preg_replace('/\\n"\\s+"/m', '\\n', $content);
// remove multiple line breaks
$content = preg_replace('/("\n+")/m', '', $content);
// remove the backslash from double quotes (\"foobar\" -> "foobar")
$content = preg_replace('/(\\\")/m', '"', $content);
return $content;
}
private static function _isUtf8($input) {
$len = strlen($input);
for ($i = 0; $i < $len; $i++) {
$char = ord($input[$i]);
$n = 0;
if ($char < 0x80) {
// ASCII char
continue;
} else if (($char & 0xE0) === 0xC0 && $char > 0xC1) {
// 2 byte long char
$n = 1;
} else if (($char & 0xF0) === 0xE0) {
// 3 byte long char
$n = 2;
} else if (($char & 0xF8) === 0xF0 && $char < 0xF5) {
// 4 byte long char
$n = 3;
} else {
return false;
}
for ($j = 0; $j < $n; $j++) {
$i++;
if ($i == $len || (ord($input[$i]) & 0xC0) !== 0x80) {
return false;
}
}
}
return true;
}
}