<?php /*

 ocPortal
 Copyright (c) ocProducts, 2004-2012

 See text/EN/licence.txt for full licencing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license		http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright	ocProducts Ltd
 * @package		core
 */

/*EXTRA FUNCTIONS: levenshtein*/

/**
 * Standard code module initialisation function.
 */
function init__lang()
{
	if (!function_exists('do_lang'))
	{
		/**
		 * Get the human-readable form of a language id, or a language entry from a language INI file.
		 *
		 * @param  ID_TEXT		The language id
		 * @param  ?mixed			The first token [string or tempcode] (replaces {1}) (NULL: none)
		 * @param  ?mixed			The second token [string or tempcode] (replaces {2}) (NULL: none)
		 * @param  ?mixed			The third token (replaces {3}). May be an array of [of string], to allow any number of additional args (NULL: none)
		 * @param  ?LANGUAGE_NAME The language to use (NULL: users language)
		 * @param  boolean		Whether to cause ocPortal to exit if the lookup does not succeed
		 * @return ?mixed			The human-readable content (NULL: not found). String normally. Tempcode if tempcode parameters.
		 */
		function do_lang($codename,$token1=NULL,$token2=NULL,$token3=NULL,$lang=NULL,$require_result=true)
		{
			return _do_lang($codename,$token1,$token2,$token3,$lang,$require_result);
		}
	}

	global $COMCODE_LANG_STRING_CACHE;
	$COMCODE_LANG_STRING_CACHE=array();

	global $LANG;
	$LANG=array();
	global $LANG_LOADED,$LANG_LOADED_LANG,$LANG_REQUESTED_LANG,$LANGS_REQUESTED;
	// Tracks what has already been require'd, although loading may have been deferred.
	$LANG_REQUESTED_LANG=array(); // By lang pack and lang file
	$LANGS_REQUESTED=array(); // Just by lang file
	// Tracks what has already been require'd and not deferred. This will not track all require_lang calls, as ocPortal will try and use the page's own lang cache first.
	$LANG_LOADED_LANG=array(); // By lang pack and lang file
	$LANG_LOADED=array(); // Just by lang file

	global $LANGUAGE;
	$LANGUAGE=array();

	global $LANGS_MAP;
	$LANGS_MAP=NULL;

	global $USER_LANG_CACHED,$USER_LANG_EARLY_CACHED,$USER_LANG_LOOP,$REQUIRE_LANG_LOOP;
	global $RECORD_LANG_STRINGS,$RECORDED_LANG_STRINGS,$RECORD_LANG_STRINGS_CONTENT,$RECORDED_LANG_STRINGS_CONTENT;
	$RECORD_LANG_STRINGS=false;
	$RECORDED_LANG_STRINGS=array();
	$RECORD_LANG_STRINGS_CONTENT=false;
	$RECORDED_LANG_STRINGS_CONTENT=array();
	$USER_LANG_LOOP=0;
	$USER_LANG_CACHED=NULL;
	$USER_LANG_EARLY_CACHED=NULL;
	$REQUIRE_LANG_LOOP=0;
	global $REQUIRED_ALL_LANG;
	$REQUIRED_ALL_LANG=array();

	// Lazy loading code: learning algorithm to cache strings against different pages without loading all, unless we get a cache miss in the page's pool
	global $PAGE_CACHE_FILE,$PAGE_CACHE_LANG_LOADED,$PAGE_CACHE_LAZY_LOAD,$PAGE_CACHE_LANGS_REQUESTED;
	$PAGE_CACHE_FILE=mixed();
	$PAGE_CACHE_LANG_LOADED=array();
	$PAGE_CACHE_LAZY_LOAD=false;
	$PAGE_CACHE_LANGS_REQUESTED=array();
	if (((function_exists('get_option')) && (get_option('is_on_lang_cache')=='1') && ((!array_key_exists('page',$_GET)) || (/*(is_string($_GET['page'])) && */(is_string($_GET['page'])) && (strpos($_GET['page'],'..')===false))))/* && (!is_null($GLOBALS['MEM_CACHE']))*/)
	{
		if (running_script('index'))
		{
			$key='page__'.get_zone_name().'__'.get_page_name();
		} else
		{
			$key='script__'.md5(serialize(ocp_srv('PHP_SELF')));
		}
		$cache_path=get_custom_file_base().'/lang_cached/'.user_lang().'/'.filter_naughty($key,true).'.lcd';
		if (!is_null($GLOBALS['MEM_CACHE']))
		{
			$PAGE_CACHE_LANG_LOADED=persistent_cache_get($cache_path);
			if (is_array($PAGE_CACHE_LANG_LOADED))
			{
				$LANGUAGE=$PAGE_CACHE_LANG_LOADED;
			}
			$PAGE_CACHE_FILE=$cache_path;
		} else
		{
			$contents=@file_get_contents($cache_path);
			if ($contents!==false)
			{
				$PAGE_CACHE_LANG_LOADED=@unserialize($contents);
				if (is_array($PAGE_CACHE_LANG_LOADED))
				{
					$LANGUAGE=$PAGE_CACHE_LANG_LOADED;
				} else
				{
					$PAGE_CACHE_LANG_LOADED=array();
				}
			}
			$PAGE_CACHE_FILE=$cache_path;
		}
		$PAGE_CACHE_LAZY_LOAD=true;
	}

	require_lang('critical_error');
	require_lang('global');

	global $SEARCH__CONTENT_BITS;
	$SEARCH__CONTENT_BITS=NULL;

	if (strtolower(get_charset())=='utf-8')
	{
		global $HTML_ESCAPE_1_STRREP,$HTML_ESCAPE_2;
		$HTML_ESCAPE_1_STRREP=array('&'/*,'ì','î'*/,'"','\'','<','>'/*,'£'*/);
		$HTML_ESCAPE_2=array('&amp;'/*,'&quot;','&quot;'*/,'&quot;','&#039;','&lt;','&gt;'/*,'&pound;'*/);
	}

	global $OPTIONS;
	if (isset($OPTIONS))
	{
		if (user_lang()!=get_site_default_lang())
		{
			foreach ($OPTIONS as $name=>$option)
			{
				unset($OPTIONS[$name]['config_value_translated']);
			}
		}
	}
}

// ====
// CODE
// ====

/**
 * Open up our language cache file for appending.
 */
function open_page_cache_file()
{
	if ($GLOBALS['MEM_CACHE']===NULL)
	{
		global $PAGE_CACHE_FILE,$PAGE_CACHE_LAZY_LOAD;
		if ($PAGE_CACHE_FILE===NULL) return;
		if (!is_string($PAGE_CACHE_FILE)) return;
		$cache_path=$PAGE_CACHE_FILE;
		$PAGE_CACHE_FILE=@fopen($cache_path,'at');
		if ($PAGE_CACHE_FILE!==false)
		{
			if (ftell($PAGE_CACHE_FILE)==0)
			{
				require_code('files');
				fix_permissions($cache_path,0666);
			}
		} else
		{
			$PAGE_CACHE_FILE=NULL;
		}
	}
}

