<?php
/**
 * This file implements functions for handling locales and i18n.
 *
 * This file is part of the evoCore framework - {@link http://evocore.net/}
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
 *
 * @package evocore
 *
 * @todo Make it a class / global object!
 *        - Provide (static) functions to extract .po files / generate _global.php files (single quoted strings!)
 */
if( !defined('EVO_CONFIG_LOADED') ) die( 'Please, do not access this page directly.' );


// DEBUG: (Turn switch on or off to log debug info for specified category)
$GLOBALS['debug_locale'] = true;


// LOCALIZATION:
if( isset( $use_l10n ) && $use_l10n )
{ // We are going to use localization:

	/**
	 * TRANSLATE!
	 *
	 * Translate a text to the desired locale or to the current locale.
	 *
	 * @param string String to translate, '' to get language file info (as in gettext spec)
	 * @param string locale to translate to, '' to use current locale
	 * @param array Array containing the following keys (all optional):
	 *              - 'ext_transarray': A reference to an alternate array
	 *                                  to use for the caching of the
	 *                                  translated strings or NULL to use
	 *                                  the internal array.
	 *              - 'alt_basedir': Alternate base directory to search
	 *                               for translations, e. g. a plugin or
	 *                               skin directory.
	 *              - 'for_helper': (boolean) Is the translation for the b2evoHelper object?
	 * @return string The translated string or the original string on error.
	 *
	 * @internal The last parameter/its 'alt_basedir' key is used by
	 *           Plugin::T_() and Skin::T_().
	 */
	function T_( $string, $req_locale = '', $params = array() )
	{
		/**
		 * The translations keyed by locale.
		 *
		 * This array is only used if $params['ext_transarray'] === NULL.
		 *
		 * @var array
		 * @static
		 */
		static $_trans = array();

		global $current_locale, $locales, $locales_path, $plugins_path;
		global $evo_charset, $Debuglog;

		$params = array_merge( array(
								'ext_transarray' => NULL,
								'alt_basedir'    => '',
								'for_helper'     => false,
								'add_transarray' => NULL,
								), $params );

		if( empty( $req_locale ) )
		{ // By default we use the current locale
			if( empty( $current_locale ) )
			{ // don't translate if we have no locale
				return $string;
			}

			$req_locale = $current_locale;
		}

		if( ! isset( $locales[$req_locale]['messages'] ) )
		{
			$Debuglog->add( 'No messages file path for locale. $locales["'
					.$req_locale.'"] is '.var_export( @$locales[$req_locale], true ), 'locale' );

			if( ! empty( $evo_charset ) ) // this extra check is needed, because $evo_charset may not yet be determined.. :/
			{
				$string = convert_charset( $string, $evo_charset, 'iso-8859-1' );
			}
			return $string;
		}

		$messages = $locales[$req_locale]['messages'];

		if ( is_null( $params['ext_transarray'] ) )
		{	// use our array
			//$Debuglog->add( 'Using internal array', 'locale' );
			$trans = & $_trans;
		}
		else
		{	// use external array:
			//$Debuglog->add( 'Using external array', 'locale' );
			$trans = & $params['ext_transarray'];
		}

		if( ! isset( $trans[ $messages ] ) )
		{ // Translations for current locale have not yet been loaded:
			$Debuglog->add( 'We need to load a new translation file to translate: "'. $string.'"', 'locale' );

			if ( $params['alt_basedir'] != '' )
			{	// Load the translation file from the alternative base dir:
				//$Debuglog->add( 'Using alternative basedir ['.$params['alt_basedir'].']', 'locale' );
				$path = $params['alt_basedir'].'/locales/'.$messages.'/_global.php';
			}
			else
			{	// Load our global translation file.
				$path = $locales_path.$messages.'/_global.php';
			}

			if( file_exists($path) && is_readable($path) )
			{
				$Debuglog->add( 'T_: Loading file: '.$path, 'locale' );
				include_once $path;
			}
			else
			{
				$Debuglog->add( 'T_: Messages file does not exist or is not readable: '.$path, 'locale' );
			}
			if( ! isset($trans[ $messages ] ) )
			{ // Still not loaded... file doesn't exist, memorize that no translations are available
				// echo 'file not found!';
				$trans[ $messages ] = array();
			}
			else
			{
				if( ! isset($trans[$messages]['__meta__']) )
				{ // Unknown/old messages format (< version 1):
					$Debuglog->add( 'Found deprecated messages format (no __meta__ info).', 'locale' );
					// Translate keys (e.g. 'foo\nbar') to real strings ("foo\nbar")
					// Doing this here for all strings, is actually faster than doing it on key lookup (like it has been done before always)
					foreach($trans[$messages] as $k => $v)
					{
						if( ($pos = strpos($k, '\\')) === false )
						{ // fast-path-skip
							continue;
						}
						// Replace string as done in the good old days:
						$new_k = str_replace( array('\n', '\r', '\t'), array("\n", "\r", "\t"), $k );
						if( $new_k != $k )
						{
							$trans[$messages][$new_k] = $v;
							unset($trans[$messages][$k]);
						}
					}
				}
			}
		}

		if( ! empty( $params['add_transarray'] ) )
		{
			switch( $params['add_transarray'] )
			{
				case 'backoffice':
					static $_backoffice_array = array();
					$add_transarray_var = 'trans_backoffice';
					${$add_transarray_var} = & $_backoffice_array;
					$trans_file_name    = '_back-office.php';
					break;

				case 'demo_contents':
					static $_demo_contents_array = array();
					$add_transarray_var    = 'trans_demo_contents';
					${$add_transarray_var} = & $_demo_contents_array;
					$trans_file_name       = '_demo-contents.php';
					break;

				default:
					debug_die( 'Invalid additional translation array.' );
			}

			if( ! isset( ${$add_transarray_var}[ $messages ] ) )
			{ // Additional translations for current locale have not yet been loaded:
				$Debuglog->add( 'We need to load additional translation file to translate: "'. $string.'"', 'locale' );

				if ( $params['alt_basedir'] != '' )
				{	// Load the translation file from the alternative base dir:
					//$Debuglog->add( 'Using alternative basedir ['.$params['alt_basedir'].']', 'locale' );
					$path = $params['alt_basedir'].'/locales/'.$messages.'/'.$trans_file_name;
				}
				else
				{	// Load our additional translation file.
					$path = $locales_path.$messages.'/'.$trans_file_name;
				}

				if( file_exists($path) && is_readable($path) )
				{
					$Debuglog->add( 'T_: Loading additional file: '.$path, 'locale' );
					include_once $path;
				}
				else
				{
					$Debuglog->add( 'T_: Messages file does not exist or is not readable: '.$path, 'locale' );
				}
				if( ! isset( ${$add_transarray_var}[ $messages ] ) )
				{ // Still not loaded... file doesn't exist, memorize that no translations are available
					// echo 'file not found!';
					${$add_transarray_var}[ $messages ] = array();
				}
				else
				{
					if( ! isset( ${$add_transarray_var}[$messages]['__meta__'] ) )
					{ // Unknown/old messages format (< version 1):
						$Debuglog->add( 'Found deprecated messages format (no __meta__ info).', 'locale' );
						// Translate keys (e.g. 'foo\nbar') to real strings ("foo\nbar")
						// Doing this here for all strings, is actually faster than doing it on key lookup (like it has been done before always)
						foreach( ${$add_transarray_var}[$messages] as $k => $v )
						{
							if( ($pos = strpos($k, '\\')) === false )
							{ // fast-path-skip
								continue;
							}
							// Replace string as done in the good old days:
							$new_k = str_replace( array( '\n', '\r', '\t' ), array( "\n", "\r", "\t" ), $k );
							if( $new_k != $k )
							{
								${$add_transarray_var}[$messages][$new_k] = $v;
								unset( ${$add_transarray_var}[$messages][$k] );
							}
						}
					}
				}
			}
		}

		// sam2kb> b2evolution creates _global.php files with "\n" line breaks, and we must normalize newlines
		// in supplied string before trying to translate it. Otherwise strings won't match.
		// fp> TODO: this is not really satisfying in the long term. We need our own
		// parser that will extract T_() TS_() NT_() etc string and create a normalized potfile.
		// Actually it sgould create several potfiles. One for general use, one for admin, one for install, etc.
		// That way translators can concentrate on the most essential stuff first.
		$search_string = str_replace( array("\r\n", "\r"), "\n", $string );

		if( ! empty( $params['add_transarray'] ) && isset( ${$add_transarray_var}[ $messages ][ $search_string ] ) )
		{ // If the string has been translated:
			//$Debuglog->add( 'String ['.$string.'] found', 'locale' );
			$r = ${$add_transarray_var}[ $messages ][ $search_string ];
			if( isset( ${$add_transarray_var}[$messages]['__meta__']['charset']) )
			{ // new format: charset in meta data:
				$messages_charset = ${$add_transarray_var}[$messages]['__meta__']['charset'];
			}
			else
			{ // old format.. extract charset from content type or fall back to setting from global locale definition:
				$meta = ${$add_transarray_var}[$messages][''];
				if( preg_match( '~^Content-Type: text/plain; charset=(.*);?$~m', $meta, $match ) )
				{
					$messages_charset = $match[1];
				}
				else
				{
					$messages_charset = $locales[$req_locale]['charset'];
				}
				// Set it accordingly to new format.
				${$add_transarray_var}[$messages]['__meta__']['charset'] = $messages_charset;
			}
		}
		elseif( isset( $trans[ $messages ][ $search_string ] ) )
		{ // If the string has been translated:
			//$Debuglog->add( 'String ['.$string.'] found', 'locale' );
			$r = $trans[ $messages ][ $search_string ];
			if( isset($trans[$messages]['__meta__']['charset']) )
			{ // new format: charset in meta data:
				$messages_charset = $trans[$messages]['__meta__']['charset'];
			}
			else
			{ // old format.. extract charset from content type or fall back to setting from global locale definition:
				$meta = $trans[$messages][''];
				if( preg_match( '~^Content-Type: text/plain; charset=(.*);?$~m', $meta, $match ) )
				{
					$messages_charset = $match[1];
				}
				else
				{
					$messages_charset = $locales[$req_locale]['charset'];
				}
				// Set it accordingly to new format.
				$trans[$messages]['__meta__']['charset'] = $messages_charset;
			}
		}
		else
		{
			//$Debuglog->add( 'String ['.$string.'] not found', 'locale' );
			// Return the English string:
			$r = $string;

			// $messages_charset = 'iso-8859-1'; // our .php file encoding
			// fp> I am changing the above for the User custom field group labels (in theroy the php files are plain ASCII anyways!!)
			$messages_charset = $evo_charset;
		}

		if( ! empty($evo_charset) ) // this extra check is needed, because $evo_charset may not yet be determined.. :/
		{
			$r = convert_charset( $r, $evo_charset, $messages_charset );
		}
		else
		{
			$Debuglog->add(sprintf('Warning: evo_charset not set to translate "%s"', htmlspecialchars($string)), 'locale');
		}

		if( $params['for_helper'] )
		{ // translation is for the b2evoHelper object
			add_js_translation( $string, $r );
		}

		//$Debuglog->add( 'Result: ['.$r.']', 'locale' );
		return $r;
	}

	/**
	 * Translate strings in Back-office UI.
	 *
	 * @param string String to translate
	 * @return string The translated string
	 */
	function TB_( $string, $req_locale = '', $params = array() )
	{
		$params = array_merge( array(
				'add_transarray' => 'backoffice',
			), $params );

		return T_( $string, $req_locale, $params );
	}


	/**
	 * Translate strings in demo/sample contents.
	 *
	 * @param string String to translate
	 * @return string The translated string
	 */
	function TD_( $string, $req_locale = '', $params = array() )
	{
		// TODO: Later we will make a specific .POT file containing ONLY demo contents.
		$params = array_merge( array(
			'add_transarray' => 'demo_contents',
		), $params );

		return T_( $string, $req_locale, $params );
	}

}
else
{ // We are not localizing at all:

	/**
	 * @ignore
	 */
	function T_( $string, $req_locale = '', $params = array() )
	{
		return $string;
	}


	/**
	 * @ignore
	 */
	function TB_( $string, $req_locale = '', $params = array() )
	{
		return $string;
	}


	/**
	 * @ignore
	 */
	function TD_( $string, $req_locale = '', $params = array() )
	{
		return $string;
	}

}

