Source for file BitLanguage.php
Documentation is available at BitLanguage.php
* Copyright (c) 2005 bitweaver.org
* Copyright (c) 2004-2005, Christian Fowler, et. al.
* All Rights Reserved. See below for details and a complete list of authors.
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See http://www.gnu.org/copyleft/lesser.html for details
* @author spider <spider@steelsun.com>
// list of available (non-disabled) languages
# TODO - put '@' here due to beta1->beta2 upgrades - wolff_borg
if (isset ($_SESSION['bitlanguage'])) {
// users not logged that change the preference
} elseif (isset ($_SERVER['HTTP_ACCEPT_LANGUAGE']) && $gBitSystem->isFeatureActive( 'i18n_browser_languages' )) {
// Get supported languages
if( $browserLangs = preg_split( '/,/', preg_replace('/;q=[0-9.]+/', '', $_SERVER['HTTP_ACCEPT_LANGUAGE']) ) ) {
foreach( $browserLangs as $bl ) {
} elseif( strpos( $bl, '-' ) ) {
$baseLang = substr( $bl, 0, 2 );
$this->setLanguage( $gBitSystem->getConfig('bitlanguage', 'en') );
* getLanguage get acvtive language
* @return active language
* setLanguage set active language
* @param string $pLangCode Language code
$this->mLanguageInfo = $this->mDb->getRow( "SELECT il.* FROM `". BIT_DB_PREFIX. "i18n_languages` il WHERE `lang_code` = ?", array( $pLangCode ) );
return( !empty( $this->mLanguageInfo['right_to_left'] ) );
* verifyLanguage verify language hash before storing it
* @param array $pParamHash parameters that will be stored
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
if( empty( $pParamHash['lang_code'] ) || strlen( $pParamHash['lang_code'] ) < 2 ) {
$this->mErrors['lang_code'] = tra( 'The language code must be at least 2 characters.' );
} elseif( !empty( $langs[$pParamHash['lang_code']] ) && empty( $pParamHash['update_lang_code'] ) ) {
$this->mErrors['lang_code'] = tra( 'This language code is already used by ' ). $langs[$pParamHash['lang_code']]['native_name'];
if( empty( $pParamHash['native_name'] ) ) {
$this->mErrors['native_name'] = 'You must provide the native language name';
if( !isset ( $pParamHash['english_name'] ) ) {
$pParamHash['english_name'] = NULL;
$pParamHash['is_disabled'] = !empty( $pParamHash['is_disabled'] ) ? 'y' : NULL;
* storeLanguage store language in database
* @param array $pParamHash parameters that will be stored
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
if( empty( $pParamHash['update_lang_code'] ) ) {
$query = "INSERT INTO `". BIT_DB_PREFIX. "i18n_languages` (`lang_code`,`english_name`,`native_name`,`is_disabled`) values (?,?,?,?)";
$result = $this->mDb->query( $query, array( $pParamHash['lang_code'], $pParamHash['english_name'], $pParamHash['native_name'], $pParamHash['is_disabled'] ) );
$query = "UPDATE `". BIT_DB_PREFIX. "i18n_languages` SET `lang_code`=?, `english_name`=?, `native_name`=?, `is_disabled`=? WHERE `lang_code`=?";
$result = $this->mDb->query( $query, array( $pParamHash['lang_code'], $pParamHash['english_name'], $pParamHash['native_name'], $pParamHash['is_disabled'], $pParamHash['update_lang_code'] ) );
* expungeLanguage remove language from database
* @param string $pLangCode Language code
if( !empty( $pLangCode ) ) {
$this->mDb->StartTrans();
$query = "DELETE FROM `". BIT_DB_PREFIX. "i18n_strings` WHERE `lang_code`=?";
$result = $this->mDb->query( $query, array( $pLangCode ) );
$query = "DELETE FROM `". BIT_DB_PREFIX. "i18n_languages` WHERE `lang_code`=?";
$result = $this->mDb->query( $query, array( $pLangCode ) );
$this->mDb->CompleteTrans();
* expungeMasterString remove master string from database
* @param string $pSourceHash MD5 hash of master string
* @return TRUE on success, FALSE on failure
if( !empty( $pSourceHash ) ) {
$this->mDb->StartTrans();
$query = "DELETE FROM `". BIT_DB_PREFIX. "i18n_strings` WHERE `source_hash`=?";
$result = $this->mDb->query( $query, array( $pSourceHash ) );
$query = "DELETE FROM `". BIT_DB_PREFIX. "i18n_masters` WHERE `source_hash`=?";
$result = $this->mDb->query( $query, array( $pSourceHash ) );
$this->mDb->CompleteTrans();
* getImportedLanguages get a list of languages that have been imported
* @return array of available languages
if( $rs = $this->mDb->query( 'SELECT DISTINCT(`lang_code`) AS `lang_code` FROM `'. BIT_DB_PREFIX. 'i18n_strings`' ) ) {
$res[] = $rs->fields['lang_code'];
foreach( $res as $langCode ) {
$ret[$langCode] = $langs[$langCode];
* listLanguages list languages
* @param boolean $pListDisabled
* @param boolean $pListOnlyImportable
* @return array of languages
function listLanguages( $pListDisabled= TRUE, $pListOnlyImportable= FALSE ) {
$whereSql = " WHERE `is_disabled` IS NULL ";
$ret = $this->mDb->getAssoc( "SELECT il.`lang_code` AS `hash_key`, il.* FROM `". BIT_DB_PREFIX. "i18n_languages` il $whereSql ORDER BY il.`lang_code`" );
$ret[$langCode]['translated_name'] = $this->translate( $ret[$langCode]['english_name'] );
$ret[$langCode]['full_name'] = $ret[$langCode]['native_name']. ' ('. $this->translate( $ret[$langCode]['english_name'] ). ', '. $langCode. ')';
$langs[$langCode] = $ret[$langCode];
* verifyMastersLoaded verify that master strings are loaded
// see if there is anything in the table
$query = "SELECT COUNT(`source_hash`) FROM `". BIT_DB_PREFIX. "i18n_masters`";
$count = $this->mDb->getOne( $query );
* masterStringExists check to see if a given master string already exists
* @param array $pSourceHash MD5 hash of string to be checked
* @return TRUE if found, FALSE otherwise
return( !empty( $this->mStrings['master'][$pSourceHash] ) );
* searchMasterStrings find master string in database
* @param string $pQuerySource string
* @return TRUE on success, FALSE on failure
SELECT im.`source_hash` AS `hash_key`, `source`, `package`, im.`source_hash`
WHERE UPPER( `source` ) LIKE ? ORDER BY im.`source`";
return( $this->mDb->getAssoc( $query, array( '%'. strtoupper( $pQuerySource ). '%' ) ) );
* loadMasterStrings load all master strings
* @param string $pSourceHash MD5 hash to load
* @param string $pFilter Limit strings loaded to unlimited (default), translated or untranslated
* @return all master strings in $this->mStrings['master']
function loadMasterStrings( $pSourceHash = NULL, $pFilter = NULL, $pLangCode = NULL ) {
$bindVars = $whereSql = $joinSql = NULL;
$whereSql = ' WHERE `source_hash`=? ';
$bindVars = array( $pSourceHash );
// some basic filter options
if( !empty( $pFilter )) {
$joinSql = "LEFT OUTER JOIN `". BIT_DB_PREFIX. "i18n_strings` ist ON( im.`source_hash` = ist.`source_hash` )";
if( $pFilter == 'translated' ) {
$whereSql = "WHERE ist.`trans` IS NOT NULL";
if( !empty( $pLangCode )) {
$whereSql .= " AND ist.`lang_code` = ?";
$bindVars[] = $pLangCode;
} elseif( $pFilter == 'untranslated' ) {
$whereSql = "WHERE ist.`trans` IS NULL";
// can't work out SQL to do language limits in this filter
SELECT im.`source_hash` AS `hash_key`, `source`, `package`, im.`source_hash`
$joinSql $whereSql ORDER BY im.`source`";
$this->mStrings['master'] = $this->mDb->getAssoc( $query, $bindVars );
* storeMasterString store master string
* @param array $pParamHash data to be stored
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
if( !empty( $gBitSmarty->mCompileRsrc ) ) {
list ($type, $location) = explode( ':', $gBitSmarty->mCompileRsrc );
list ($package, $file) = explode( '/', $location );
$this->mDb->StartTrans();
$newSourceHash = $this->getSourceHash( $pParamHash['new_source'] );
$oldCount = $this->mDb->getOne( "SELECT COUNT(`source_hash`) FROM `". BIT_DB_PREFIX. "i18n_strings` WHERE `source_hash`=?", array( $pParamHash['source_hash'] ) );
$newCount = $this->mDb->getOne( "SELECT COUNT(`source_hash`) FROM `". BIT_DB_PREFIX. "i18n_strings` WHERE `source_hash`=?", array( $newSourceHash ) );
$this->mErrors['master'] = 'There was a conflict updating the master string. The new string already has translations entered.';
// we have updated a master string to an existing master string
$query = "UPDATE `". BIT_DB_PREFIX. "i18n_strings` SET `source_hash`=?, `last_modified`=? WHERE `source_hash`=?";
$trans = $this->mDb->query($query, array( $newSourceHash, time(), $pParamHash['source_hash'] ) );
$query = "DELETE FROM `". BIT_DB_PREFIX. "i18n_masters` WHERE `source_hash`=?";
$trans = $this->mDb->query($query, array( $pParamHash['source_hash'] ) );
$query = "UPDATE `". BIT_DB_PREFIX. "i18n_strings` SET `source_hash`=?, `last_modified`=? WHERE `source_hash`=?";
$trans = $this->mDb->query($query, array( $newSourceHash, time(), $pParamHash['source_hash'] ) );
$query = "UPDATE `". BIT_DB_PREFIX. "i18n_masters` SET `source_hash`=?, `source`=?, `created`=? WHERE `source_hash`=?";
$trans = $this->mDb->query($query, array( $newSourceHash, $pParamHash['new_source'], time(), $pParamHash['source_hash'] ) );
unset ( $this->mStrings[$pParamHash['source_hash']] );
$query = "INSERT INTO `". BIT_DB_PREFIX. "i18n_masters` (`source`,`source_hash`, `created`, `package`) VALUES (?,?,?,?)";
$trans = $this->mDb->query($query, array( $pParamHash['new_source'], $newSourceHash, time(), $package ) );
$this->mStrings['master'][$newSourceHash]['source'] = $pParamHash['new_source'];
$this->mStrings['master'][$newSourceHash]['source_hash'] = $newSourceHash;
$this->mDb->CompleteTrans();
* @param boolean $pOverwrite
* @return TRUE on success, FALSE on failure
foreach( $lang as $key=> $val ) {
$query = "SELECT * FROM `". BIT_DB_PREFIX. "i18n_masters` WHERE `source_hash`=?";
$trans = $this->mDb->getAssoc($query, array( $sourceHash ) );
$query = "UPDATE `". BIT_DB_PREFIX. "i18n_masters` SET `source`=?, `created`=? WHERE `source_hash`=?";
$trans = $this->mDb->query($query, array( $val, time(), $sourceHash ) );
$this->storeMasterString( array( 'new_source' => $val, 'source_hash' => $sourceHash ) );
* @param string $pLangCode Language code
* @param string $pSourceHash MD5 hash of master string
$query = "DELETE FROM `". BIT_DB_PREFIX. "i18n_strings` WHERE `source_hash`=? AND `lang_code`=?";
$result = $this->mDb->query( $query, array( $pSourceHash, $pLangCode ) );
// we don't need things where '{$menu.menu_title}' is the full string in the database
// if you change this regexp, please modify the one in kernel/smarty_bit/prefilter.tr.php as well (approx line 76)
if( !empty( $pString ) && !preg_match( '!^(\{\$[^\}]*\})+$!', $pString ) ) {
$query = "INSERT INTO `". BIT_DB_PREFIX. "i18n_strings` (`lang_code`,`trans`,`source_hash`, `last_modified`) values (?,?,?,?)";
$result = $this->mDb->query( $query, array( $pLangCode, $pString, $pSourceHash, time() ) );
// pretty brutal on mass-saving, but cache always needs purging after translation saved.
$this->mStrings[$pLangCode][$pSourceHash]['trans'] = $pString;
* @param string $pSourceHash MD5 hash of master string
* @return array of translated strings
SELECT ist.`lang_code` AS `hash_key`, `trans`, ist.`source_hash`, ist.`lang_code`
WHERE ist.`source_hash`=?
ORDER BY ist.`lang_code`";
return( $this->mDb->getAssoc( $query, array( $pSourceHash ) ) );
* @param string $pSourceHash MD5 hash of master string
* @param string $pLangCode Language code
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
SELECT im.`source_hash` AS `hash_key`, `source`, `trans`, im.`source_hash`
LEFT OUTER JOIN `". BIT_DB_PREFIX. "i18n_strings` ist ON( ist.`source_hash`=im.`source_hash` AND ist.`lang_code`=? )
return( $this->mDb->getAssoc( $query, array( $pLangCode, $pSourceHash ) ) );
* @param string $pLangCode Language code
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
* @param string $pLangCode Language code
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
* importTranslationStrings
* @param string $pLangCode Language code
* @param boolean $pOverwrite
* @param string $pFile path to file
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
// read the file and parse out the master/trans string pairs manually to prevent any evil shit from getting exec'ed
$handle = fopen( $pFile, "r" );
$line .= fgets( $handle );
if( preg_match( '/([\'"])(.*?)(?<!\\\\)\1[\n\r\s]*=>[\n\r\s]*([\'"])(.*?)(?<!\\\\)\3/msS', $line, $match )) {
foreach( $lang as $key=> $val ) {
$this->storeMasterString( array( 'source_hash' => $hashKey, 'new_source' => $key ) );
$query = "UPDATE `". BIT_DB_PREFIX. "i18n_strings` SET `trans`=?, `last_modified`=? WHERE `source_hash`=? AND `lang_code`=?";
$trans = $this->mDb->query($query, array( $val, time(), $hashKey, $pLangCode ) );
$this->mImportConflicts[$pLangCode][$hashKey]['import'] = $val;
$this->mImportConflicts[$pLangCode][$hashKey]['existing'] = $trans;
if( !empty( $this->mStrings['master'][$hashKey]['source'] ) ) {
$this->mImportConflicts[$pLangCode][$hashKey]['master'] = $this->mStrings['master'][$hashKey]['source'];
$query = "INSERT INTO `". BIT_DB_PREFIX. "i18n_strings` (`trans`,`source_hash`,`lang_code`,`last_modified`) VALUES (?,?,?,?)";
$trans = $this->mDb->query($query, array( $val, $hashKey, $pLangCode, time() ) );
* verifyTranslationLoaded
* @param string $pLangCode Language code
// see if there is anything in the table
$query = "SELECT COUNT(`source_hash`) FROM `". BIT_DB_PREFIX. "i18n_strings` ist WHERE ist.`lang_code`=?";
$count = $this->mDb->getOne($query, array( $pLangCode ) );
* @param string $pLangCode Language code
SELECT im.`source_hash` AS `hash_key`, `source`, `trans`, im.`source_hash`, ivm.`version`
LEFT OUTER JOIN `". BIT_DB_PREFIX. "i18n_strings` ist ON( ist.`source_hash`=im.`source_hash` AND ist.`lang_code`=? )
LEFT OUTER JOIN `". BIT_DB_PREFIX. "i18n_version_map` ivm ON( im.`source_hash`=ivm.`source_hash` )
$this->mStrings[$pLangCode] = $this->mDb->getAssoc( $query, array( $pLangCode ) );
global $gBitTranslationHash, $gBitSystem;
$cacheFile = TEMP_PKG_PATH. "lang/". $this->mLanguage. "/". $sourceHash;
} elseif( !empty( $this->mStrings[$this->mLanguage][$sourceHash] ) ) {
$ret = $this->mStrings[$this->mLanguage][$sourceHash]['trans'];
} elseif( file_exists( $cacheFile ) && !$gBitSystem->isFeatureActive( 'i18n_interactive_translation' ) ) {
if( empty( $this->mStrings[$this->mLanguage] ) ) {
// lookup failed. let's snag the first part of the langCode if it is a dialect (e.g. pt-br )
// write out the cache - translated or not so we don't keep hitting the database
$fp = fopen( $cacheFile, 'w' );
$this->mStrings[$this->mLanguage][$sourceHash]['trans'] = $tran;
// interactive translation process
if( $gBitSystem->isFeatureActive( 'i18n_interactive_translation' ) ) {
if( empty( $gBitTranslationHash ) ) {
$gBitTranslationHash = array();
if( !$index = array_search( $sourceHash, $gBitTranslationHash ) ) {
$gBitTranslationHash[] = $sourceHash;
$index = count( $gBitTranslationHash ) - 1;
* @param string $pLangCode Language code
* @param boolean $pOverrideUsage
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
$query = "SELECT `trans`, ivm.`version`, ivm.`source_hash` AS `usage_source_hash`
LEFT OUTER JOIN `". BIT_DB_PREFIX. "i18n_version_map` ivm ON( ivm.`source_hash`=im.`source_hash` AND ivm.`version`=? )
LEFT OUTER JOIN `". BIT_DB_PREFIX. "i18n_strings` ist ON( im.`source_hash`=ist.`source_hash` AND `lang_code`=? )
WHERE im.`source_hash`=?";
if( $pOverrideUsage && $gBitSystem->isFeatureActive( 'i18n_record_untranslated' ) ) {
$query = "SELECT `source_hash` FROM `". BIT_DB_PREFIX. "i18n_masters` WHERE `source_hash`=?";
$source = $this->mDb->getOne($query, array( $sourceHash ) );
$this->storeMasterString( array( 'source_hash' => $sourceHash, 'new_source' => $pString ) );
if( $pOverrideUsage && $gBitSystem->isFeatureActive( 'i18n_track_translation_usage' ) ) {
if( empty( $ret['usage_source_hash'] ) ) {
$query = "INSERT INTO `". BIT_DB_PREFIX. "i18n_version_map` (`source_hash`,`version`) VALUES (?,?)";
return (isset ( $ret['trans'] ) ? $ret['trans'] : NULL );
* @param string $pSourceHash
* @return master string with given source hash
return( $this->mDb->getOne( "SELECT `source` FROM `" . BIT_DB_PREFIX . "i18n_masters` WHERE `source_hash` = ? ", array( $pSourceHash ) ) );
* @return MD5 hash of string
unlink_r( TEMP_PKG_PATH. "templates_c/" );
|