/**
 * This function is called when no other language works, and it will return the original default language - 'EN'. You may change this to another language, but this is not advised, as ocPortal is being shipped with the EN language complete and unabridged as standard - hence you cannot go wrong if you leave it as EN.
 * In theory, this is the only hook to English that there is.
 *
 * @return LANGUAGE_NAME			The fallback language
 */
function fallback_lang()
{
	return 'EN';
}

/**
 * Get the user's currently selected language.
 *
 * @return LANGUAGE_NAME  The user's current language
 */
function user_lang()
{
	global $USER_LANG_CACHED;
	if ($USER_LANG_CACHED!==NULL) return $USER_LANG_CACHED;
	global $MEMBER_CACHED,$USER_LANG_LOOP,$IN_MINIKERNEL_VERSION;

	if ($IN_MINIKERNEL_VERSION==1)
	{
		return get_site_default_lang();
	}

	// In URL?
	$lang='';
	$special_page_type=get_param('special_page_type','');
	if ($special_page_type!='' && substr($special_page_type,0,5)=='lang_')
	{
		if (substr($special_page_type,0,13)=='lang_content_')
		{
			$lang=substr($special_page_type,13);
		} else
		{
			$lang=substr($special_page_type,5);
		}
	}
	if ($lang=='')
	{
		$lang=filter_naughty(either_param('lang',get_param('keep_lang','')));
	}

	if ((!function_exists('get_member')) || ($USER_LANG_LOOP==1) || ($MEMBER_CACHED===NULL))
	{
		global $USER_LANG_EARLY_CACHED;
		if ($USER_LANG_EARLY_CACHED!==NULL) return $USER_LANG_EARLY_CACHED;

		if (($lang!='') && (does_lang_exist($lang))) return $lang;

		if ((array_key_exists('GET_OPTION_LOOP',$GLOBALS)) && ($GLOBALS['GET_OPTION_LOOP']==0) && (function_exists('get_option')) && (get_option('detect_lang_browser',true)=='1'))
		{
			// In browser?
			$lang=get_lang_browser();
			if ($lang!==NULL)
			{
				$USER_LANG_EARLY_CACHED=$lang;
				return $lang;
			}
		}

		$lang=get_site_default_lang();
		$USER_LANG_EARLY_CACHED=$lang;
		return $lang; // Booting up and we don't know the user yet
	}
	$USER_LANG_LOOP=1;

	// In URL?
	if (($lang!='') && (does_lang_exist($lang)))
	{
		$USER_LANG_CACHED=$lang;
	} else
	{
		if (((get_forum_type()=='ocf') || (get_option('detect_lang_forum',true)=='1') || (get_option('detect_lang_browser',true)=='1')) && ((!$GLOBALS['DEV_MODE']) || (get_site_default_lang()!='Gibb')))
		{
			// In forum?
			if (($USER_LANG_CACHED===NULL) && (get_option('detect_lang_forum',true)=='1'))
			{
				$USER_LANG_CACHED=get_lang_forum_user(get_member());
			}
			if (($USER_LANG_CACHED===NULL) && (get_option('detect_lang_browser',true)=='1'))
			{
				$USER_LANG_CACHED=get_lang_browser();
			}
		}
	}

	if ($USER_LANG_CACHED===NULL)
	{
		$USER_LANG_CACHED=get_site_default_lang();
	}
	$USER_LANG_LOOP=0;
	return $USER_LANG_CACHED;
}

/**
 * Get the closest fit language code to what the browser is requesting.
 *
 * @return ?LANGUAGE_NAME The closest-fit language to what the browser wants (NULL: browser doesn't ask)
 */
function get_lang_browser()
{
	// In browser?
	if (array_key_exists('HTTP_ACCEPT_LANGUAGE',$_SERVER)) $http_lang=$_SERVER['HTTP_ACCEPT_LANGUAGE'];
	elseif (array_key_exists('HTTP_ACCEPT_LANGUAGE',$_ENV)) $http_lang=$_ENV['HTTP_ACCEPT_LANGUAGE'];
	else $http_lang='';
	if (strlen($http_lang)>0)
	{
		$http_langs=explode(',',$http_lang);
		foreach ($http_langs as $lang)
		{
			// Clean up
			$lang=strtoupper(trim($lang));
			$pos=strpos($lang,';');
			if ($pos!==false) $lang=substr($lang,0,$pos);
			$pos=strpos($lang,'-');
			if ($pos!==false) $lang=substr($lang,0,$pos);

			if (does_lang_exist($lang))
			{
				return $lang;
			}
		}
	}

	return NULL;
}

/**
 * Find whether the specified language exists.
 *
 * @param  LANGUAGE_NAME	The language
 * @return boolean			Whether the language exists
 */
function does_lang_exist($lang)
{
	if ($lang=='') return false;
	if ($lang=='Gibb') return true; // Test language
	if ($lang=='xxx') return true; // Test language

	$file_a=get_file_base().'/lang/'.$lang;
	$file_b=get_custom_file_base().'/lang_custom/'.$lang;
	$file_c=get_file_base().'/lang_custom/'.$lang;

	return is_dir($file_a) || is_dir($file_b) || is_dir($file_c);
}

/**
 * Get the site's default language, with support for URL overrides.
 *
 * @return LANGUAGE_NAME  The site's default language
 */
function get_site_default_lang()
{
	// Site default then
	global $SITE_INFO;
	if (!array_key_exists('default_lang',$SITE_INFO)) // We must be installing
	{
		global $IN_MINIKERNEL_VERSION;
		if ($IN_MINIKERNEL_VERSION==1)
		{
			if (array_key_exists('lang',$_POST)) return $_POST['lang'];
			if (array_key_exists('lang',$_GET)) return $_GET['lang'];
		}
		return fallback_lang();
	}
	return $SITE_INFO['default_lang'];
}

/**
 * Get what language the given member uses. The language is sent through a mapping to ensure it is in the right format, or dumped if it will not map.
 *
 * @param  MEMBER				The member id
 * @return ?LANGUAGE_NAME	The language used by the member (NULL: the language will not map)
 */
function get_lang_forum_user($member)
{
	// In forum?
	$lang=$GLOBALS['FORUM_DRIVER']->forum_get_lang($member);
	if ((!is_null($lang)) && (strlen($lang)>0))
	{
		$_lang=strtoupper($lang);
		if ((!is_dir(get_file_base().'/lang/'.$_lang)) && (!is_dir(get_custom_file_base().'/lang_custom/'.$_lang)) && (!is_dir(get_file_base().'/lang_custom/'.$_lang)))
		{
			$map_file_a=get_file_base().'/lang/map.ini';
			$map_file_b=get_custom_file_base().'/lang_custom/map.ini';
			if (!is_file($map_file_b)) $map_file_b=$map_file_a;
			$map=better_parse_ini_file($map_file_b);
			if (!array_key_exists($lang,$map))
			{
				//fatal_exit('The specified language ('.$lang.') is missing. The language needs installing/creating in ocPortal, or the language map file needs updating (to map this language to a known ocPortal one), or both.');
				$_lang=NULL; // Instead of the above, let's just fallback to default! So people's weird forum integration doesn't make ocPortal die
			} else
			{
				$_lang=$map[$lang];
				if ((!is_dir(get_file_base().'/lang/'.$_lang)) && (!is_dir(get_custom_file_base().'/lang_custom/'.$_lang)) && (!is_dir(get_file_base().'/lang_custom/'.$_lang)))
					$_lang=NULL;
			}
		}
		if (!is_null($_lang)) return $_lang;
	}

	return NULL;
}

