diff --git a/inc/main.php b/inc/main.php index 902e20e..150d18e 100755 --- a/inc/main.php +++ b/inc/main.php @@ -41,6 +41,12 @@ abstract class Main extends PhpObject //Variables protected $asMasks; protected $asContext; + + /** + * Language Translator + * @var Translator + */ + protected $oLang; /** * Main constructor @@ -123,7 +129,7 @@ abstract class Main extends PhpObject if($sPage!=$sMainPage) $asGlobalVars['consts']['pages'][$sPage] = $sPageContent; } - $oMainMask = new Mask($sMainPage); + $oMainMask = new Mask($sMainPage, $this->oLang); $oMainMask->setTag('GLOBAL_VARS', json_encode($asGlobalVars)); $oMainMask->setTags($asMainPageTags); diff --git a/inc/mask.php b/inc/mask.php index d0c6ed5..df9c2d2 100755 --- a/inc/mask.php +++ b/inc/mask.php @@ -13,6 +13,8 @@ class Mask extends PhpObject private $asTags; private $asPartsSource; private $aoInstances; + + private $oLang; const MASK_FOLDER = 'masks/'; const MASK_EXT = '.html'; @@ -20,7 +22,7 @@ class Mask extends PhpObject const END_TAG = 'END'; const TAG_MARK = '[#]'; - public function __construct($sFileName='') + public function __construct($sFileName='', Translator $oLang=null) { //init parent::__construct(__FILE__, Settings::DEBUG); @@ -31,12 +33,10 @@ class Mask extends PhpObject $this->asPartsSource = array(); $this->aoInstances = array(); $this->sFilePath = ''; + $this->oLang = $oLang; //load file - if($sFileName!='') - { - $this->initFile($sFileName); - } + if($sFileName!='') $this->initFile($sFileName); } public static function getMaskFile($sFileName) @@ -69,6 +69,7 @@ class Mask extends PhpObject $this->sMaskName = $sMaskName; $this->sMask = $sSource; $this->setParts(); + $this->setLangTags(); } private function setParts() @@ -134,7 +135,7 @@ class Mask extends PhpObject $sPartSource = $oMask->asPartsSource[$sPartName]; //Creating new instance - $oInstance = new Mask(); + $oInstance = new Mask('', $this->oLang); $oInstance->initFileFromString($sPartSource, $sPartName); $oMask->aoInstances[$sPartName][] = $oInstance; } @@ -180,16 +181,21 @@ class Mask extends PhpObject } } - public function getTags() + public function getTags($bLang=false) { $sSafeTagMark = preg_quote(self::TAG_MARK); - $sPattern = '/'.$sSafeTagMark.'(?P\w+)'.$sSafeTagMark.'/u'; + $sPattern = '/'.$sSafeTagMark.'(?P(lang\:|)\w+)'.$sSafeTagMark.'/u'; preg_match_all($sPattern, $this->sMask, $asMatches); return array_unique(array_filter($asMatches['tag'])); } public function setTag($sTagName, $sTagValue) { + //Check if tagged should be translated + if(self::isLangTag($sTagValue)) { + if(is_null($this->oLang)) $this->addError('Missing Lang Class. Please provide in constructor'); + else $sTagValue = $this->oLang->getTranslation(mb_substr($sTagValue, 5)); + } $this->asTags[$sTagName] = $sTagValue; } @@ -197,6 +203,22 @@ class Mask extends PhpObject { foreach($asTags as $sTagName=>$sTagValue) $this->setTag($sTagName, $sTagValue); } + + /** + * Default value for tags tagged as translation: Starting with lang/ + */ + private function setLangTags() { + if($this->oLang != null) { + $asTags = $this->getTags(); + foreach($asTags as $sTagName) { + if(self::isLangTag($sTagName)) $this->setTag($sTagName, $this->oLang->getTranslation(mb_substr($sTagName, 5))); + } + } + } + + private static function isLangTag($sTag) { + return (mb_substr($sTag, 0, 5) == 'lang:'); + } public function getMask() { diff --git a/inc/toolbox.php b/inc/toolbox.php index 4560de4..9e444b2 100755 --- a/inc/toolbox.php +++ b/inc/toolbox.php @@ -126,7 +126,7 @@ class ToolBox return json_encode($asData); } - public static function curl($sUrl, $bHeader=false, $asPostData=array(), $sCookie='', $sCreds='') { + public static function curl($sUrl, $bHeader=false, $asPostData=array(), $sReturnType='text', $sCookie='', $sCreds='') { $oCurl = curl_init(); curl_setopt($oCurl, CURLOPT_URL, $sUrl); curl_setopt($oCurl, CURLOPT_VERBOSE, false); @@ -148,8 +148,18 @@ class ToolBox if($sCreds!='') curl_setopt($oCurl, CURLOPT_USERPWD, $sCreds); $sContent = curl_exec($oCurl); + + $bSuccess = ($sContent!==false); + $sDesc = ''; + if(!$bSuccess) $sDesc = curl_errno($oCurl).': '.curl_strerror(curl_errno($oCurl)); curl_close($oCurl); - return $sContent; + + switch($sReturnType) { + case 'json': $oContent = json_decode($sContent, true); break; + default: $oContent = $sContent; + } + + return array('result'=>$bSuccess, 'desc'=>$sDesc, 'content'=>$oContent); } public static function getMimeType($sPath, $bSubTypeOnly=false) @@ -454,6 +464,41 @@ class ToolBox return $sText; } + public static function getUserLanguage($available_languages, $sDefaultLang='') { + $http_accept_language = isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])?$_SERVER['HTTP_ACCEPT_LANGUAGE']:''; + + //Format: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 + preg_match_all("/([[:alpha:]]{1,8})(-([[:alpha:]|-]{1,8}))?(\s*;\s*q\s*=\s*(1\.0{0,3}|0\.\d{0,3}))?\s*(,|$)/i", $http_accept_language, $hits, PREG_SET_ORDER); + + $bestlang = $sDefaultLang; + $bestqval = 0; + + foreach($hits as $arr) { + $langprefix = strtolower($arr[1]); + if(!empty($arr[3])) { + $langrange = strtolower($arr[3]); + $language = $langprefix.'-'.$langrange; + } + else $language = $langprefix; + + //Q Value + $qvalue = 1.0; + if(!empty($arr[5])) $qvalue = floatval($arr[5]); + + //find q-maximal language + if(in_array($language, $available_languages) && $qvalue > $bestqval) { + $bestlang = $language; + $bestqval = $qvalue; + } + //if no direct hit, try the prefix only but decrease q-value by 10% (as http_negotiate_language does) + elseif(in_array($langprefix, $available_languages) && ($qvalue * 0.9) > $bestqval) { + $bestlang = $langprefix; + $bestqval = $qvalue * 0.9; + } + } + return $bestlang; + } + /** * Return relative time description * FIXME shitty implementation of i18n diff --git a/inc/translator.php b/inc/translator.php index ec6863e..ecc227c 100755 --- a/inc/translator.php +++ b/inc/translator.php @@ -8,52 +8,59 @@ class Translator extends PhpObject { private $sLang; + private $sDefaultLang; private $asLanguages; private $asTranslations; // [lang][key_word] = translation const LANG_FOLDER = 'languages/'; const LANG_EXT = '.lang'; const LANG_SEP = '='; - const DEFAULT_LANG = 'FR'; - public function __construct($sLang='') + /** + * Constructor + * @param string $sLang leave empty for auto selection based on $_SERVER['HTTP_ACCEPT_LANGUAGE'] + * @param string $sDefaultLang Fallback language if no fitting language is available + */ + public function __construct($sLang='', $sDefaultLang='en') { parent::__construct(__FILE__, Settings::DEBUG); $this->asLanguages = array(); $this->asTranslations = array(); $this->loadLanguages(); - $this->setLanguage($sLang); + $this->setLanguage($sLang, $sDefaultLang); } - public function setLanguage($sLang) - { - $this->sLang = in_array($sLang, $this->asLanguages)?$sLang:self::DEFAULT_LANG; + public function setLanguage($sLang, $sDefaultLang) { + $this->sDefaultLang = $sDefaultLang; + $this->sLang = ($sLang=='')?ToolBox::getUserLanguage($this->asLanguages, $this->sDefaultLang):$sLang; } - public function getTranslation($sTransKey, $sLang='') + public function getTranslation($sTransKey='', $sLang='') { - $sTransText = false; + $oTransText = false; - //Select language - if($sLang=='') - { - $sLang = $this->sLang; - } + //Select language & Scope + if($sLang=='') $sLang = $this->sLang; + $bAllTrans = ($sTransKey==''); //Look up in the selected language dictionary - if(in_array($sLang, $this->asLanguages) && array_key_exists($sTransKey, $this->asTranslations[$sLang])) + if(in_array($sLang, $this->asLanguages) && ($bAllTrans || array_key_exists($sTransKey, $this->asTranslations[$sLang]))) { - $sTransText = $this->asTranslations[$sLang][$sTransKey]; + $oTransText = $bAllTrans?$this->asTranslations[$sLang]:$this->asTranslations[$sLang][$sTransKey]; } //Look up in the default language dictionary - elseif(array_key_exists($sTransKey, $this->asTranslations[self::DEFAULT_LANG])) + elseif(array_key_exists($sTransKey, $this->asTranslations[$this->sDefaultLang])) { - $this->addWarning('Missing translation in "'.$sLang.'" for the key "'.$sTransKey.'", falling back to "'.self::DEFAULT_LANG.'"'); - $sTransText = $this->asTranslations[self::DEFAULT_LANG][$sTransKey]; + $this->addWarning('Missing translation in "'.$sLang.'" for the key "'.$sTransKey.'", falling back to "'.$this->sDefaultLang.'"'); + $oTransText = $this->asTranslations[$this->sDefaultLang][$sTransKey]; } - else $this->addWarning('Missing translation in "'.$sLang.'" for the key "'.$sTransKey.'"'); + else $this->addWarning($bAllTrans?'Missing language "'.$sLang.'"':'Missing translation in "'.$sLang.'" for the key "'.$sTransKey.'"'); - return $sTransText; + return $oTransText; + } + + public function getTranslations($sLang='') { + return $this->getTranslation('', $sLang); } public function getHashToPage($asMenuPages) @@ -83,7 +90,7 @@ class Translator extends PhpObject { //List all available languages $asLangPaths = glob(self::getLangPath('*')); - $this->asLanguages = array_map('basename', $asLangPaths, array_fill(1, count($asLangPaths), self::LANG_EXT)); + $this->asLanguages = array_map('basename', $asLangPaths, array_fill(1, count($asLangPaths), self::LANG_EXT)); //Load languages array_walk($this->asLanguages, array($this, 'loadLanguageFile')); @@ -97,11 +104,11 @@ class Translator extends PhpObject $asData = explode("\n", $sData); foreach($asData as $sTranslation) { - $iSepPos = stripos($sTranslation, self::LANG_SEP); - if($iSepPos!==false) + $iSepPos = mb_stripos($sTranslation, self::LANG_SEP); + if($iSepPos !== false) { - $sTransKey = trim(substr($sTranslation, 0, $iSepPos)); - $sTransText = /*htmlspecialchars(*/trim(substr($sTranslation, $iSepPos+1))/*, ENT_QUOTES)*/; //TODO when all entities have been removed + $sTransKey = trim(mb_substr($sTranslation, 0, $iSepPos)); + $sTransText = trim(mb_substr($sTranslation, $iSepPos+1)); $this->asTranslations[$sLang][$sTransKey] = $sTransText; } } @@ -112,6 +119,4 @@ class Translator extends PhpObject { return self::LANG_FOLDER.$sLang.self::LANG_EXT; } -} - -?> \ No newline at end of file +} \ No newline at end of file