/**
 * Translate and escape single quotes.
 *
 * This is to be used mainly for Javascript strings.
 *
 * @uses T_()
 * @param string String to translate
 * @param string Locale to use
 * @param array  See {@link T_()}
 * @return string The translated and escaped string.
 */
function TS_( $string, $req_locale = '', $params = array() )
{
	return str_replace( "'", "\\'", T_( $string, $req_locale, $params ) );
}


/**
 * Temporarily switch to another locale
 *
 * Calls can be nested, see {@link locale_restore_previous()}.
 *
 * @param string locale to activate
 * @return boolean true on success, false on failure
 */
function locale_temp_switch( $locale )
{
	global $saved_locales, $current_locale, $Timer;

	// $Timer->resume( 'locale_temp_switch' );

	if( !isset( $saved_locales ) || ! is_array( $saved_locales ) )
	{
		$saved_locales = array();
	}

	$prev_locale = $current_locale;
	if( locale_activate( $locale ) )
	{
		array_push( $saved_locales, $prev_locale );
		return true;
	}

	// $Timer->stop( 'locale_temp_switch' );
	return false;
}


/**
 * Restore the locale in use before the switch
 *
 * @see locale_temp_switch()
 * @return boolean true on success, false on failure (no locale stored before)
 */
function locale_restore_previous()
{
	global $saved_locales;

	if( !empty( $saved_locales ) && is_array( $saved_locales ) )
	{
		locale_activate( array_pop( $saved_locales ) );
		return true;
	}
	return false;
}


/**
 * Activate a locale.
 *
 * @todo dh> this should make sure, that e.g. "charset" is set for the locale in {@link $locales}. See http://forums.b2evolution.net/viewtopic.php?p=43980#43980
 *
 * @param string locale to activate
 * @param boolean True on success/change, false on failure (if already set or not existant)
 */
function locale_activate( $locale )
{
	global $locales, $current_locale, $current_charset;

	if( $locale == $current_locale
			|| empty( $locale )
			|| ! isset( $locales[$locale] ) )
	{
		return false;
	}

	// Memorize new locale:
	$current_locale = $locale;
	// Memorize new charset:
	$current_charset = $locales[ $locale ][ 'charset' ];
	return true;
}


/**
 * locale_by_lang(-)
 *
 * Find first locale matching lang
 */
function locale_by_lang( $lang, $fallback_to_default = true )
{
	global $locales, $default_locale;

	foreach( $locales as $locale => $locale_params )
	{
		if( substr( $locale, 0 ,2 ) == $lang )
		{ // found first matching locale
			return $locale;
		}
	}

	// Not found...
	if( $fallback_to_default )
		return $default_locale;
	else
		return $lang;
}


/**
 * Displays/Returns the current locale. (for backward compatibility)
 *
 * This is for HTML lang attributes
 *
 * @param boolean true (default) if we want it to be outputted
 * @return string current locale, if $disp = false
 */
function locale_lang( $disp = true )
{
	global $current_locale;

	if( $disp )
		echo $current_locale;
	else
		return $current_locale;
}


/**
 * Returns the charset of the current locale
 */
function locale_charset( $disp = true )
{
	global $current_charset;

	if( $disp )
		echo $current_charset;
	else
		return $current_charset;
}


/**
 * Resolves a #date_format_code to the correct date or time format
 *
 * @param string Locale, must be set in {@link $locales}
 * @return string Date format of the locale, e.g. 'd.m.Y'
 */
function locale_resolve_datetime_fmt( $format, $locale = NULL )
{
	// Get datetime format:
	switch( $format )
	{
		case '#short_date':
		case '':
			$format = locale_datefmt( $locale );
			break;

		case '#long_date':
			$format = locale_longdatefmt( $locale );
			break;

		case '#extended_date':
			$format = locale_extdatefmt( $locale );
			break;

		case '#short_time':
			$format = locale_shorttimefmt( $locale );
			break;

		case '#long_time':
			$format = locale_timefmt( $locale );
			break;

		case '#short_date_time':
			$format = locale_datefmt( $locale ).' '.locale_shorttimefmt( $locale );
			break;
	}

	return $format;
}


/**
 * Returns the current locale's default date format
 * @param string Locale, must be set in {@link $locales}
 * @return string Date format of the locale, e.g. 'd.m.Y'
 */
function locale_datefmt( $locale = NULL )
{
	return locale_get( 'datefmt', $locale );
}


/**
 * Returns the current locale's default long date format
 * @param string Locale, must be set in {@link $locales}
 * @return string Date format of the locale, e.g. 'd.m.Y'
 */
function locale_longdatefmt( $locale = NULL )
{
	global $locales;

	if( empty($locale) )
	{
		global $current_locale;
		$locale = $current_locale;
	}

	return $locales[$locale]['longdatefmt'];
}


/**
 * Returns the current locale's default extended date format
 * @param string Locale, must be set in {@link $locales}
 * @return string Date format of the locale, e.g. 'd.m.Y'
 */
function locale_extdatefmt( $locale = NULL )
{
	global $locales;

	if( empty($locale) )
	{
		global $current_locale;
		$locale = $current_locale;
	}

	return $locales[$locale]['extdatefmt'];
}


/**
 * Returns the current locale's default input date format
 * @param string Locale, must be set in {@link $locales}
 * @return string Date format of the locale, e.g. 'd.m.Y'
 */
function locale_input_datefmt( $locale = NULL )
{
	global $locales;

	if( empty($locale) )
	{
		global $current_locale;
		$locale = $current_locale;
	}

	return $locales[$locale]['input_datefmt'];
}


/**
 * Returns the current locale's default time format
 */
function locale_timefmt()
{
	return locale_get( 'timefmt' );
}

/**
 * Returns the current locale's default short time format
 */
function locale_shorttimefmt()
{
	return locale_get( 'shorttimefmt' );
}


function locale_datetimefmt( $separator = ' ' )
{
	return locale_get( 'datefmt' ).$separator.locale_get( 'timefmt' );
}


/**
 * Returns the current locale's start of week
 *
 * @return integer 0 for Sunday, 1 for Monday
 */
function locale_startofweek()
{
	return ( int ) locale_get( 'startofweek' );
}


/**
 * Get the country locale
 *
 * @param string locale to use, '' for current
 *
 * @return string country locale
 */
function locale_country( $locale = '' )
{
	global $current_locale;

	if( empty($locale) ) $locale = $current_locale;

	return substr( $locale, 3, 2 );
}


/**
 *	Get the locale country dialing code
 */
function locale_dialing_code( $locale = '' )
{
		global $current_locale, $CountryCache;

		if( empty($locale) )
		{
			$locale = locale_country();
		}

		$edited_Country = $CountryCache->get_by_ID( $locale);

		return $edited_Country->dialing_code;
}


/**
 * Template function: Display locale flag
 *
 * @param string locale to use, '' for current
 * @param string DEPRECATED PARAM - NOT USED IN THE FUNCTION ANYMORE !!
 * @param string name of class for IMG tag   !! OLD PARAM - NOT USED IN THE FUNCTION ANYMORE !!
 * @param string deprecated HTML align attribute   !! OLD PARAM - NOT USED IN THE FUNCTION ANYMORE !!
 * @param boolean to echo or not
 * @param mixed use absolute url (===true) or path to flags directory (used in installer)   !! OLD PARAM - NOT USED IN THE FUNCTION ANYMORE !!
 */
function locale_flag( $locale = '', $collection = 'deprecated_param', $class = 'flag', $align = '', $disp = true, $absoluteurl = true )
{
	global $locales, $current_locale, $country_flags_bg;

	if( empty( $locale ) )
	{
		$locale = $current_locale;
	}

	// extract flag name:
	$country_code = strtolower( substr( $locale, 3, 2 ) );

	$flag_attribs = array(
		'class' => 'flag',
		'title' => isset($locales[$locale]['name']) ? $locales[$locale]['name'] : $locale,
	);

	if( isset( $country_flags_bg[ $country_code ] ) )
	{	// Set background-position from config
		$flag_attribs['style'] = 'background-position:'.$country_flags_bg[ $country_code ];
	}

	$r = '<span'.get_field_attribs_as_string( $flag_attribs ).'></span>';

	if( $disp )
		echo $r;   // echo it
	else
		return $r; // return it
}


/**
 * [callback function] Outputs an <option> set with default locale selected.
 * Optionally returns an array with a locale key and name if there's only one enabled locale.
 *
 * @param string default value
 * @param boolean echo output?
 * @param boolean Return array (locale key + name) if there's only one enabled locale?
 * @return string|array The options string or an array (locale key + name) if there's only one enabled locale and $array_if_onelocale == true.
 */