/**
 * Get the current language.
 * First it tries to get the GET or POST language values, then it tries the user's language, then site default, then it resorts to EN.
 *
 * @param  ?MEMBER			The member id (NULL: site default language, although better just to call get_site_default_lang directly)
 * @return LANGUAGE_NAME	The current language
 */
function get_lang($member=NULL)
{
	if ($member!==NULL)
	{
		if ($member==get_member()) return user_lang();

		$lang=get_lang_forum_user($member);
		if ($lang!==NULL) return $lang;
	}

	return get_site_default_lang();
}

/**
 * Includes a language file for use in the script.
 * If $type is not null, then this specifies whether to use 'lang_custom' or 'custom' (otherwise, normal priorities occur).
 *
 * @param  ID_TEXT			The language file name
 * @param  ?LANGUAGE_NAME	The language (NULL: uses the current language)
 * @param  ?string			The language type (lang_custom, or custom) (NULL: normal priorities are used)
 * @set    lang_custom custom
 * @param  boolean			Whether to just return if there was a loading error
 */
function require_lang($codename,$lang=NULL,$type=NULL,$ignore_errors=false) // $type is for efficiency only - to avoid needing to doubly-search when requiring all
{
	// So we can keep track of what code loads what langs
	global $LANGS_REQUESTED,$LANG_REQUESTED_LANG,$REQUIRE_LANG_LOOP,$PAGE_CACHE_LAZY_LOAD,$PAGE_CACHE_LANGS_REQUESTED,$LANG_LOADED_LANG,$LANGUAGE;
	$LANGS_REQUESTED[$codename]=1;

	if ($lang===NULL) $lang=user_lang();

	if ((isset($LANG_REQUESTED_LANG[$lang])) && (isset($LANG_REQUESTED_LANG[$lang][$codename])))
		return;
	$LANG_REQUESTED_LANG[$lang][$codename]=1;

	$cfb=get_custom_file_base();
	$fb=get_file_base();
	$codename=filter_naughty($codename);

	if ($PAGE_CACHE_LAZY_LOAD)
	{
		global $SITE_INFO;
		$support_smart_decaching=(!isset($SITE_INFO['disable_smart_decaching'])) || ($SITE_INFO['disable_smart_decaching']=='0');
		if ($support_smart_decaching)
		{
			$cache_path=$cfb.'/lang_cached/'.$lang.'/'.$codename.'.lcd';
			$lang_file_default=$fb.'/lang/'.$lang.'/'.$codename.'.ini';
			if (!is_file($lang_file_default))
			{
				$lang_file_default=$fb.'/lang/'.fallback_lang().'/'.$codename.'.ini';
			}
			$lang_file=$fb.'/lang_custom/'.$lang.'/'.$codename.'.ini';
			if (!is_file($lang_file))
			{
				$lang_file=$fb.'/lang_custom/'.$lang.'/'.$codename.'.po';
				if (!is_file($lang_file))
				{
					$lang_file=$fb.'/lang_custom/'.$lang.'/'.filter_naughty($codename).'-'.strtolower($lang).'.po';
					if (!is_file($lang_file))
					{
						$lang_file=$lang_file_default;
					} else
					{
						if (!is_file($lang_file_default)) $lang_file_default=$lang_file;
					}
				} else
				{
					if (!is_file($lang_file_default)) $lang_file_default=$lang_file;
				}
			} else
			{
				if (!is_file($lang_file_default)) $lang_file_default=$lang_file;
			}
		}
		if ((!$support_smart_decaching) || ((is_file($cache_path)) && (is_file($lang_file)) && (@/*race conditions*/filemtime($cache_path)>filemtime($lang_file)) && (@/*race conditions*/filemtime($cache_path)>filemtime($lang_file_default))))
		{
			if ($lang===NULL) $lang=user_lang();
			$PAGE_CACHE_LANGS_REQUESTED[]=array($codename,$lang);
			return;
		} else // Invalidate it, as our .lcd cache was dirty
		{
			global $PAGE_CACHE_FILE;
			open_page_cache_file();
			$LANGUAGE=array();
			@rewind($PAGE_CACHE_FILE);
			@flock($PAGE_CACHE_FILE,LOCK_EX);
			@ftruncate($PAGE_CACHE_FILE,0);
			@flock($PAGE_CACHE_FILE,LOCK_UN);
			$PAGE_CACHE_LAZY_LOAD=false;
			$LANG_LOADED_LANG=array();
			$PAGE_CACHE_LANGS_REQUESTED[]=array($codename,$lang);

			foreach ($PAGE_CACHE_LANGS_REQUESTED as $request)
			{
				list($that_codename,$that_lang)=$request;
				unset($LANG_REQUESTED_LANG[$that_lang][$that_codename]);
				require_lang($that_codename,$that_lang,NULL,$ignore_errors);
			}
		}
	}

	if ((isset($LANG_LOADED_LANG[$lang])) && (isset($LANG_LOADED_LANG[$lang][$codename])))
		return;

	$REQUIRE_LANG_LOOP++;

	if ((function_exists('memory_get_usage')) && (isset($_GET['keep_show_loading'])) && ($_GET['keep_show_loading']=='1'))
	{
		print('<!-- require_lang: '.htmlentities($codename).' ('.integer_format(memory_get_usage()).' before) -->'."\n");
		flush();
	}

	$bad=false;
	$done=false;

	if (!isset($LANGUAGE[$lang])) $LANGUAGE[$lang]=array();

	$cache_path=$cfb.'/lang_cached/'.$lang.'/'.$codename.'.lcd';

	// Try lang_cached
	if ($GLOBALS['MEM_CACHE']!==NULL)
	{
		$desire_cache=true;
		global $SITE_INFO;
		$support_smart_decaching=(!isset($SITE_INFO['disable_smart_decaching'])) || ($SITE_INFO['disable_smart_decaching']=='0');
		if ($support_smart_decaching)
		{
			$lang_file=$fb.'/lang/'.$lang.'/'.$codename.'.ini';
			if (!is_file($lang_file))
			{
				$lang_file=$fb.'/lang/'.$lang.'/'.$codename.'.po';
				if (!is_file($lang_file)) $lang_file=$fb.'/lang/'.$lang.'/'.filter_naughty($codename).'-'.strtolower($lang).'.po';
			}
			$pcache=persistent_cache_get(array('LANG',$lang,$codename),is_file($lang_file)?filemtime($lang_file):NULL);
		} else
		{
			$pcache=persistent_cache_get(array('LANG',$lang,$codename));
		}
		if (is_array($pcache))
		{
			$LANGUAGE[$lang]+=$pcache;
			$done=true;
		}
	} else
	{
		$desire_cache=(function_exists('get_option')) && ((get_option('is_on_lang_cache',true)=='1') || (get_param_integer('keep_cache',0)==1) || (get_param_integer('cache',0)==1)) && (get_param_integer('keep_cache',NULL)!==0) && (get_param_integer('cache',NULL)!==0);
		if ($desire_cache)
		{
			$cache_path=$cfb.'/lang_cached/'.$lang.'/'.$codename.'.lcd';
			$lang_file_default=$fb.'/lang/'.$lang.'/'.$codename.'.ini';
			if (!is_file($lang_file_default))
			{
				$lang_file_default=$fb.'/lang/'.fallback_lang().'/'.$codename.'.ini';
			}
			$lang_file=$fb.'/lang_custom/'.$lang.'/'.$codename.'.ini';
			if (!is_file($lang_file))
			{
				$lang_file=$fb.'/lang_custom/'.$lang.'/'.$codename.'.po';
				if (!is_file($lang_file))
				{
					$lang_file=$fb.'/lang_custom/'.$lang.'/'.filter_naughty($codename).'-'.strtolower($lang).'.po';
					if (!is_file($lang_file))
					{
						$lang_file=$lang_file_default;
					}
				}
			}

			if (!is_file($lang_file_default)) $lang_file_default=$lang_file;
			if ((is_file($cache_path)) && ((!is_file($lang_file)) || ((@/*race conditions*/filemtime($cache_path)>filemtime($lang_file)) && (@/*race conditions*/filemtime($cache_path)>filemtime($lang_file_default)))))
			{
				$tmp=@file_get_contents($cache_path);
				if ($tmp!='')
				{
					$unserialized=@unserialize($tmp);
					if ($unserialized!==false)
					{
						$LANGUAGE[$lang]+=$unserialized;
						$done=true;
					}
				}
			}
		}
	}
	if (!$done)
	{
		if (($desire_cache) && (is_file($cache_path))) // Must have been dirty cache, so we need to kill compiled templates too (as lang is compiled into them)
		{
			require_code('view_modes');
			global $ERASED_TEMPLATES_ONCE;
			if (!$ERASED_TEMPLATES_ONCE)
				erase_cached_templates();
		}

		require_code('lang_compile');
		$bad=$bad || require_lang_compile($codename,$lang,$type,$cache_path,$ignore_errors);
	}

	global $LANG_LOADED;
	$LANG_LOADED[$codename]=$type;

	if (!isset($LANG_LOADED_LANG[$lang])) $LANG_LOADED_LANG[$lang]=array();
	$LANG_LOADED_LANG[$lang][$codename]=1;

	$REQUIRE_LANG_LOOP--;
}

