Add language detection (guessing)

This commit is contained in:
2019-10-13 18:57:48 +02:00
parent 6e253911cf
commit a1003760f8
4 changed files with 117 additions and 39 deletions

View File

@@ -41,6 +41,12 @@ abstract class Main extends PhpObject
//Variables //Variables
protected $asMasks; protected $asMasks;
protected $asContext; protected $asContext;
/**
* Language Translator
* @var Translator
*/
protected $oLang;
/** /**
* Main constructor * Main constructor
@@ -123,7 +129,7 @@ abstract class Main extends PhpObject
if($sPage!=$sMainPage) $asGlobalVars['consts']['pages'][$sPage] = $sPageContent; 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->setTag('GLOBAL_VARS', json_encode($asGlobalVars));
$oMainMask->setTags($asMainPageTags); $oMainMask->setTags($asMainPageTags);

View File

@@ -13,6 +13,8 @@ class Mask extends PhpObject
private $asTags; private $asTags;
private $asPartsSource; private $asPartsSource;
private $aoInstances; private $aoInstances;
private $oLang;
const MASK_FOLDER = 'masks/'; const MASK_FOLDER = 'masks/';
const MASK_EXT = '.html'; const MASK_EXT = '.html';
@@ -20,7 +22,7 @@ class Mask extends PhpObject
const END_TAG = 'END'; const END_TAG = 'END';
const TAG_MARK = '[#]'; const TAG_MARK = '[#]';
public function __construct($sFileName='') public function __construct($sFileName='', Translator $oLang=null)
{ {
//init //init
parent::__construct(__FILE__, Settings::DEBUG); parent::__construct(__FILE__, Settings::DEBUG);
@@ -31,12 +33,10 @@ class Mask extends PhpObject
$this->asPartsSource = array(); $this->asPartsSource = array();
$this->aoInstances = array(); $this->aoInstances = array();
$this->sFilePath = ''; $this->sFilePath = '';
$this->oLang = $oLang;
//load file //load file
if($sFileName!='') if($sFileName!='') $this->initFile($sFileName);
{
$this->initFile($sFileName);
}
} }
public static function getMaskFile($sFileName) public static function getMaskFile($sFileName)
@@ -69,6 +69,7 @@ class Mask extends PhpObject
$this->sMaskName = $sMaskName; $this->sMaskName = $sMaskName;
$this->sMask = $sSource; $this->sMask = $sSource;
$this->setParts(); $this->setParts();
$this->setLangTags();
} }
private function setParts() private function setParts()
@@ -134,7 +135,7 @@ class Mask extends PhpObject
$sPartSource = $oMask->asPartsSource[$sPartName]; $sPartSource = $oMask->asPartsSource[$sPartName];
//Creating new instance //Creating new instance
$oInstance = new Mask(); $oInstance = new Mask('', $this->oLang);
$oInstance->initFileFromString($sPartSource, $sPartName); $oInstance->initFileFromString($sPartSource, $sPartName);
$oMask->aoInstances[$sPartName][] = $oInstance; $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); $sSafeTagMark = preg_quote(self::TAG_MARK);
$sPattern = '/'.$sSafeTagMark.'(?P<tag>\w+)'.$sSafeTagMark.'/u'; $sPattern = '/'.$sSafeTagMark.'(?P<tag>(lang\:|)\w+)'.$sSafeTagMark.'/u';
preg_match_all($sPattern, $this->sMask, $asMatches); preg_match_all($sPattern, $this->sMask, $asMatches);
return array_unique(array_filter($asMatches['tag'])); return array_unique(array_filter($asMatches['tag']));
} }
public function setTag($sTagName, $sTagValue) 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; $this->asTags[$sTagName] = $sTagValue;
} }
@@ -197,6 +203,22 @@ class Mask extends PhpObject
{ {
foreach($asTags as $sTagName=>$sTagValue) $this->setTag($sTagName, $sTagValue); 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() public function getMask()
{ {

View File

@@ -126,7 +126,7 @@ class ToolBox
return json_encode($asData); 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(); $oCurl = curl_init();
curl_setopt($oCurl, CURLOPT_URL, $sUrl); curl_setopt($oCurl, CURLOPT_URL, $sUrl);
curl_setopt($oCurl, CURLOPT_VERBOSE, false); curl_setopt($oCurl, CURLOPT_VERBOSE, false);
@@ -148,8 +148,18 @@ class ToolBox
if($sCreds!='') curl_setopt($oCurl, CURLOPT_USERPWD, $sCreds); if($sCreds!='') curl_setopt($oCurl, CURLOPT_USERPWD, $sCreds);
$sContent = curl_exec($oCurl); $sContent = curl_exec($oCurl);
$bSuccess = ($sContent!==false);
$sDesc = '';
if(!$bSuccess) $sDesc = curl_errno($oCurl).': '.curl_strerror(curl_errno($oCurl));
curl_close($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) public static function getMimeType($sPath, $bSubTypeOnly=false)
@@ -454,6 +464,41 @@ class ToolBox
return $sText; 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 * Return relative time description
* FIXME shitty implementation of i18n * FIXME shitty implementation of i18n

View File

@@ -8,52 +8,59 @@
class Translator extends PhpObject class Translator extends PhpObject
{ {
private $sLang; private $sLang;
private $sDefaultLang;
private $asLanguages; private $asLanguages;
private $asTranslations; // [lang][key_word] = translation private $asTranslations; // [lang][key_word] = translation
const LANG_FOLDER = 'languages/'; const LANG_FOLDER = 'languages/';
const LANG_EXT = '.lang'; const LANG_EXT = '.lang';
const LANG_SEP = '='; 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); parent::__construct(__FILE__, Settings::DEBUG);
$this->asLanguages = array(); $this->asLanguages = array();
$this->asTranslations = array(); $this->asTranslations = array();
$this->loadLanguages(); $this->loadLanguages();
$this->setLanguage($sLang); $this->setLanguage($sLang, $sDefaultLang);
} }
public function setLanguage($sLang) public function setLanguage($sLang, $sDefaultLang) {
{ $this->sDefaultLang = $sDefaultLang;
$this->sLang = in_array($sLang, $this->asLanguages)?$sLang:self::DEFAULT_LANG; $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 //Select language & Scope
if($sLang=='') if($sLang=='') $sLang = $this->sLang;
{ $bAllTrans = ($sTransKey=='');
$sLang = $this->sLang;
}
//Look up in the selected language dictionary //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 //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.'"'); $this->addWarning('Missing translation in "'.$sLang.'" for the key "'.$sTransKey.'", falling back to "'.$this->sDefaultLang.'"');
$sTransText = $this->asTranslations[self::DEFAULT_LANG][$sTransKey]; $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) public function getHashToPage($asMenuPages)
@@ -83,7 +90,7 @@ class Translator extends PhpObject
{ {
//List all available languages //List all available languages
$asLangPaths = glob(self::getLangPath('*')); $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 //Load languages
array_walk($this->asLanguages, array($this, 'loadLanguageFile')); array_walk($this->asLanguages, array($this, 'loadLanguageFile'));
@@ -97,11 +104,11 @@ class Translator extends PhpObject
$asData = explode("\n", $sData); $asData = explode("\n", $sData);
foreach($asData as $sTranslation) foreach($asData as $sTranslation)
{ {
$iSepPos = stripos($sTranslation, self::LANG_SEP); $iSepPos = mb_stripos($sTranslation, self::LANG_SEP);
if($iSepPos!==false) if($iSepPos !== false)
{ {
$sTransKey = trim(substr($sTranslation, 0, $iSepPos)); $sTransKey = trim(mb_substr($sTranslation, 0, $iSepPos));
$sTransText = /*htmlspecialchars(*/trim(substr($sTranslation, $iSepPos+1))/*, ENT_QUOTES)*/; //TODO when all entities have been removed $sTransText = trim(mb_substr($sTranslation, $iSepPos+1));
$this->asTranslations[$sLang][$sTransKey] = $sTransText; $this->asTranslations[$sLang][$sTransKey] = $sTransText;
} }
} }
@@ -112,6 +119,4 @@ class Translator extends PhpObject
{ {
return self::LANG_FOLDER.$sLang.self::LANG_EXT; return self::LANG_FOLDER.$sLang.self::LANG_EXT;
} }
} }
?>