function locale_options( $default = '', $disp = true, $array_if_onelocale = false )
{
	global $locales, $default_locale;

	if( empty( $default ) ) $default = $default_locale;

	$r = '';
	$enabled_count = 0;
	$enabled_lastkey = '';

	foreach( $locales as $this_localekey => $this_locale )
	{
		if( $this_locale['enabled'] || $this_localekey == $default )
		{
			$r .= '<option value="'. $this_localekey. '"';
			if( $this_localekey == $default )
				$r .= ' selected="selected"';
			$r .= '>'. T_($this_locale['name']). '</option>';

			++$enabled_count;
			$enabled_lastkey = $this_localekey;
		}
	}

	if( $disp )
	{	// the result must be displayed
		echo $r;
	}

	if ( $array_if_onelocale && $enabled_count == 1 )
	{	// We've only one enabled locale:
		return array( $enabled_lastkey, $locales[$enabled_lastkey]['name'] );
	}
	else
	{	// Return the string.
		return $r;
	}
}


/**
 * [callback function] Returns an <option> set with default locale selected
 *
 * @param string default value
 */
function locale_options_return( $default = '' )
{
	$r = locale_options( $default, false );
	return $r;
}


/**
 * Detect language from HTTP_ACCEPT_LANGUAGE
 *
 * HTTP_ACCEPT_LANGUAGE is sorted by prio and then the best match is used
 * (either full locale ("en-US") or best fitting locale for a short one ("en").
 *
 * This gets tested in {@link test_locale_from_httpaccept()}.
 *
 * @author Rewritten by blueyed in Revision 1.42
 *
 * @return string Locale made out of HTTP_ACCEPT_LANGUAGE or $default_locale, if no match
 */
function locale_from_httpaccept()
{
	global $locales, $default_locale;
	if( isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) )
	{
		$accept = strtolower( $_SERVER['HTTP_ACCEPT_LANGUAGE'] );

		// Create list of accepted locales.
		if( ! preg_match_all('/([a-z]{1,8}(?:-[a-z]{1,8})?)\s*(?:;\s*q\s*=\s*(1|0(?:\.[0-9]+)?))?/i', $accept, $accept_list) )
		{
			return $default_locale;
		}
		// Create list of enabled locales.
		$enabled_locales = array();
		foreach($locales as $k => $v)
		{
			if( empty($v['enabled']) )
			{
				continue;
			}
			$enabled_locales[strtolower($k)] = $k;
		}
		if( empty($enabled_locales) )
		{
			return $default_locale;
		}
		// Build mapping of short code to long code(s)
		$short_locales = array();
		foreach($enabled_locales as $v)
		{
			if( $pos = strpos($v, '-') )
			{
				$short = substr($v, 0, $pos);
				$short_locales[$short][] = $v;
			}
		}
		// Create "locale" => "prio" list
		$accept_list = array_combine($accept_list[1], $accept_list[2]);
		$maxq = count($accept_list)+1;
		foreach( $accept_list as $k => $v )
		{
			if( $v === '' )
			{ // should be kept in order
				$accept_list[$k] = $maxq--;
			}
			elseif( $v == 0 )
			{ // not acceptable (RFC 2616)
				unset($accept_list[$k]);
			}
		}
		arsort($accept_list);
		$accept_list = array_values(array_keys($accept_list));

		// Go through the list of accepted locales and find best match.
		for( $i = 0, $n = count($accept_list); $i<$n; $i++ )
		{
			$test = $accept_list[$i];

			if( isset($enabled_locales[$test]) )
			{ // the accepted locale is enabled and a full match
				return $enabled_locales[$test];
			}
			if( isset($short_locales[$test]) )
			{ // this is a short locale: find the best/first match in accepted locales
				$first = NULL;
				foreach($short_locales[$test] as $v)
				{
					$pos = array_search(strtolower($v), $accept_list);
					if( $pos !== false && ( ! isset($first) || $pos < $first ) )
					{
						$first = $pos;
					}
				}
				if( isset($first) )
				{ // found exact match, via short locale.
					return $enabled_locales[$accept_list[$first]];
				}
				if( isset($enabled_locales[$test.'-'.$test]) )
				{ // test for e.g. "de-DE" when only "de" is accepted
					return $enabled_locales[$test.'-'.$test];
				}
				// Fallback: use first enabled locale matching the short accepted
				return $short_locales[$test][0];
			}
		}
	}
	return $default_locale;
}


/**
 * user sort function to sort locales by priority
 *
 * 1 is highest priority.
 *
 */
function locale_priosort( $a, $b )
{
	return $a['priority'] - $b['priority'];
}


/**
 * load locales from DB into $locales array. Also sets $default_locale.
 *
 * @return mixed new default locale on succes, false on failure
 */