/**
 * Include all the language files for use in the script.
 * NOTE: This may reduce performance, so you should only use it if you really have to.
 *
 * @param ?LANGUAGE_NAME 	The language to include files from (NULL: use current users language).
 * @param boolean			 	Only load it up if it is specifically defined for our language.
 */
function require_all_lang($lang=NULL,$only_if_for_lang=false)
{
	$GLOBALS['SITE_INFO']['disable_smart_decaching']='1'; // We'll temporarily set this to stop hundreds of disk checks happening

	if (is_null($lang))
	{
		global $REQUIRED_ALL_LANG;
		if (array_key_exists($lang,$REQUIRED_ALL_LANG)) return;
		$REQUIRED_ALL_LANG[$lang]=true;
	}

	if (is_null($lang)) $lang=user_lang();

	require_code('lang2');

	$lang_files=get_lang_files(fallback_lang());

	foreach (array_keys($lang_files) as $file)
	{
		if ((!$only_if_for_lang) || (is_file(get_custom_file_base().'/lang_custom/'.$lang.'/'.$file.'.ini')) || (is_file(get_custom_file_base().'/lang/'.$lang.'/'.$file.'.ini')) || (is_file(get_custom_file_base().'/lang_custom/'.$lang.'/'.$file.'.po')) || (is_file(get_custom_file_base().'/lang_custom/'.$lang.'/'.$file.'-'.strtolower($lang).'.po')))
			require_lang($file,$lang,NULL,true);
	}
}

/**
 * Convert the specified language codename to the default content, and return the language key.
 *
 * @param  ID_TEXT		The language codename
 * @param  boolean		Whether the given codes value is to be parsed as comcode
 * @param  integer		The level of importance this language string holds
 * @return integer		The language ID
 */
function lang_code_to_default_content($code,$comcode=false,$level=2)
{
	$lang_key=insert_lang(do_lang($code),$level,NULL,$comcode);
	$langs=find_all_langs();
	foreach ($langs as $lang=>$lang_type)
	{
		if ($lang!=user_lang())
		{
			if (is_file(get_file_base().'/'.$lang_type.'/'.$lang.'/critical_error.ini')) // Make sure it's a reasonable looking pack, not just a stub (Google Translate addon can be made to go nuts otherwise)
				insert_lang(do_lang($code,'','','',$lang),$level,NULL,true,$lang_key,$lang);
		}
	}
	return $lang_key;
}

/**
 * Require all the open language files. This doesn't hurt performance a lot.
 *
 * @param  ?LANGUAGE_NAME  The language to require open files from (NULL: uses the current language)
 */
function require_all_open_lang_files($lang=NULL)
{
	global $PAGE_CACHE_LAZY_LOAD,$LANG_REQUESTED_LANG,$LANGS_REQUESTED;
	$PAGE_CACHE_LAZY_LOAD=false;
	$LANG_REQUESTED_LANG=array(); // So require_lang will do a re-load
	$langs_requested_copy=$LANGS_REQUESTED;
	foreach (array_keys($langs_requested_copy) as $toload)
	{
		require_lang($toload,$lang,NULL,true);
	}
	// Block cacheing might have hidden that we loaded these
	require_lang('global',$lang,NULL,true);
	require_lang('critical_error',$lang,NULL,true);
}

/**
 * URL'ise specially encoded text-acceptance language strings.
 *
 * @param  string			The language string
 * @param  mixed			The URL (either tempcode or string)
 * @param  string			The title of the hyperlink
 * @param  boolean		Whether to use a new window
 * @return tempcode		The encoded version
 */
function urlise_lang($string,$url,$title='',$new_window=false)
{
	require_code('lang_urlise');
	return _urlise_lang($string,$url,$title,$new_window);
}

/**
 * Stop some text being escapable by the Tempcode layer.
 *
 * @param  mixed			Text
 * @return tempcode		Text that can't be escaped
 */
function protect_from_escaping($in)
{
	return do_lang_tempcode('dont_escape_trick',$in);
}

/**
 * Get the human-readable form of a language id, or a language entry from a language INI file.
 *
 * @param  ID_TEXT		The language id
 * @param  ?mixed			The first token [string or tempcode] (replaces {1}) (NULL: none)
 * @param  ?mixed			The second token [string or tempcode] (replaces {2}) (NULL: none)
 * @param  ?mixed			The third token (replaces {3}). May be an array of [of string], to allow any number of additional args (NULL: none)
 * @param  ?LANGUAGE_NAME The language to use (NULL: users language)
 * @param  boolean		Whether to cause ocPortal to exit if the lookup does not succeed
 * @return ?mixed			The human-readable content (NULL: not found). String normally. Tempcode if tempcode parameters.
 */
function _do_lang($codename,$token1=NULL,$token2=NULL,$token3=NULL,$lang=NULL,$require_result=true)
{
	global $LANGUAGE,$USER_LANG_CACHED,$RECORD_LANG_STRINGS,$XSS_DETECT,$PAGE_CACHE_FILE,$PAGE_CACHE_LANG_LOADED;

	if ($lang===NULL)
	{
		$lang=($USER_LANG_CACHED===NULL)?user_lang():$USER_LANG_CACHED;
	}// else // This else assumes we initially load all language files in the users language. Reasonable. EDIT: Actually, no it is not - the user_lang() initially is not accurate until ocPortal gets past a certain startup position
	{
		if ($GLOBALS['SEMI_DEV_MODE']) // Special syntax for easily inlining language strings
		{
			$pos=strpos($codename,'=');
			if ($pos!==false)
			{
				// Find loaded file with smallest levenstein distance to current page
				$best=mixed();
				$best_for=NULL;
				global $LANGS_REQUESTED;
				foreach (array_keys($LANGS_REQUESTED) as $possible)
				{
					$dist=levenshtein(get_page_name(),$possible);
					if ((is_null($best)) || ($best>$dist))
					{
						$best=$dist;
						$best_for=$possible;
					}
				}
				$save_path=get_file_base().'/lang/'.fallback_lang().'/'.$best_for.'.ini';
				if (!is_file($save_path))
					$save_path=get_file_base().'/lang_custom/'.fallback_lang().'/'.$best_for.'.ini';
				// Tack language strings onto this file
				list($codename,$value)=explode('=',$codename,2);
				$myfile=fopen($save_path,'at');
				fwrite($myfile,"\n".$codename.'='.$value);
				fclose($myfile);
				// Fake-load the string
				$LANGUAGE[$lang][$codename]=$value;
				// Go through all required files, doing a string replace if needed
				$included_files=get_included_files();
				foreach ($included_files as $inc)
				{
					$orig_contents=file_get_contents($inc);
					$contents=str_replace("'".$codename.'='.$value."'","'".$codename."'",$orig_contents);
					if ($orig_contents!=$contents)
					{
						$myfile=fopen($inc,'at');
						flock($myfile,LOCK_EX);
						ftruncate($myfile,0);
						fwrite($myfile,$contents);
						flock($myfile,LOCK_UN);
						fclose($myfile);
					}
				}
			}
		}

		$there=isset($LANGUAGE[$lang][$codename]);

		if (!$there)
		{
			$pos=strpos($codename,':');
			if ($pos!==false)
			{
				require_lang(substr($codename,0,$pos),NULL,NULL,!$require_result);
				$codename=substr($codename,$pos+1);
			}

			$there=isset($LANGUAGE[$lang][$codename]);
		}

		if ($RECORD_LANG_STRINGS)
		{
			global $RECORDED_LANG_STRINGS;
			$RECORDED_LANG_STRINGS[$codename]=1;
		}

		if ((!$there) && ((!isset($LANGUAGE[$lang])) || (!array_key_exists($codename,$LANGUAGE[$lang]))))
		{
			global $PAGE_CACHE_LAZY_LOAD,$PAGE_CACHE_LANGS_REQUESTED,$LANG_REQUESTED_LANG;

			if ($PAGE_CACHE_LAZY_LOAD)
			{
				$PAGE_CACHE_LAZY_LOAD=false; // We can't be lazy any more, but we will keep growing our pool so hopefully CAN be lazy the next time
				foreach ($PAGE_CACHE_LANGS_REQUESTED as $request)
				{
					list($that_codename,$that_lang)=$request;
					unset($LANG_REQUESTED_LANG[$that_lang][$that_codename]);
					require_lang($that_codename,$that_lang,NULL,true);
				}
				$ret=_do_lang($codename,$token1,$token2,$token3,$lang,$require_result);
				if ($ret===NULL)
				{
					$PAGE_CACHE_LANG_LOADED[$lang][$codename]=NULL;
					if ($GLOBALS['MEM_CACHE']!==NULL)
					{
						persistent_cache_set($PAGE_CACHE_FILE,$PAGE_CACHE_LANG_LOADED);
					} else
					{
						open_page_cache_file();
						@rewind($PAGE_CACHE_FILE);
						@flock($PAGE_CACHE_FILE,LOCK_EX);
						@ftruncate($PAGE_CACHE_FILE,0);
						@fwrite($PAGE_CACHE_FILE,serialize($PAGE_CACHE_LANG_LOADED));
						@flock($PAGE_CACHE_FILE,LOCK_UN);
					}
				}
				return $ret;
			}

			require_all_open_lang_files($lang);
		}
	}

	if ($lang=='xxx') return 'xxx'; // Helpful for testing language compliancy. We don't expect to see non x's if we're running this language

	if ((!isset($LANGUAGE[$lang][$codename])) && (($require_result) || (!isset($LANGUAGE[$lang])) || (!array_key_exists($codename,$LANGUAGE[$lang]))))
	{
		if ($lang!=fallback_lang())
		{
			$ret=do_lang($codename,$token1,$token2,$token3,fallback_lang(),$require_result);

			if ($PAGE_CACHE_FILE!==NULL)
			{
				if ((!isset($PAGE_CACHE_LANG_LOADED[$lang][$codename])) && (isset($PAGE_CACHE_LANG_LOADED[fallback_lang()][$codename])))
				{
					$PAGE_CACHE_LANG_LOADED[$lang][$codename]=$PAGE_CACHE_LANG_LOADED[fallback_lang()][$codename]; // Will have been cached into fallback_lang() from the nested do_lang call, we need to copy it into our cache bucket for this language
					if ($GLOBALS['MEM_CACHE']!==NULL)
					{
						persistent_cache_set($PAGE_CACHE_FILE,$PAGE_CACHE_LANG_LOADED);
					} else
					{
						open_page_cache_file();
						@rewind($PAGE_CACHE_FILE);
						@flock($PAGE_CACHE_FILE,LOCK_EX);
						@ftruncate($PAGE_CACHE_FILE,0);
						@fwrite($PAGE_CACHE_FILE,serialize($PAGE_CACHE_LANG_LOADED));
						@flock($PAGE_CACHE_FILE,LOCK_UN);
					}
				}
			}

			return $ret;
		} else
		{
			if ($require_result)
			{
				global $USER_LANG_LOOP,$REQUIRE_LANG_LOOP;
				//print_r(debug_backtrace());
				if ($USER_LANG_LOOP==1) critical_error('RELAY','Missing language code: '.escape_html($codename).'. This language code is required to produce error messages, and thus a critical error was prompted by the non-ability to show less-critical error messages. It is likely the source language files (lang/'.fallback_lang().'/*.ini) for ocPortal on this website have been corrupted.');
				if ($REQUIRE_LANG_LOOP>=2) return ''; // Probably failing to load global.ini, so just output with some text missing
				require_code('view_modes');
				erase_cached_language();
				attach_message(do_lang_tempcode('MISSING_LANG_ENTRY',escape_html($codename)),'warn');
				return '';
			} else
			{
				return NULL;
			}
		}
	}

	if ($PAGE_CACHE_FILE!==NULL)
	{
		if ((!isset($PAGE_CACHE_LANG_LOADED[$lang][$codename])) && ((!isset($PAGE_CACHE_LANG_LOADED[$lang])) || (!array_key_exists($codename,$PAGE_CACHE_LANG_LOADED[$lang]))))
		{
			$PAGE_CACHE_LANG_LOADED[$lang][$codename]=$LANGUAGE[$lang][$codename];
			if ($GLOBALS['MEM_CACHE']!==NULL)
			{
				persistent_cache_set($PAGE_CACHE_FILE,$PAGE_CACHE_LANG_LOADED);
			} else
			{
				open_page_cache_file();
				@rewind($PAGE_CACHE_FILE);
				@flock($PAGE_CACHE_FILE,LOCK_EX);
				@ftruncate($PAGE_CACHE_FILE,0);
				@fwrite($PAGE_CACHE_FILE,serialize($PAGE_CACHE_LANG_LOADED));
				@flock($PAGE_CACHE_FILE,LOCK_UN);
			}
		}
	}

	// Put in parameters
	static $non_plural_non_vowel=array('1','b','c','d','f','g','h','j','k','l','m','n','p','q','r','s','t','v','w','x','y','z','{'/*for no-op param usage*/);
	$looked_up=$LANGUAGE[$lang][$codename];
	if ($looked_up===NULL)
	{
		return NULL; // Learning cache pool has told us this string definitely does not exist
	}
	$out=str_replace('\n',"\n",$looked_up);
	$plural_or_vowel_check=strpos($out,'|')!==false;
	if ($XSS_DETECT) ocp_mark_as_escaped($out);
	if ($token1!==NULL)
	{
		if (((is_object($token1)) && ($token2===NULL)) || (($token2!==NULL) && (is_object($token2)))) // Tempcode only supported in first two
		{
			$bits=preg_split('#\{\d[^\}]*\}#',$out,2,PREG_SPLIT_OFFSET_CAPTURE);

			$ret=new ocp_tempcode();
			foreach ($bits as $bit)
			{
				if ($XSS_DETECT) ocp_mark_as_escaped($bit[0]);

				$at=$bit[1];

				if ($at!=0)
				{
					if ($out[$at-2]=='1') $ret->attach($token1);
					elseif ($out[$at-2]=='2') $ret->attach($token2);
					elseif (($plural_or_vowel_check) && (substr($out[$at-2],0,2)=='1|'))
					{
						$exploded=explode('|',$out[$at-2]);
						$_token=$token1->evaluate();
						$_token_denum=str_replace(',','',$_token);
						$ret->attach((in_array(is_numeric($_token_denum)?$_token_denum:ocp_mb_strtolower(ocp_mb_substr($_token,0,1)),$non_plural_non_vowel))?$exploded[1]:$exploded[2]);
					}
					elseif (($plural_or_vowel_check) && (substr($out[$at-2],0,2)=='2|'))
					{
						$exploded=explode('|',$out[$at-2]);
						$_token=$token2->evaluate();
						$_token_denum=str_replace(',','',$_token);
						$ret->attach((in_array(is_numeric($_token_denum)?$_token_denum:ocp_mb_strtolower(ocp_mb_substr($_token,0,1)),$non_plural_non_vowel))?$exploded[1]:$exploded[2]);
					}
				}
				$ret->attach($bit[0]);
			}

			return $ret;
		} elseif ($token1!==NULL)
		{
			$out=str_replace('{1}',$token1,$out);
			if ($plural_or_vowel_check)
			{
				$_token_denum=str_replace(',','',$token1);
				$out=preg_replace('#\{1\|(.*)\|(.*)\}#U',(in_array(is_numeric($_token_denum)?$_token_denum:ocp_mb_strtolower(ocp_mb_substr($token1,0,1)),$non_plural_non_vowel))?'\\1':'\\2',$out);
			}
			if (($XSS_DETECT) && (ocp_is_escaped($token1))) ocp_mark_as_escaped($out);
		}

		if ($token2!==NULL)
		{
			if ($XSS_DETECT) $escaped=ocp_is_escaped($out);
			$out=str_replace('{2}',$token2,$out);
			if ($plural_or_vowel_check)
			{
				$_token_denum=str_replace(',','',$token1);
				$out=preg_replace('#\{2\|(.*)\|(.*)\}#U',(in_array(is_numeric($_token_denum)?$_token_denum:ocp_mb_strtolower(ocp_mb_substr($token2,0,1)),$non_plural_non_vowel))?'\\1':'\\2',$out);
			}
			if (($XSS_DETECT) && (ocp_is_escaped($token2)) && ($escaped)) ocp_mark_as_escaped($out);

			if ($token3!==NULL)
			{
				$i=3;
				if (!is_array($token3)) $token3=array($token3);
				foreach ($token3 as $token)
				{
					if ($XSS_DETECT) $escaped=ocp_is_escaped($out);
					$out=str_replace('{'.strval($i).'}',$token,$out);
					if ($plural_or_vowel_check)
					{
						$_token_denum=str_replace(',','',$token);
						$out=preg_replace('#\{'.strval($i).'\|(.*)\|(.*)\}#U',(in_array(is_numeric($_token_denum)?$_token_denum:ocp_mb_strtolower(ocp_mb_substr($token,0,1)),$non_plural_non_vowel))?'\\1':'\\2',$out);
					}
					if (($XSS_DETECT) && (ocp_is_escaped($token)) && ($escaped)) ocp_mark_as_escaped($out);
					$i++;
				}
			}
		}
	}
	return $out;
}