function locale_overwritefromDB()
{
	global $DB, $locales, $default_locale, $Settings, $Debuglog, $Messages;

	// Load all locales from disk to get 'charset' insrtad of DB
	locales_load_available_defs();

	$usedprios = array();  // remember which priorities are used already.
	$priocounter = 0;
	$query = 'SELECT
						loc_locale, loc_datefmt, loc_longdatefmt, loc_extdatefmt, loc_input_datefmt, loc_timefmt, loc_shorttimefmt, loc_input_timefmt, loc_startofweek,
						loc_name, loc_messages, loc_priority, loc_transliteration_map, loc_enabled
						FROM T_locales ORDER BY loc_priority';

	$ctrl = isset( $_GET['ctrl'] ) ? $_GET['ctrl'] : '';
	$priocounter_max = 0;
	$wrong_locales = array();
	$wrong_default_locale = '';
	foreach( $DB->get_results( $query, ARRAY_A ) as $row )
	{ // Loop through loaded locales:

		if( $row['loc_priority'] == $priocounter )
		{ // priority conflict (the same)
			$priocounter++;
		}
		else
		{
			$priocounter = $row['loc_priority'];
		}
		$priocounter_max = $priocounter;

		//remember that we used this
		$usedprios[] = $priocounter;

		$transliteration_map = '';
		// Try to unserialize the value
		if( ( $r = @unserialize( @base64_decode( $row[ 'loc_transliteration_map' ] ) ) ) !== false )
		{
			$transliteration_map = $r;
		}

		if( isset( $locales[ $row['loc_locale'] ], $locales[ $row['loc_locale'] ]['charset'] ) )
		{ // Use charset from locale file if such file exists on disk
			$loc_charset = $locales[ $row['loc_locale'] ]['charset'];
		}
		else
		{ // Use charset from DB
			$loc_charset = 'utf-8';
		}

		if( ( $ctrl == 'locales' || $ctrl == 'regional' ) && $row['loc_enabled'] && $loc_charset != 'utf-8' )
		{ // Check wrong non utf-8 locales in order to deactivate them
			$Messages->add( sprintf( T_('The locale %s has been deactivated because it\'s not UTF-8'), '<b>'.$row['loc_locale'].'</b>' ) );
			if( $Settings->get( 'default_locale' ) == $row['loc_locale'] && $default_locale != $row['loc_locale'] )
			{ // Change main locale to default
				$wrong_default_locale = $row['loc_locale'];
				$Messages->add( sprintf( T_('The main locale was no longer valid and has been set to %s.'), '<b>'.$default_locale.'</b>' ) );
			}
			$wrong_locales[] = $row['loc_locale'];
			$row['loc_enabled'] = 0;
		}

		$locales[ $row['loc_locale'] ] = array(
				'charset'      => $loc_charset,
				'datefmt'      => $row['loc_datefmt'],
				'longdatefmt'  => $row['loc_longdatefmt'],
				'extdatefmt'   => $row['loc_extdatefmt'],
				'input_datefmt' => $row['loc_input_datefmt'],
				'timefmt'      => $row['loc_timefmt'],
				'shorttimefmt' => $row['loc_shorttimefmt'],
				'input_timefmt' => $row['loc_input_timefmt'],
				'startofweek'  => $row['loc_startofweek'],
				'name'         => $row['loc_name'],
				'messages'     => $row['loc_messages'],
				'transliteration_map' => $transliteration_map,
				'priority'     => $priocounter,
				'enabled'      => $row['loc_enabled'],
				'fromdb'       => 1
			);
	}

	if( count( $wrong_locales ) )
	{ // Deactivate locales that have wrong charset
		$DB->query( 'UPDATE T_locales
			  SET loc_enabled = 0
			WHERE loc_locale IN( '.$DB->quote( $wrong_locales ).' )' );
		if( ! empty( $wrong_default_locale ) )
		{ // If main locale is wring we sgould updated it to default and use this for all users and blogs that used this wrong locale
			$Settings->set( 'default_locale', $default_locale );
			$Settings->dbupdate();
			$DB->query( 'UPDATE T_users
				  SET user_locale = '.$DB->quote( $default_locale ).'
				WHERE user_locale = '.$DB->quote( $wrong_default_locale ) );
			$DB->query( 'UPDATE T_blogs
				  SET blog_locale = '.$DB->quote( $default_locale ).'
				WHERE blog_locale = '.$DB->quote( $wrong_default_locale ) );
		}
	}

	foreach( $locales as $l_ley => $locale )
	{ // Change priority of non-db locales to put them at the end of list
		if( empty( $locale['fromdb'] ) && $locale['priority'] < $priocounter_max )
		{
			$locales[ $l_ley ]['priority'] = $priocounter_max + $locale['priority'];
		}
	}

	// set default priorities, if nothing was set in DB.
	// Missing "priority gaps" will get filled here.
	if( $DB->num_rows != count($locales) )
	{ // we have locales from conf file that need a priority
		$priocounter = 1;
		foreach( $locales as $lkey => $lval )
		{ // Loop through memory locales:
			if( !isset($lval['priority']) )
			{ // Found one that has no assigned priority
				while( in_array( $priocounter, $usedprios ) )
				{
					$priocounter++;
				}
				// Priocounter has max value
				$locales[$lkey]['priority'] = $priocounter;
				$usedprios[] = $priocounter;
			}
		}
	}

	// sort by priority
	uasort( $locales, 'locale_priosort' );

	// overwrite default_locale from DB settings - if enabled.
	// Checks also if previous $default_locale is enabled. Defaults to en-EU, even if not enabled.
	$locale_fromdb = $Settings->get('default_locale');

	if( $locale_fromdb )
	{
		if( !isset( $locales[$locale_fromdb] ) )
		{
			$Debuglog->add( 'Default locale ['.$locale_fromdb.'] from general settings is not available.', 'locale' );
			return false;
		}
		else
		{
			$default_locale = $locale_fromdb;
			return $default_locale;
		}
	}
}


/**
 * Write $locales array to DB table
 */
function locale_updateDB()
{
	global $locales, $DB, $Settings, $Messages, $action, $admin_url;
	global $saved_params;

	$templocales = $locales;
	$disabled_locales = array();
	$saved_params = array();

	$current_default_locale = $Settings->get( 'default_locale' );

	$lnr = 0;
	// Loop through list of all HTTP POSTed params:
	foreach( $_POST as $pkey => $pval )
	{
		if( ! preg_match( '/loc_(\d+)_(.*)/', $pkey, $matches ) )
		{
			continue;
		}

		// This is a locale related parameter, get it now:
		$pval = param( $pkey, 'string', '' );
		$saved_params[ $pkey ] = $pval;

		$lfield = $matches[2];

		if( $matches[1] != $lnr )
		{ // we have a new locale
			$lnr = $matches[1];
			$plocale = $pval;

			if( $templocales[ $plocale ]['enabled'] || ( $plocale == $current_default_locale && ! isset( $_POST['loc_'.$lnr.'_enabled'] ) ) )
			{	// Collect previously enabled locales AND if current default locale is not enabled yet:
				$disabled_locales[ $plocale ] = $plocale;
			}
			// checkboxes default to 0
			$templocales[ $plocale ]['enabled'] = 0;
		}
		elseif( $lnr != 0 )  // be sure to have catched a locale before
		{
			if( $lfield == 'datefmt' && empty( $pval ) )
			{
				param_error( $pkey, T_('Date format cannot be empty.').' ('.$plocale.')' );
			}
			elseif( $lfield == 'input_datefmt' && empty( $pval ) )
			{
				param_error( $pkey, T_('Input date format cannot be empty.').' ('.$plocale.')' );
			}
			elseif( $lfield == 'timefmt' && empty( $pval ) )
			{
				param_error( $pkey, T_('Time format cannot be empty.').' ('.$plocale.')' );
			}
			elseif( $lfield == 'input_timefmt' && empty( $pval ) )
			{
				param_error( $pkey, T_('Input time format cannot be empty.').' ('.$plocale.')' );
			}

			if( $lfield == 'startofweek' && ( $pval < 0 || $pval > 6 ) )
			{ // startofweek must be between 0 and 6
				continue;
			}
			if( $lfield == 'enabled' )
			{ // This locale is enabled, remove from the list of the disabled locales
				unset( $disabled_locales[ $plocale ] );
			}
			elseif( $plocale == $current_default_locale && isset( $disabled_locales[ $plocale ] ) && ! isset( $_POST['loc_'.$lnr.'_enabled'] ) )
			{ // The default locale must be always enabled
				unset( $disabled_locales[ $plocale ] );
				// Force the default locale to be enabled
				$templocales[ $plocale ]['enabled'] = 1;
				// Inform user about this action
				$Messages->add( sprintf( T_('The locale %s was re-enabled automatically because it\'s currently the default locale. The default locale must be a valid and enabled locale.'), '<b>'.$current_default_locale.'</b>' ), 'warning' );
			}
			$templocales[ $plocale ][ $lfield ] = $pval;
		}
	}

	if( $Messages->has_errors() )
	{
		return false;
	}

	$locales = $templocales;

	// Check the usage of the disabled locales
	$disabled_locales = ( count( $disabled_locales ) > 0 ) ? $DB->quote( $disabled_locales ) : false;
	if( $disabled_locales && ( $action != 'confirm_update' ) )
	{ // Some locales were disabled, create warning message if required
		global $warning_message;
		$warning_message = '';

		// Display what MAIN collection locales will be disabled:
		$SQL = new SQL( 'Get collections with disabled main locales' );
		$SQL->SELECT( 'blog_locale, GROUP_CONCAT( blog_ID SEPARATOR "," ) ' );
		$SQL->FROM( 'T_blogs' );
		$SQL->WHERE( 'blog_locale IN ( '.$disabled_locales.' )' );
		$SQL->ORDER_BY( 'blog_locale' );
		$SQL->GROUP_BY( 'blog_locale' );
		$SQL->HAVING( 'COUNT( blog_ID ) > 0' );
		$main_locale_colls = $DB->get_assoc( $SQL );
		if( count( $main_locale_colls ) > 0 )
		{
			$BlogCache = & get_BlogCache();
			$BlogCache->load_list( explode( ',', implode( ',', $main_locale_colls ) ) );
			foreach( $main_locale_colls as $main_locale => $main_locale_coll_IDs )
			{
				$warning_message .= sprintf( T_('The locale %s is used as main locale by the following collections:'), '<code>'.$main_locale.'</code>' ).'<ul style="list-style:disc;margin-left:20px">';
				$main_locale_coll_IDs = explode( ',', $main_locale_coll_IDs );
				foreach( $main_locale_coll_IDs as $main_locale_coll_ID )
				{
					$locale_Blog = & $BlogCache->get_by_ID( $main_locale_coll_ID );
					$coll_url = check_user_perm( 'blog_properties', 'edit', false, $main_locale_coll_ID )
						? $admin_url.'?ctrl=coll_settings&amp;tab=general&amp;blog='.$locale_Blog->ID.'#fieldset_wrapper_language'
						: $locale_Blog->get( 'url' );
					$warning_message .= '<li><a href="'.$coll_url.'">'.$locale_Blog->get( 'name' ).'</a></li>';
				}
				$warning_message .= '</ul>'.sprintf( T_('These will be switched to the default locale %s.'), '<code>'.$main_locale.'</code>' ).'<br><br>';
			}
		}

		// Display what EXTRA collection locales will be disabled:
		$SQL = new SQL( 'Get collections with disabled extra locales' );
		$SQL->SELECT( 'cl_locale, GROUP_CONCAT( cl_coll_ID SEPARATOR "," ) ' );
		$SQL->FROM( 'T_coll_locales' );
		$SQL->WHERE( 'cl_locale IN ( '.$disabled_locales.' )' );
		$SQL->ORDER_BY( 'cl_locale' );
		$SQL->GROUP_BY( 'cl_locale' );
		$SQL->HAVING( 'COUNT( cl_coll_ID ) > 0' );
		$extra_locale_colls = $DB->get_assoc( $SQL );
		if( count( $extra_locale_colls ) > 0 )
		{
			$BlogCache = & get_BlogCache();
			$BlogCache->load_list( explode( ',', implode( ',', $extra_locale_colls ) ) );
			foreach( $extra_locale_colls as $extra_locale => $extra_locale_coll_IDs )
			{
				$warning_message .= sprintf( T_('The locale %s is also used as extra locale by the following collections:'), '<code>'.$extra_locale.'</code>' ).'<ul style="list-style:disc;margin-left:20px">';
				$extra_locale_coll_IDs = explode( ',', $extra_locale_coll_IDs );
				foreach( $extra_locale_coll_IDs as $extra_locale_coll_ID )
				{
					$locale_Blog = & $BlogCache->get_by_ID( $extra_locale_coll_ID );
					$coll_url = check_user_perm( 'blog_properties', 'edit', false, $extra_locale_coll_ID )
						? $admin_url.'?ctrl=coll_settings&amp;tab=general&amp;blog='.$locale_Blog->ID.'#fieldset_wrapper_language'
						: $locale_Blog->get( 'url' );
					$warning_message .= '<li><a href="'.$coll_url.'">'.$locale_Blog->get( 'name' ).'</a></li>';
				}
				$warning_message .= '</ul>'.sprintf( T_('These collections will no longer use the %s extra locale.'), '<code>'.$extra_locale.'</code>' ).'<br><br>';
			}
		}

		// Display what USER locales will be disabled:
		$SQL = new SQL( 'Get users with disabled locales' );
		$SQL->SELECT( 'user_locale, GROUP_CONCAT( user_ID SEPARATOR "," ) ' );
		$SQL->FROM( 'T_users' );
		$SQL->WHERE( 'user_locale IN ( '.$disabled_locales.' )' );
		$SQL->ORDER_BY( 'user_locale' );
		$SQL->GROUP_BY( 'user_locale' );
		$SQL->HAVING( 'COUNT( user_ID ) > 0' );
		$locale_users = $DB->get_assoc( $SQL );
		if( count( $locale_users ) > 0 )
		{
			$UserCache = & get_UserCache();
			$UserCache->load_list( explode( ',', implode( ',', $locale_users ) ) );
			foreach( $locale_users as $user_locale => $locale_user_IDs )
			{
				$warning_message .= sprintf( T_('The locale %s is also used as preferred locale by the following users:'), '<code>'.$user_locale.'</code>' ).'<ul style="list-style:disc;margin-left:20px">';
				$locale_user_IDs = explode( ',', $locale_user_IDs );
				foreach( $locale_user_IDs as $locale_user_ID )
				{
					$locale_User = & $UserCache->get_by_ID( $locale_user_ID );
					$warning_message .= '<li>'.$locale_User->get_identity_link( array( 'user_tab' => 'userprefs' ) ).'</li>';
				}
				$warning_message .= '</ul>'.sprintf( T_('These users\' preferred locale will fall back to %s.'), '<code>'.$user_locale.'</code>' ).'<br><br>';
			}
		}

		if( ! empty( $warning_message ) )
		{
			return false;
		}
	}

	$query = "REPLACE INTO T_locales ( loc_locale, loc_datefmt, loc_longdatefmt, loc_extdatefmt, loc_input_datefmt,
			loc_timefmt, loc_shorttimefmt, loc_input_timefmt, loc_startofweek, loc_name, loc_messages, loc_priority,
			loc_transliteration_map, loc_enabled ) VALUES ";
	foreach( $locales as $localekey => $lval )
	{
		if( empty($lval['messages']) )
		{ // if not explicit messages file is given we'll translate the locale
			$lval['messages'] = strtr($localekey, '-', '_');
		}

		$transliteration_map = '';
		if( !empty($lval['transliteration_map']) )
		{
			if( is_string($lval['transliteration_map']) )
			{	// The value is already serialized and encoded
				$transliteration_map = $lval['transliteration_map'];
			}
			else
			{	// Encode the value
				$transliteration_map = base64_encode( serialize($lval['transliteration_map']) );
			}
		}

		$query .= '(
			'.$DB->quote($localekey).',
			'.$DB->quote($lval['datefmt']).',
			'.$DB->quote($lval['longdatefmt']).',
			'.$DB->quote($lval['extdatefmt']).',
			'.$DB->quote($lval['input_datefmt']).',
			'.$DB->quote($lval['timefmt']).',
			'.$DB->quote($lval['shorttimefmt']).',
			'.$DB->quote($lval['input_timefmt']).',
			'.$DB->quote($lval['startofweek']).',
			'.$DB->quote($lval['name']).',
			'.$DB->quote($lval['messages']).',
			'.$DB->quote($lval['priority']).',
			'.$DB->quote($transliteration_map).',
			'.$DB->quote($lval['enabled']).'
		), ';
	}
	$query = substr($query, 0, -2);

	$DB->begin();

	$result = true;
	if( $action == 'confirm_update' )
	{ // Update users and blogs locale to the default if the prevously used locale was disabled
		$users_update = $DB->query( 'UPDATE T_users
			  SET user_locale = '.$DB->quote( $current_default_locale ).'
			WHERE  user_locale IN ( '.$disabled_locales.' )' );
		$colls_main_locales_update = $DB->query( 'UPDATE T_blogs
			  SET blog_locale = '.$DB->quote( $current_default_locale ).'
			WHERE blog_locale IN ( '.$disabled_locales.' )' );
		$colls_extra_locales_update = $DB->query( 'DELETE FROM T_coll_locales
			WHERE cl_locale IN ( '.$disabled_locales.' )' );
		$result = ( $users_update !== false ) && ( $colls_main_locales_update !== false ) && ( $colls_extra_locales_update !== false );
	}

	if( $result && ( $DB->query($query) !== false ) )
	{ // Commit after successful update
		$DB->commit();
		return true;
	}

	// Some error occured, rollback the transaction and add error message
	$DB->rollback();
	$Messages->add( T_('Unexpected error occured, regional settings could not be updated.') );
	return false;
}


/**
 * Convert a string from one charset to another.
 *
 * @todo Implement iconv and PHP mapping tables
 *
 * @see can_convert_charsets()
 * @param string String to convert
 * @param string Target charset (TO)
 * @param string Source charset (FROM)
 * @return string Encoded string (if it cannot be converted it's the original one)
 */
function convert_charset( $string, $dest_charset, $src_charset )
{
	if( isset( $GLOBALS['Timer'] ) )
	{
		$GLOBALS['Timer']->resume( 'convert_charset', false );
	}

	if( $dest_charset == $src_charset || $dest_charset == '' /* may happen if $evo_charset is not defined yet */ )
	{	// no conversation required
		if( isset( $GLOBALS['Timer'] ) )
		{
			$GLOBALS['Timer']->pause( 'convert_charset', false );
		}
		return $string;
	}

	if( function_exists( 'mb_convert_variables' ) )
	{	// Convert by mbstring extension if it is enabled:
		mb_convert_variables( $dest_charset, $src_charset, $string );
	}
	elseif( function_exists( 'iconv' ) )
	{	// Convert by iconv extension it it is enabled:
		$string = iconv( $src_charset, $dest_charset, $string );
	}

	if( isset( $GLOBALS['Timer'] ) )
	{
		$GLOBALS['Timer']->pause( 'convert_charset', false );
	}

	return $string;
}


/**
 * Can we convert from charset A to charset B?
 * @param string Target charset (TO)
 * @param string Source charset (FROM)
 * @return boolean
 */
function can_convert_charsets( $dest_charset, $src_charset )
{
	if( empty($dest_charset) || empty($src_charset) )
	{
		return false;
	}

	if( function_exists('mb_internal_encoding') )
	{ // mb_string extension:
		$orig = mb_internal_encoding();

		$r = false;
		if( @mb_internal_encoding($dest_charset) && @mb_internal_encoding($src_charset) )
		{ // we can set both encodings, so we should be able to convert:
			$r = true;
		}

		mb_internal_encoding($orig);
		return $r;
	}

	return false;
}


/**
 * Can we check for valid encodings of strings, using {@link check_encoding()}?
 *
 * @return boolean
 */
function can_check_encoding()
{
	return function_exists('mb_check_encoding');
}


/**
 * Check if the string is valid for the specified encoding.
 *
 * @param string String to check
 * @param string Encoding to check
 * @return
 */
function check_encoding($str, $encoding)
{
	if( function_exists('mb_check_encoding') )
	{
		return mb_check_encoding($str, $encoding);
	}

	return NULL;
}


/**
 * Init charset handling between Input/Output ($io_charset) and the internal
 * handling ($evo_charset).
 *
 * Check and possibly adjust {@link $evo_charset}.
 *
 * @staticvar boolean Used to only start mb_output_handler once
 * @param string I/O (input/output) charset to use
 * @return boolean true, if encoding has been changed
 */
function init_charsets( $req_io_charset )
{
	static $mb_output_handler_started;
	global $io_charset, $evo_charset, $Debuglog, $DB;
	global $force_io_charset_if_accepted;

	if( $req_io_charset == $io_charset )
	{ // no conversation/init needed
		return false;
	}

	// check, if we want to force a specific charset (e.g. 'utf-8'):
	if( ! empty($force_io_charset_if_accepted) )
	{ // we want to force a specific charset:
		if( ! isset($_SERVER['HTTP_ACCEPT_CHARSET']) // all allowed
			|| preg_match( '~\b(\*|'.preg_quote( $force_io_charset_if_accepted, '~' ).')\b~', $_SERVER['HTTP_ACCEPT_CHARSET'] ) )
		{
			$req_io_charset = $force_io_charset_if_accepted; // pretend that the first one has been requested
		}
	}

	if( $req_io_charset == $io_charset )
	{ // no conversation/init needed
		return false;
	}

	$io_charset = $req_io_charset;

	if( empty($evo_charset) )
	{ // empty evo_charset follows I/O charset:
		// TODO: $evo_charset will not follow, if it has followed before.. (because not empty anymore)
		$Debuglog->add( '$evo_charset follows $io_charset ('.$io_charset.').', array('locale') );
		$evo_charset = $io_charset;
	}
	elseif( $evo_charset != $io_charset )
	{ // we have to convert for I/O
		// TODO: dh> $io_charset has to forcefully follow $evo_charset, if we cannot convert, e.g. utf-8/iso-8859-1
		if( ! function_exists('mb_convert_encoding') )
		{
			$Debuglog->add( '$evo_charset differs from $io_charset, but mbstrings is not available - cannot convert I/O to internal charset!', array('errors','locale') );
			$evo_charset = $io_charset; // we cannot convert I/O to internal charset
		}
		else
		{ // check if the encodings are supported:
			// NOTE: mb_internal_encoding() is the best way to find out if the encoding is supported
			$old_mb_internal_encoding = mb_internal_encoding();
			if( ! @mb_internal_encoding($io_charset) )
			{
				$Debuglog->add( 'Cannot I/O convert because I/O charset ['.$io_charset.'] is not supported by mbstring!', array('error','locale') );
				$evo_charset = $io_charset;
				mb_internal_encoding($old_mb_internal_encoding);
			}
			elseif( ! @mb_internal_encoding($evo_charset) )
			{
				$Debuglog->add( 'Cannot I/O convert because $evo_charset='.$evo_charset.' is not supported by mbstring!', array('error','locale') );
				$evo_charset = $io_charset;
				mb_internal_encoding($old_mb_internal_encoding);
			}
			else
			{ // we can convert between I/O
				mb_http_output( $io_charset );
				if( ! $mb_output_handler_started )
				{
					ob_start( 'mb_output_handler' ); // NOTE: this will send a Content-Type header by itself for "text/..."
					$mb_output_handler_started = true;
					$Debuglog->add( 'Started mb_output_handler.', 'locale' );
				}
			}
		}
	}

	// Make sure the DB send us text in the same charset as $evo_charset (override whatever may have been set before)
	if( isset($DB) ) // not available in /install/index.php
	{	// Set encoding for MySQL connection:
		$DB->set_connection_charset( $evo_charset );
	}

	$Debuglog->add( 'evo_charset: '.$evo_charset, 'locale' );
	$Debuglog->add( 'io_charset: '.$io_charset, 'locale' );

	return true;
}


/**
 * Load available locale definitions
 */
function locales_load_available_defs()
{
	global $locales_path, $locales, $locales_loaded_form_disk, $Messages;

	if( ! empty( $locales_loaded_form_disk ) )
	{ // All locales have been already loaded
		return;
	}

	// This is where language packs will store their locale defintions:
	$locale_defs = array();

	// Get all locale folder names:
	$filename_params = array(
			'inc_files' => false,
			'recurse'   => false,
			'basename'  => true,
		);
	$locale_folders = get_filenames( $locales_path, $filename_params );
	// Go through all locale folders:
	foreach( $locale_folders as $locale_folder )
	{
		//pre_dump( $locale_folder );
		$ad_locale_folder = $locales_path.'/'.$locale_folder;
		// Get files in folder:
		$filename_params = array(
				'inc_dirs'	=> false,
				'recurse'	=> false,
				'basename'	=> true,
			);
		$locale_def_files = get_filenames( $ad_locale_folder, $filename_params );
		if( $locale_def_files === false )
		{	// Impossible to read the locale folder:
			$Messages->add( sprintf( /* No trans for debug messages */ 'The locale %s is not readable. Please check file permissions.', $locale_folder ), 'warning' );
			continue;
		}
		// Go through files in locale folder:
		foreach( $locale_def_files as $locale_def_file )
		{	// Check if it's a definition file:
			// pre_dump( $locale_def_file );
			if( preg_match( '~[a-z0-9\-.]\.locale\.php$~i', $locale_def_file ) )
			{	// We found a definition file:
				// pre_dump( $locale_def_file );
				include $ad_locale_folder.'/'.$locale_def_file;
			}
		}
	}

	// Copy any new locale definitions over to $locales:
	foreach( $locale_defs as $locale_code => $locale_def )
	{
		if( !isset( $locales[$locale_code] ) )
		{	// New locale, add to main array:
			$locales[$locale_code] = $locale_def;
			// ... but mark as not enabled!
			$locales[$locale_code]['enabled'] = 0;
		}
	}


	// Assign priorities:

	// Find highest used priority:
	$max_prio = 0;
	foreach( $locales as $locale )
	{
		if( isset($locale['priority']) && $locale['priority'] > $max_prio )
		{
			$max_prio = $locale['priority'];
		}
	}

	foreach( $locales as $lkey => $locale )
	{
		if( !isset($locale['priority']) )
		{
			$locales[$lkey]['priority'] = ++$max_prio;
		}
	}

	// Set this var to TRUE to don't call this function twice
	$locales_loaded_form_disk = true;
}


/**
 * Get a number of the messages in the .PO & .POT file
 *
 * @param string File name (messages.po)
 * @param boolean TRUE - to calc a percent of translated messages
 * @return integer Number of the messages
 */
function locale_file_po_info( $po_file_name, $calc_percent_done = false )
{
	$all = 0;
	$fuzzy = 0;
	$this_fuzzy = false;
	$untranslated = 0;
	$translated = 0;
	$status = '-';
	$matches = array();

	if( file_exists( $po_file_name ) )
	{
		$lines = file( $po_file_name );
		$lines[] = '';	// Adds a blank line at the end in order to ensure complete handling of the file

		foreach( $lines as $line )
		{
			if( trim( $line ) == '' )
			{	// Blank line, go back to base status:
				if( $status == 't' )
				{	// ** End of a translation ** :
					if( $msgstr == '' )
					{
						$untranslated++;
						// echo 'untranslated: ', $msgid, '<br />';
					}
					else
					{
						$translated++;
					}
					if( $msgid == '' && $this_fuzzy )
					{	// It's OK if first line is fuzzy
						$fuzzy--;
					}
					$msgid = '';
					$msgstr = '';
					$this_fuzzy = false;
				}
				$status = '-';
			}
			elseif( ( $status == '-' ) && preg_match( '#^msgid "(.*)"#', $line, $matches ) )
			{	// Encountered an original text
				$status = 'o';
				$msgid = $matches[1];
				// echo 'original: "', $msgid, '"<br />';
				$all++;
			}
			elseif( ( $status == 'o' ) && preg_match( '#^msgstr "(.*)"#', $line, $matches ) )
			{	// Encountered a translated text
				$status = 't';
				$msgstr = $matches[1];
				// echo 'translated: "', $msgstr, '"<br />';
			}
			elseif( preg_match( '#^"(.*)"#', $line, $matches ) )
			{	// Encountered a followup line
				if( $status == 'o' )
					$msgid .= $matches[1];
				elseif( $status == 't' )
					$msgstr .= $matches[1];
			}
			elseif( strpos( $line,'#, fuzzy' ) === 0 )
			{
				$this_fuzzy = true;
				$fuzzy++;
			}
		}
	}

	$info = array(
			'all'          => $all,
			'fuzzy'        => $fuzzy,
			'translated'   => $translated,
			'untranslated' => $untranslated
		);

	if( $calc_percent_done )
	{
		$po_path_info    = pathinfo( $po_file_name );
		$pot_file_name   = $po_path_info['filename'].'.pot';
		$info['percent'] = locale_file_po_percent_done( $info, $pot_file_name );
	}

	return $info;
}


/**
 * Get a percent of translated messages in the .PO file
 *
 * @param array File info (see result of the function locale_file_po_info() )
 * @return integer Percent
 */
function locale_file_po_percent_done( $po_file_info, $pot_file )
{
	global $messages_pot_file_info;

	if( !isset( $messages_pot_file_info[$pot_file] ) )
	{	// Initialize a file info for the main language file if it doesn't yet set
		global $locales_path;
		$messages_pot_file_info[$pot_file] = locale_file_po_info( $locales_path.$pot_file );
	}

	$percent_done = ( $messages_pot_file_info[$pot_file]['all'] > 0 ) ? round( ( $po_file_info['translated'] - $po_file_info['fuzzy'] / 2 ) / $messages_pot_file_info[$pot_file]['all'] * 100 ) : 0;

	return $percent_done;
}


/**
 * Insert default locales into T_locales.
 */
function locale_insert_default()
{
	global $DB, $current_locale, $locales, $install_test_features;

	$activate_locales = array();

	if( ! empty( $install_test_features ) )
	{	// Activate also additional locales on install:
		$activate_locales[] = 'en-US';
		$activate_locales[] = 'de-DE';
		$activate_locales[] = 'fr-FR';
		$activate_locales[] = 'ru-RU';
	}

	if( ! empty( $current_locale ) )
	{ // Make sure the user sees his new system localized.
		$activate_locales[] = $current_locale;
	}

	$activate_locales = array_unique( $activate_locales );

	if( ! empty( $activate_locales ) )
	{ // Insert locales into DB
		$SQL = new SQL( 'Get locales' );
		$SQL->SELECT( 'loc_locale' );
		$SQL->FROM( 'T_locales' );
		$existing_locales = $DB->get_col( $SQL );

		$insert_data = array();
		foreach( $activate_locales as $a_locale )
		{
			if( !isset( $locales[ $a_locale ] ) || in_array( $a_locale, $existing_locales ) )
			{ // Skip an incorrect locale or it already exists in DB
				continue;
			}

			// Make sure default transliteration_map is set
			$transliteration_map = '';
			if( isset( $locales[ $a_locale ]['transliteration_map'] ) && is_array( $locales[ $a_locale ]['transliteration_map'] ) )
			{
				$transliteration_map = base64_encode( serialize( $locales[ $a_locale ]['transliteration_map'] ) );
			}

			$insert_data[] = '( '.$DB->quote( $a_locale ).', '
				.$DB->quote( $locales[ $a_locale ]['datefmt'] ).', '
				.$DB->quote( empty( $locales[ $a_locale ]['longdatefmt'] ) ? str_replace( 'y', 'Y', $locales[ $a_locale ]['datefmt'] ) : $locales[ $a_locale ]['longdatefmt'] ).', '
				.$DB->quote( empty( $locales[ $a_locale ]['extdatefmt'] ) ? str_replace( array( '.', ',', '\\', '/' ), ' ', str_replace( array( 'y', 'm' ), array( 'Y', 'M' ), $locales[ $a_locale ]['datefmt'] ) ) : $locales[ $a_locale ]['extdatefmt'] ).', '
				.$DB->quote( $locales[ $a_locale ]['input_datefmt'] ).', '
				.$DB->quote( $locales[ $a_locale ]['timefmt'] ).', '
				.$DB->quote( empty( $locales[ $a_locale ]['shorttimefmt'] ) ? str_replace( ':s', '', $locales[ $a_locale ]['timefmt'] ) : $locales[ $a_locale ]['shorttimefmt'] ).', '
				.$DB->quote( $locales[ $a_locale ]['input_timefmt'] ).', '
				.$DB->quote( $locales[ $a_locale ]['startofweek'] ).', '
				.$DB->quote( $locales[ $a_locale ]['name'] ).', '
				.$DB->quote( $locales[ $a_locale ]['messages'] ).', '
				.$DB->quote( $locales[ $a_locale ]['priority'] ).', '
				.$DB->quote( $transliteration_map ).', '
				.'1 )';
		}

		if( count( $insert_data ) )
		{ // Do insert only if at least one locale requires this
			$DB->query( 'INSERT INTO T_locales '
					 .'( loc_locale, loc_datefmt, loc_longdatefmt, loc_extdatefmt, loc_input_datefmt, loc_timefmt, loc_shorttimefmt, loc_input_timefmt, '
					 .'loc_startofweek, loc_name, loc_messages, loc_priority, '
					 .'loc_transliteration_map, loc_enabled ) '
					 .'VALUES '.implode( ', ', $insert_data ) );
		}
	}
}


/**
 * Restore default values of locales
 *
 * @param array Locale keys/codes
 */
function locale_restore_defaults( $restored_locales )
{
	global $DB, $locales;

	if( empty( $restored_locales ) && ! is_array( $restored_locales ) )
	{	// No locales to restore, Exit here:
		return;
	}

	foreach( $restored_locales as $restored_locale_key )
	{
		if( ! isset( $locales[ $restored_locale_key ] ) )
		{	// Skip an incorrect locale:
			continue;
		}

		$restored_locale = $locales[ $restored_locale_key ];

		// Make sure default transliteration_map is set:
		$transliteration_map = '';
		if( isset( $restored_locale['transliteration_map'] ) && is_array( $restored_locale['transliteration_map'] ) )
		{
			$transliteration_map = base64_encode( serialize( $restored_locale['transliteration_map'] ) );
		}

		// Restore all locale fields to default values:
		$DB->query( 'UPDATE T_locales SET
			loc_datefmt             = '.$DB->quote( $restored_locale['datefmt'] ).',
			loc_longdatefmt         = '.$DB->quote( empty( $restored_locale['longdatefmt'] ) ? str_replace( 'y', 'Y', $restored_locale['datefmt'] ) : $restored_locale['longdatefmt'] ).',
			loc_extdatefmt          = '.$DB->quote( empty( $restored_locale['extdatefmt'] ) ? str_replace( array( '.', '-', '\\', '/'), ' ', str_replace( array( 'y', 'm' ), array( 'Y', 'M' ), $restored_locale['datefmt'] ) ) : $restored_locale['extdatefmt'] ).',
			loc_input_datefmt       = '.$DB->quote( $restored_locale['input_datefmt'] ).',
			loc_timefmt             = '.$DB->quote( $restored_locale['timefmt'] ).',
			loc_shorttimefmt        = '.$DB->quote( empty( $restored_locale['shorttimefmt'] ) ? str_replace( ':s', '', $restored_locale['timefmt'] ) : $restored_locale['shorttimefmt'] ).',
			loc_input_timefmt       = '.$DB->quote( $restored_locale['input_timefmt'] ).',
			loc_startofweek         = '.$DB->quote( $restored_locale['startofweek'] ).',
			loc_name                = '.$DB->quote( $restored_locale['name'] ).',
			loc_messages            = '.$DB->quote( $restored_locale['messages'] ).',
			loc_priority            = '.$DB->quote( $restored_locale['priority'] ).',
			loc_transliteration_map = '.$DB->quote( $transliteration_map ).'
			WHERE loc_locale = '.$DB->quote( $restored_locale_key ) );
	}
}


/**
 * Check default locale and enable if it is not yet
 * Used after each upgrade process
 */
function locale_check_default()
{
	global $DB, $Settings, $default_locale, $locales;

	// Get default locale from DB:
	$current_default_locale = $Settings->get( 'default_locale' );

	if( isset( $locales[ $current_default_locale ] ) && $locales[ $current_default_locale ]['enabled'] )
	{	// Current default locale is correct and enabled
		// Nothing to fix, Exit here:
		return;
	}

	if( ! isset( $locales[ $current_default_locale ] ) )
	{	// If current default locale is wrong and doesn't exist anymore then use default locale from config:
		$current_default_locale = $default_locale;
		// Fix wrong default locale to correct value:
		$Settings->set( 'default_locale', $current_default_locale );
		$Settings->dbupdate();
	}

	// Get a row of current default locale in order to check if it is enabled:
	$SQL = new SQL( 'Check if current default locale is enabled' );
	$SQL->SELECT( 'loc_enabled' );
	$SQL->FROM( 'T_locales' );
	$SQL->WHERE( 'loc_locale = '.$DB->quote( $current_default_locale ) );
	$current_default_locale_enabled = $DB->get_var( $SQL );

	if( $current_default_locale_enabled !== NULL )
	{	// Current default locale exists in DB
		if( ! $current_default_locale_enabled )
		{	// Enable current default locale if it is not enabled yet:
			$DB->query( 'UPDATE T_locales
				  SET loc_enabled = 1
				WHERE loc_locale = '.$DB->quote( $current_default_locale ) );
		}
	}
	else
	{	// No record of default locale

		// Make sure default transliteration_map is set
		$transliteration_map = '';
		if( isset( $locales[ $current_default_locale ]['transliteration_map'] ) && is_array( $locales[ $current_default_locale ]['transliteration_map'] ) )
		{
			$transliteration_map = base64_encode( serialize( $locales[ $current_default_locale ]['transliteration_map'] ) );
		}

		// Insert default locale into DB in order to make it enabled:
		$DB->query( 'INSERT INTO T_locales '
			.'( loc_locale, loc_datefmt, loc_longdatefmt, loc_extdatefmt, loc_input_datefmt, loc_timefmt, loc_shorttimefmt, loc_input_timefmt, '
			.'loc_startofweek, loc_name, loc_messages, loc_priority, '
			.'loc_transliteration_map, loc_enabled ) '
			.'VALUES ( '
			.$DB->quote( $current_default_locale ).', '
			.$DB->quote( $locales[ $current_default_locale ]['datefmt'] ).', '
			.$DB->quote( empty( $locales[ $current_default_locale ]['longdatefmt'] ) ? str_replace( 'y', 'Y', $locales[ $current_default_locale ]['datefmt'] ) : $locales[ $current_default_locale ]['longdatefmt'] ).', '
			.$DB->quote( empty( $locales[ $current_default_locale ]['extdatefmt'] ) ? str_replace( array( '.'. '-', '\\', '/' ), ' ', str_replace( array( 'y', 'm' ), array( 'Y', 'Y' ), $locales[ $current_default_locale ]['datefmt'] ) ) : $locales[ $current_default_locale ]['extdatefmt'] ).', '
			.$DB->quote( $locales[ $current_default_locale ]['input_datefmt'] ).', '
			.$DB->quote( $locales[ $current_default_locale ]['timefmt'] ).', '
			.$DB->quote( empty( $locales[ $current_default_locale ]['shorttimefmt'] ) ? str_replace( ':s', '', $locales[ $current_default_locale ]['timefmt'] ) : $locales[ $current_default_locale ]['shorttimefmt'] ).', '
			.$DB->quote( $locales[ $current_default_locale ]['input_timefmt'] ).', '
			.$DB->quote( $locales[ $current_default_locale ]['startofweek'] ).', '
			.$DB->quote( $locales[ $current_default_locale ]['name'] ).', '
			.$DB->quote( $locales[ $current_default_locale ]['messages'] ).', '
			.$DB->quote( $locales[ $current_default_locale ]['priority'] ).', '
			.$DB->quote( $transliteration_map ).', '
			.'1 )' );
	}

}


/**
 * Returns the locale's default field value
 *
 * @param string Locale field
 * @param string Locale name, '#' to get default value not specific to a locale
 * @param mixed Value to return if locale field is empty and no default value is assigned
 * @return mixed Locale field value
 */
function locale_get( $field, $locale = NULL, $default = NULL )
{
	global $locales;

	if( is_null( $locale ) )
	{
		global $current_locale;
		$locale = $current_locale;
	}

	if( $locale == '#' || empty( $locales[$locale][$field] ) )
	{
		$default_values = array(
			'datefmt' => 'Y-m-d',
			'longdatefmt' => 'Y-m-d',
			'extdatefmt' => 'M d, Y',
			'input_datefmt' => 'Y-m-d',
			'timefmt' => 'H:i:s',
			'shorttimefmt' => 'H:i',
			'input_timefmt' => 'H:i:s'
		);

		switch( $field )
		{
			case 'longdatefmt':
				if( isset( $locales[$locale]['datefmt'] ) )
				{
					return str_replace( 'y', 'Y', $locales[$locale]['datefmt'] );
				}
				else
				{
					return isset( $default ) ? $default : $default_values['longdatefmt'];
				}
				break;

			case 'extdatefmt':
				if( isset( $locales[$locale]['datefmt'] ) )
				{
					return str_replace( array( '.', '-', '\\', '/' ), ' ', str_replace( array( 'm', 'y' ), array( 'M', 'Y' ), $locales[$locale]['datefmt'] ) );
				}
				else
				{
					return isset( $default ) ? $default : $default_values['extdatefmt'];
				}
				break;

			case 'shorttimefmt':
				if( isset( $locales[$locale]['timefmt'] ) )
				{
					return str_replace( ':s', '', $locales[$locale]['timefmt'] );
				}
				else
				{
					return isset( $default ) ? $default : $default_values['shorttimefmt'];
				}
				break;

			default:
				return isset( $default_values[$field] ) ? $default_values[$field] : ( isset( $default ) ? $default : NULL );
		}
	}
	else
	{
		return $locales[$locale][$field];
	}
}


/**
 * Set default values of locales formats
 *
 * @param array Locale
 * @param array Source locale
 */
function locale_set_default_formats( & $locale, $source_locale = NULL )
{
	if( is_null( $source_locale ) )
	{
		$source_locale = $locale;
	}

	foreach( $source_locale as $l_key => $l_value )
	{
		if( $default_value = locale_get( $l_key, $source_locale ) )
		{
			$locale[$l_key] = $default_value;
		}

	}
}


/**
 * Provide normalizer_normalize for older versions of PHP < 5.3.0 and PECL intl < 1.0.0
 *
 * Normalizes the input provided and returns the normalized string
 *
 * @param string The input string to normalize
 * @param integer One of the normalization forms
 * @return string The normalized string or FALSE if an error occurred
 */
if( ! function_exists( 'normalizer_normalize' ) )
{
	function normalizer_normalize( $input, $form = NULL )
	{
		$transliteration = array(
			'Ĳ'=>'I','Ö'=>'O','Œ'=>'O','Ü'=>'U','ä'=>'a','æ'=>'a',
			'ĳ'=>'i','ö'=>'o','œ'=>'o','ü'=>'u','ß'=>'s','ſ'=>'s',
			'À'=>'A','Á'=>'A','Â'=>'A','Ã'=>'A','Ä'=>'A','Å'=>'A',
			'Æ'=>'A','Ā'=>'A','Ą'=>'A','Ă'=>'A','Ç'=>'C','Ć'=>'C',
			'Č'=>'C','Ĉ'=>'C','Ċ'=>'C','Ď'=>'D','Đ'=>'D','È'=>'E',
			'É'=>'E','Ê'=>'E','Ë'=>'E','Ē'=>'E','Ę'=>'E','Ě'=>'E',
			'Ĕ'=>'E','Ė'=>'E','Ĝ'=>'G','Ğ'=>'G','Ġ'=>'G','Ģ'=>'G',
			'Ĥ'=>'H','Ħ'=>'H','Ì'=>'I','Í'=>'I','Î'=>'I','Ï'=>'I',
			'Ī'=>'I','Ĩ'=>'I','Ĭ'=>'I','Į'=>'I','İ'=>'I','Ĵ'=>'J',
			'Ķ'=>'K','Ľ'=>'K','Ĺ'=>'K','Ļ'=>'K','Ŀ'=>'K','Ł'=>'L',
			'Ñ'=>'N','Ń'=>'N','Ň'=>'N','Ņ'=>'N','Ŋ'=>'N','Ò'=>'O',
			'Ó'=>'O','Ô'=>'O','Õ'=>'O','Ø'=>'O','Ō'=>'O','Ő'=>'O',
			'Ŏ'=>'O','Ŕ'=>'R','Ř'=>'R','Ŗ'=>'R','Ś'=>'S','Ş'=>'S',
			'Ŝ'=>'S','Ș'=>'S','Š'=>'S','Ť'=>'T','Ţ'=>'T','Ŧ'=>'T',
			'Ț'=>'T','Ù'=>'U','Ú'=>'U','Û'=>'U','Ū'=>'U','Ů'=>'U',
			'Ű'=>'U','Ŭ'=>'U','Ũ'=>'U','Ų'=>'U','Ŵ'=>'W','Ŷ'=>'Y',
			'Ÿ'=>'Y','Ý'=>'Y','Ź'=>'Z','Ż'=>'Z','Ž'=>'Z','à'=>'a',
			'á'=>'a','â'=>'a','ã'=>'a','ā'=>'a','ą'=>'a','ă'=>'a',
			'å'=>'a','ç'=>'c','ć'=>'c','č'=>'c','ĉ'=>'c','ċ'=>'c',
			'ď'=>'d','đ'=>'d','è'=>'e','é'=>'e','ê'=>'e','ë'=>'e',
			'ē'=>'e','ę'=>'e','ě'=>'e','ĕ'=>'e','ė'=>'e','ƒ'=>'f',
			'ĝ'=>'g','ğ'=>'g','ġ'=>'g','ģ'=>'g','ĥ'=>'h','ħ'=>'h',
			'ì'=>'i','í'=>'i','î'=>'i','ï'=>'i','ī'=>'i','ĩ'=>'i',
			'ĭ'=>'i','į'=>'i','ı'=>'i','ĵ'=>'j','ķ'=>'k','ĸ'=>'k',
			'ł'=>'l','ľ'=>'l','ĺ'=>'l','ļ'=>'l','ŀ'=>'l','ñ'=>'n',
			'ń'=>'n','ň'=>'n','ņ'=>'n','ŉ'=>'n','ŋ'=>'n','ò'=>'o',
			'ó'=>'o','ô'=>'o','õ'=>'o','ø'=>'o','ō'=>'o','ő'=>'o',
			'ŏ'=>'o','ŕ'=>'r','ř'=>'r','ŗ'=>'r','ś'=>'s','š'=>'s',
			'ť'=>'t','ù'=>'u','ú'=>'u','û'=>'u','ū'=>'u','ů'=>'u',
			'ű'=>'u','ŭ'=>'u','ũ'=>'u','ų'=>'u','ŵ'=>'w','ÿ'=>'y',
			'ý'=>'y','ŷ'=>'y','ż'=>'z','ź'=>'z','ž'=>'z','Α'=>'A',
			'Ά'=>'A','Ἀ'=>'A','Ἁ'=>'A','Ἂ'=>'A','Ἃ'=>'A','Ἄ'=>'A',
			'Ἅ'=>'A','Ἆ'=>'A','Ἇ'=>'A','ᾈ'=>'A','ᾉ'=>'A','ᾊ'=>'A',
			'ᾋ'=>'A','ᾌ'=>'A','ᾍ'=>'A','ᾎ'=>'A','ᾏ'=>'A','Ᾰ'=>'A',
			'Ᾱ'=>'A','Ὰ'=>'A','ᾼ'=>'A','Β'=>'B','Γ'=>'G','Δ'=>'D',
			'Ε'=>'E','Έ'=>'E','Ἐ'=>'E','Ἑ'=>'E','Ἒ'=>'E','Ἓ'=>'E',
			'Ἔ'=>'E','Ἕ'=>'E','Ὲ'=>'E','Ζ'=>'Z','Η'=>'I','Ή'=>'I',
			'Ἠ'=>'I','Ἡ'=>'I','Ἢ'=>'I','Ἣ'=>'I','Ἤ'=>'I','Ἥ'=>'I',
			'Ἦ'=>'I','Ἧ'=>'I','ᾘ'=>'I','ᾙ'=>'I','ᾚ'=>'I','ᾛ'=>'I',
			'ᾜ'=>'I','ᾝ'=>'I','ᾞ'=>'I','ᾟ'=>'I','Ὴ'=>'I','ῌ'=>'I',
			'Θ'=>'T','Ι'=>'I','Ί'=>'I','Ϊ'=>'I','Ἰ'=>'I','Ἱ'=>'I',
			'Ἲ'=>'I','Ἳ'=>'I','Ἴ'=>'I','Ἵ'=>'I','Ἶ'=>'I','Ἷ'=>'I',
			'Ῐ'=>'I','Ῑ'=>'I','Ὶ'=>'I','Κ'=>'K','Λ'=>'L','Μ'=>'M',
			'Ν'=>'N','Ξ'=>'K','Ο'=>'O','Ό'=>'O','Ὀ'=>'O','Ὁ'=>'O',
			'Ὂ'=>'O','Ὃ'=>'O','Ὄ'=>'O','Ὅ'=>'O','Ὸ'=>'O','Π'=>'P',
			'Ρ'=>'R','Ῥ'=>'R','Σ'=>'S','Τ'=>'T','Υ'=>'Y','Ύ'=>'Y',
			'Ϋ'=>'Y','Ὑ'=>'Y','Ὓ'=>'Y','Ὕ'=>'Y','Ὗ'=>'Y','Ῠ'=>'Y',
			'Ῡ'=>'Y','Ὺ'=>'Y','Φ'=>'F','Χ'=>'X','Ψ'=>'P','Ω'=>'O',
			'Ώ'=>'O','Ὠ'=>'O','Ὡ'=>'O','Ὢ'=>'O','Ὣ'=>'O','Ὤ'=>'O',
			'Ὥ'=>'O','Ὦ'=>'O','Ὧ'=>'O','ᾨ'=>'O','ᾩ'=>'O','ᾪ'=>'O',
			'ᾫ'=>'O','ᾬ'=>'O','ᾭ'=>'O','ᾮ'=>'O','ᾯ'=>'O','Ὼ'=>'O',
			'ῼ'=>'O','α'=>'a','ά'=>'a','ἀ'=>'a','ἁ'=>'a','ἂ'=>'a',
			'ἃ'=>'a','ἄ'=>'a','ἅ'=>'a','ἆ'=>'a','ἇ'=>'a','ᾀ'=>'a',
			'ᾁ'=>'a','ᾂ'=>'a','ᾃ'=>'a','ᾄ'=>'a','ᾅ'=>'a','ᾆ'=>'a',
			'ᾇ'=>'a','ὰ'=>'a','ᾰ'=>'a','ᾱ'=>'a','ᾲ'=>'a','ᾳ'=>'a',
			'ᾴ'=>'a','ᾶ'=>'a','ᾷ'=>'a','β'=>'b','γ'=>'g','δ'=>'d',
			'ε'=>'e','έ'=>'e','ἐ'=>'e','ἑ'=>'e','ἒ'=>'e','ἓ'=>'e',
			'ἔ'=>'e','ἕ'=>'e','ὲ'=>'e','ζ'=>'z','η'=>'i','ή'=>'i',
			'ἠ'=>'i','ἡ'=>'i','ἢ'=>'i','ἣ'=>'i','ἤ'=>'i','ἥ'=>'i',
			'ἦ'=>'i','ἧ'=>'i','ᾐ'=>'i','ᾑ'=>'i','ᾒ'=>'i','ᾓ'=>'i',
			'ᾔ'=>'i','ᾕ'=>'i','ᾖ'=>'i','ᾗ'=>'i','ὴ'=>'i','ῂ'=>'i',
			'ῃ'=>'i','ῄ'=>'i','ῆ'=>'i','ῇ'=>'i','θ'=>'t','ι'=>'i',
			'ί'=>'i','ϊ'=>'i','ΐ'=>'i','ἰ'=>'i','ἱ'=>'i','ἲ'=>'i',
			'ἳ'=>'i','ἴ'=>'i','ἵ'=>'i','ἶ'=>'i','ἷ'=>'i','ὶ'=>'i',
			'ῐ'=>'i','ῑ'=>'i','ῒ'=>'i','ῖ'=>'i','ῗ'=>'i','κ'=>'k',
			'λ'=>'l','μ'=>'m','ν'=>'n','ξ'=>'k','ο'=>'o','ό'=>'o',
			'ὀ'=>'o','ὁ'=>'o','ὂ'=>'o','ὃ'=>'o','ὄ'=>'o','ὅ'=>'o',
			'ὸ'=>'o','π'=>'p','ρ'=>'r','ῤ'=>'r','ῥ'=>'r','σ'=>'s',
			'ς'=>'s','τ'=>'t','υ'=>'y','ύ'=>'y','ϋ'=>'y','ΰ'=>'y',
			'ὐ'=>'y','ὑ'=>'y','ὒ'=>'y','ὓ'=>'y','ὔ'=>'y','ὕ'=>'y',
			'ὖ'=>'y','ὗ'=>'y','ὺ'=>'y','ῠ'=>'y','ῡ'=>'y','ῢ'=>'y',
			'ῦ'=>'y','ῧ'=>'y','φ'=>'f','χ'=>'x','ψ'=>'p','ω'=>'o',
			'ώ'=>'o','ὠ'=>'o','ὡ'=>'o','ὢ'=>'o','ὣ'=>'o','ὤ'=>'o',
			'ὥ'=>'o','ὦ'=>'o','ὧ'=>'o','ᾠ'=>'o','ᾡ'=>'o','ᾢ'=>'o',
			'ᾣ'=>'o','ᾤ'=>'o','ᾥ'=>'o','ᾦ'=>'o','ᾧ'=>'o','ὼ'=>'o',
			'ῲ'=>'o','ῳ'=>'o','ῴ'=>'o','ῶ'=>'o','ῷ'=>'o','А'=>'A',
			'Б'=>'B','В'=>'V','Г'=>'G','Д'=>'D','Е'=>'E','Ё'=>'E',
			'Ж'=>'Z','З'=>'Z','И'=>'I','Й'=>'I','К'=>'K','Л'=>'L',
			'М'=>'M','Н'=>'N','О'=>'O','П'=>'P','Р'=>'R','С'=>'S',
			'Т'=>'T','У'=>'U','Ф'=>'F','Х'=>'K','Ц'=>'T','Ч'=>'C',
			'Ш'=>'S','Щ'=>'S','Ы'=>'Y','Э'=>'E','Ю'=>'Y','Я'=>'Y',
			'а'=>'A','б'=>'B','в'=>'V','г'=>'G','д'=>'D','е'=>'E',
			'ё'=>'E','ж'=>'Z','з'=>'Z','и'=>'I','й'=>'I','к'=>'K',
			'л'=>'L','м'=>'M','н'=>'N','о'=>'O','п'=>'P','р'=>'R',
			'с'=>'S','т'=>'T','у'=>'U','ф'=>'F','х'=>'K','ц'=>'T',
			'ч'=>'C','ш'=>'S','щ'=>'S','ы'=>'Y','э'=>'E','ю'=>'Y',
			'я'=>'Y','ð'=>'d','Ð'=>'D','þ'=>'t','Þ'=>'T','ა'=>'a',
			'ბ'=>'b','გ'=>'g','დ'=>'d','ე'=>'e','ვ'=>'v','ზ'=>'z',
			'თ'=>'t','ი'=>'i','კ'=>'k','ლ'=>'l','მ'=>'m','ნ'=>'n',
			'ო'=>'o','პ'=>'p','ჟ'=>'z','რ'=>'r','ს'=>'s','ტ'=>'t',
			'უ'=>'u','ფ'=>'p','ქ'=>'k','ღ'=>'g','ყ'=>'q','შ'=>'s',
			'ჩ'=>'c','ც'=>'t','ძ'=>'d','წ'=>'t','ჭ'=>'c','ხ'=>'k',
			'ჯ'=>'j','ჰ'=>'h','ʼ'=>"'",'̧' =>'', 'ḩ'=>'h','ʼ'=>"'",
			'‘'=>"'",'’'=>"'",'ừ'=>'u','ế'=>'e','ả'=>'a','ị'=>'i',
			'ậ'=>'a','ệ'=>'e','ỉ'=>'i','ộ'=>'o','ồ'=>'o','ề'=>'e',
			'ơ'=>'o','ạ'=>'a','ẵ'=>'a','ư'=>'u','ắ'=>'a','ằ'=>'a',
			'ầ'=>'a','ḑ'=>'d','Ḩ'=>'H','Ḑ'=>'D','ḑ'=>'d','ş'=>'s',
			'ā'=>'a','ţ'=>'t'
		);

		return strtr( $input, $transliteration );
	}
}


/**
 * Get currency data from locale
 *
 * @param string Locale code, '#' - to use current currency
 * @param string What return: 'code', 'symbol', 'ID'
 * @return string|boolean Currency data depending on param $return OR FALSE on failed
 */
function locale_currency( $locale = '#', $return = 'code' )
{
	global $current_locale;

	if( $locale == '#' )
	{	// Use current locale:
		$locale = $current_locale;
	}

	// Extract country code from locale code:
	$country_code = strtolower( substr( $locale, 3, 2 ) );

	$CountryCache = & get_CountryCache();
	if( ! ( $locale_Country = & $CountryCache->get_by_name( $country_code, false, false ) ) )
	{	// Couldn't detect country by code:
		return false;
	}

	if( ! ( $country_Currency = & $locale_Country->get_Currency() ) )
	{	// Couldn't detect currency for the locale country:
		return false;
	}

	// Return currency code depending on param $return:
	switch( $return )
	{
		case 'symbol':
			return $country_Currency->get( 'shortcut' );

		case 'ID':
			return $country_Currency->ID;

		case 'code':
		default:
			return $country_Currency->get( 'code' );
	}
}
?>