/**
 * Get an array of all the installed languages that can be found in root/lang/ and root/lang_custom/
 *
 * @param  boolean			Whether to even find empty languages
 * @return array				The installed languages (map, lang=>type)
 */
function find_all_langs($even_empty_langs=false)
{
	require_code('lang3');
	return _find_all_langs($even_empty_langs);
}

/**
 * Get a nice formatted XHTML listed language selector.
 *
 * @param  ?LANGUAGE_NAME	The language to have selected by default (NULL: uses the current language)
 * @param  boolean			Whether to show languages that have no language details currently defined for them
 * @return tempcode			The language selector
 */
function nice_get_langs($select_lang=NULL,$show_unset=false)
{
	require_code('lang3');
	return _nice_get_langs($select_lang,$show_unset);
}

// =======
// CONTENT
// =======

/**
 * Insert a comcode language entry into the translation table, and returns the id.
 *
 * @param  string			The text
 * @param  integer		The level of importance this language string holds
 * @set    1 2 3 4
 * @param  ?object		The database connection to use (NULL: standard site connection)
 * @param  boolean		Whether to insert it as an admin (any comcode parsing will be carried out with admin privileges)
 * @param  ?string		The special identifier for this lang code on the page it will be displayed on; this is used to provide an explicit binding between languaged elements and greater templated areas (NULL: none)
 * @param  integer		Comcode parser wrap position
 * @param  boolean		Whether to generate a fatal error if there is invalid Comcode
 * @param  boolean		Whether we are saving as a 'volatile' file extension (used in the XML DB driver, to mark things as being non-syndicated to subversion)
 * @return integer		The id of the newly added language entry
 */
function insert_lang_comcode($text,$level,$connection=NULL,$insert_as_admin=false,$pass_id=NULL,$wrap_pos=60,$preparse_mode=true,$save_as_volatile=false)
{
	if (is_null($connection)) $connection=$GLOBALS['SITE_DB'];

	return insert_lang($text,$level,$connection,true,NULL,NULL,$insert_as_admin,$pass_id,NULL,$wrap_pos,$preparse_mode,$save_as_volatile);
}

/**
 * Insert a language entry into the translation table, and returns the id.
 *
 * @param  string				The text
 * @param  integer			The level of importance this language string holds
 * @set    1 2 3 4
 * @param  ?object			The database connection to use (NULL: standard site connection)
 * @param  boolean			Whether it is to be parsed as comcode
 * @param  ?integer			The id to use for the language entry (NULL: work out next available)
 * @param  ?LANGUAGE_NAME	The language (NULL: uses the current language)
 * @param  boolean			Whether to insert it as an admin (any comcode parsing will be carried out with admin privileges)
 * @param  ?string			The special identifier for this lang code on the page it will be displayed on; this is used to provide an explicit binding between languaged elements and greater templated areas (NULL: none)
 * @param  ?string			Assembled Tempcode portion (NULL: work it out)
 * @param  integer			Comcode parser wrap position
 * @param  boolean			Whether to generate a fatal error if there is invalid Comcode
 * @param  boolean			Whether we are saving as a 'volatile' file extension (used in the XML DB driver, to mark things as being non-syndicated to subversion)
 * @return integer			The id of the newly added language entry
 */
function insert_lang($text,$level,$connection=NULL,$comcode=false,$id=NULL,$lang=NULL,$insert_as_admin=false,$pass_id=NULL,$text2=NULL,$wrap_pos=60,$preparse_mode=true,$save_as_volatile=false)
{
	require_code('lang3');
	return _insert_lang($text,$level,$connection,$comcode,$id,$lang,$insert_as_admin,$pass_id,$text2,$wrap_pos,$preparse_mode,$save_as_volatile);
}

/**
 * Remap the specified comcode language id, and return the id again - the id isn't changed.
 *
 * @param  integer		The language entries id
 * @param  string			The text to remap to
 * @param  ?object		The database connection to use (NULL: standard site connection)
 * @param  ?string		The special identifier for this lang code on the page it will be displayed on; this is used to provide an explicit binding between languaged elements and greater templated areas (NULL: none)
 * @param  ?MEMBER		The member performing the change (NULL: current member)
 * @param  boolean		Whether to generate Comcode as arbitrary admin
 * @return integer		The language entries id
 */
function lang_remap_comcode($id,$text,$connection=NULL,$pass_id=NULL,$source_member=NULL,$as_admin=false)
{
	if (is_null($connection)) $connection=$GLOBALS['SITE_DB'];

	return lang_remap($id,$text,$connection,true,$pass_id,$source_member,$as_admin);
}

/**
 * Remap the specified language id, and return the id again - the id isn't changed.
 *
 * @param  integer		The language entries id
 * @param  string			The text to remap to
 * @param  ?object		The database connection to use (NULL: standard site connection)
 * @param  boolean		Whether it is to be parsed as comcode
 * @param  ?string		The special identifier for this lang code on the page it will be displayed on; this is used to provide an explicit binding between languaged elements and greater templated areas (NULL: none)
 * @param  ?MEMBER		The member performing the change (NULL: current member)
 * @param  boolean		Whether to generate Comcode as arbitrary admin
 * @param  boolean		Whether to backup the language string before changing it
 * @return integer		The language entries id
 */
function lang_remap($id,$text,$connection=NULL,$comcode=false,$pass_id=NULL,$source_member=NULL,$as_admin=false,$backup_string=false)
{
	require_code('lang3');
	return _lang_remap($id,$text,$connection,$comcode,$pass_id,$source_member,$as_admin,$backup_string);
}

/**
 * Delete the specified language entry from the translation table.
 *
 * @param  integer		The id
 * @param  ?object		The database connection to use (NULL: standard site connection)
 */
function delete_lang($id,$connection=NULL)
{
	if (is_null($connection)) $connection=$GLOBALS['SITE_DB'];
	$connection->query_delete('translate',array('id'=>$id));
}

/**
 * This function is an offshoot of get_translated_text, it instead returns parsed comcode that is linked to the specified language id.
 *
 * @param  integer			The id
 * @param  ?object			The database connection to use (NULL: standard site connection)
 * @param  ?LANGUAGE_NAME	The language (NULL: uses the current language)
 * @param  boolean			Whether to force it to the specified language
 * @param  boolean			Whether to force as_admin, even if the lang string isn't stored against an admin (designed for comcode page cacheing)
 * @param  boolean			Whether to remove from the Tempcode cache when we're done, for performance reasons (normally don't bother with this, but some code knows it won't be needed again -- esp Comcode cache layer -- and saves RAM by removing it)
 * @return ?tempcode			The parsed comcode (NULL: the text couldn't be looked up)
 */
function get_translated_tempcode($entry,$connection=NULL,$lang=NULL,$force=false,$as_admin=false,$clear_away_from_cache=false)
{
	if ($entry==0) return paragraph(do_lang_tempcode('FAILED_ENTRY'),'rtgtedgrgd');

	if ($connection===NULL) $connection=$GLOBALS['SITE_DB'];

	global $RECORD_LANG_STRINGS_CONTENT;
	if ($RECORD_LANG_STRINGS_CONTENT)
	{
		global $RECORDED_LANG_STRINGS_CONTENT;
		$RECORDED_LANG_STRINGS_CONTENT[$entry]=($connection->connection_write!=$GLOBALS['SITE_DB']->connection_write);
	}

	if ($lang===NULL) $lang=user_lang();

	if ($lang=='xxx') return make_string_tempcode('!!!'); // Helpful for testing language compliancy. We don't expect to see non x's/!'s if we're running this language

	if ((isset($connection->text_lookup_cache[$entry])) && ($lang==user_lang()))
	{
		$ret=$connection->text_lookup_cache[$entry];
		if ($ret!=='')
		{
			if (is_string($ret))
			{
				$connection->text_lookup_cache[$entry]=new ocp_tempcode();
				$connection->text_lookup_cache[$entry]->from_assembly($ret);
				$ret=$connection->text_lookup_cache[$entry];
			}
			if ($clear_away_from_cache)
			{
				unset($connection->text_lookup_cache[$entry]);
				unset($connection->text_lookup_original_cache[$entry]);
			}
			return $ret;
		}
	}

	global $SEARCH__CONTENT_BITS;

	if ($SEARCH__CONTENT_BITS!==NULL) // Doing a search so we need to reparse, with highlighting on
	{
		$_result=$connection->query_select('translate',array('text_original','source_user'),array('id'=>$entry,'language'=>$lang),'',1);
		if (array_key_exists(0,$_result))
		{
			global $LAX_COMCODE;
			$temp=$LAX_COMCODE;
			$LAX_COMCODE=true;
			$result=$_result[0];

			if (get_value('really_want_highlighting')==='1')
			{
				require_code('comcode_from_html');
				$result['text_original']=force_clean_comcode($result['text_original']); // Highlighting only works with pure Comcode
			}

			$ret=comcode_to_tempcode($result['text_original'],$result['source_user'],$as_admin,60,NULL,$connection,false,false,false,false,false,$SEARCH__CONTENT_BITS);
			$LAX_COMCODE=$temp;
			return $ret;
		}
	}

	$_result=$connection->query_select('translate',array('text_parsed','text_original'),array('id'=>$entry,'language'=>$lang),'',1);
	$result=isset($_result[0])?$_result[0]['text_parsed']:NULL;
	if (isset($_result[0]))
	{
		if ($lang==user_lang())
		{
			$connection->text_lookup_original_cache[$entry]=$_result[0]['text_original'];
		}
	}

	if (($result===NULL) || ($result=='') || (is_browser_decacheing())) // Not cached
	{
		require_code('lang3');
		return parse_translated_text($entry,$connection,$lang,$force,$as_admin);
	}

	$parsed=new ocp_tempcode();
	if (!$parsed->from_assembly($result,true))
	{
		require_code('lang3');
		return parse_translated_text($entry,$connection,$lang,$force,$as_admin);
	}

	if ($lang==user_lang())
	{
		$connection->text_lookup_cache[$entry]=$parsed;
	}

	return $parsed;
}

/**
 * Try to return the human-readable version of the language id, passed in as $entry.
 *
 * @param  integer			The id
 * @param  ?object			The database connection to use (NULL: standard site connection)
 * @param  ?LANGUAGE_NAME	The language (NULL: uses the current language)
 * @param  boolean			Whether to force it to the specified language
 * @return ?string			The human-readable version (NULL: could not look up when $force was on)
 */
function get_translated_text($entry,$connection=NULL,$lang=NULL,$force=false)
{
	if ($entry==0) return do_lang('FAILED_ENTRY');

	if ($entry===NULL) fatal_exit(do_lang_tempcode('NULL_LANG_STRING'));

	if ($connection===NULL) $connection=$GLOBALS['SITE_DB'];

	global $RECORD_LANG_STRINGS_CONTENT;
	if ($RECORD_LANG_STRINGS_CONTENT)
	{
		global $RECORDED_LANG_STRINGS_CONTENT;
		$RECORDED_LANG_STRINGS_CONTENT[$entry]=($connection->connection_write!=$GLOBALS['SITE_DB']->connection_write);
	}

	if ($lang===NULL) $lang=user_lang();

	if ((isset($connection->text_lookup_original_cache[$entry])) && ($lang==user_lang()))
	{
		return $connection->text_lookup_original_cache[$entry];
	}

	if ($lang=='xxx') return '!!!'; // Helpful for testing language compliancy. We don't expect to see non x's/!'s if we're running this language
	$result=$connection->query_select('translate',array('text_original','text_parsed'),array('id'=>$entry,'language'=>$lang),'',1);
	if (!isset($result[0]))
	{
		if ($force) return NULL;

		$result=$connection->query_select('translate',array('*'),array('id'=>$entry),'',1);
		if (!isset($result[0]))
		{
			$result=$connection->query_select('translate',array('*'),array('id'=>$entry),'',1);
		}
		if (isset($result[0]))
		{
			$connection->query_insert('translate',array('broken'=>1,'language'=>$lang)+$result[0]);
		}
	}
	if (!isset($result[0]))
	{
		$member_id=function_exists('get_member')?get_member():$GLOBALS['FORUM_DRIVER']->get_guest_id();
		$connection->query_insert('translate',array('id'=>$entry,'source_user'=>$member_id,'broken'=>0,'importance_level'=>3,'text_original'=>'','text_parsed'=>'','language'=>$lang));
		$msg=do_lang('LANGUAGE_CORRUPTION',strval($entry));
		if (preg_match('#^localhost[\.\:$]#',ocp_srv('HTTP_HOST'))!=0) fatal_exit($msg);
		require_code('site');
		attach_message(make_string_tempcode($msg),'warn');
		return '';
	}
	if ($lang==user_lang())
	{
		$connection->text_lookup_original_cache[$entry]=$result[0]['text_original'];
		$connection->text_lookup_cache[$entry]=$result[0]['text_parsed'];
	}

	return $result[0]['text_original'];
}

/**
 * Convert a language string that is Comcode to tempcode, with potential cacheing in the db.
 *
 * @param  ID_TEXT		The language string ID
 * @return tempcode		The parsed Comcode
 */
function comcode_lang_string($lang_code)
{
	require_code('lang3');
	return _comcode_lang_string($lang_code);
}

/**
 * UI to choose a language.
 *
 * @param  tempcode			Title for the form
 * @param  boolean			Whether to give a tip about edit order
 * @param  boolean			Whether to add an 'all' entry to the list
 * @return mixed				The UI (tempcode) or the language to use (string/LANGUAGE_NAME)
 */
function choose_language($title,$tip=false,$allow_all_selection=false)
{
	if (!multi_lang()) return user_lang();

	$lang=either_param('lang',/*get_param('keep_lang',NULL)*/NULL);
	if (!is_null($lang)) return filter_naughty($lang);

	if (!$tip)
	{
		$text=do_lang_tempcode('CHOOSE_LANG_DESCRIP');
	} else
	{
		global $LANGS_MAP;
		if ($LANGS_MAP===NULL)
		{
			$map_a=get_file_base().'/lang/langs.ini';
			$map_b=get_custom_file_base().'/lang_custom/langs.ini';
			if (!is_file($map_b)) $map_b=$map_a;
			$LANGS_MAP=better_parse_ini_file($map_b);
		}

		$lang_name=get_site_default_lang();
		if (array_key_exists($lang_name,$LANGS_MAP)) $lang_name=$LANGS_MAP[$lang_name];

		$text=do_lang_tempcode('CHOOSE_LANG_DESCRIP_ADD_TO_MAIN_LANG_FIRST',escape_html($lang_name));
	}

	$langs=new ocp_tempcode();
	if ($allow_all_selection)
	{
		$langs->attach(form_input_list_entry('',false,do_lang_tempcode('_ALL')));
	}
	$langs->attach(nice_get_langs());
	require_code('form_templates');
	$fields=form_input_list(do_lang_tempcode('LANGUAGE'),do_lang_tempcode('DESCRIPTION_LANGUAGE'),'lang',$langs,NULL,true);

	$hidden=build_keep_post_fields();
	$url=get_self_url();

	return do_template('FORM_SCREEN',array('_GUID'=>'1a2823d450237aa299c095bf9c689a2a','SKIP_VALIDATION'=>true,'HIDDEN'=>$hidden,'SUBMIT_NAME'=>do_lang_tempcode('PROCEED'),'TITLE'=>$title,'FIELDS'=>$fields,'URL'=>$url,'TEXT'=>$text));
}
