<?php
# -- BEGIN LICENSE BLOCK ---------------------------------------
#
# This file is part of Clearbricks.
#
# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
# Licensed under the GPL version 2.0 license.
# See LICENSE file or
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
#
# -- END LICENSE BLOCK -----------------------------------------

/**
* Localization tools
*
* @package Clearbricks
* @subpackage Common
*/

if (!function_exists('__')) {

	/**
	 * Translated string
	 *
	 * @see l10n::trans()
	 *
	 * @param string $singular Singular form of the string
	 * @param string $pural Plural form of the string (optionnal)
	 * @param integer $count Context number for plural form (optionnal)
	 * @return string translated string
	 */
	function __($singular, $plural=null, $count=null)
	{
		return l10n::trans($singular, $plural, $count);
	}
}

/**
* Localization utilities
*/
class l10n
{
	/// @name Languages properties
	//@{
	protected static $languages_definitions         = array();
	protected static $languages_name                = null;
	protected static $languages_textdirection       = null;
	protected static $languages_pluralsnumber       = null;
	protected static $languages_pluralexpression    = null;
	//@}

	/// @name Current language properties
	//@{
	protected static $language_code                 = null;
	protected static $language_name                 = null;
	protected static $language_textdirection        = null;
	protected static $language_pluralsnumber        = null;
	protected static $language_pluralexpression     = null;
	protected static $language_pluralfunction       = null;
	//@}

	/** @ignore @deprecated */
	public static $text_direction;

	/** @ignore @deprecated */
	protected static $langs = array();

	/**
	 * L10N initialization
	 *
	 * Create global arrays for L10N stuff. Should be called before any work
	 * with other methods. For plural-forms, __l10n values can now be array.
	 *
	 * @param string $code Language code to work with
	 */
	public static function init($code='en')
	{
		$GLOBALS['__l10n'] = $GLOBALS['__l10n_files'] = array();

		self::lang($code);
	}

	/**
	 * Set a language to work on or return current working language code
	 *
	 * This set up language properties to manage plurals form.
	 * Change of language code not reset global array of L10N stuff.
	 *
	 * @param string $code Language code
	 * @return string Current language code
	 */
	public static function lang($code=null)
	{
		if ($code !== null && self::$language_code != $code && self::isCode($code)) {
			self::$language_code                = $code;
			self::$language_name                = self::getLanguageName($code);
			self::$language_textdirection       = self::getLanguageTextDirection($code);
			self::$language_pluralsnumber       = self::getLanguagePluralsNumber($code);
			self::$language_pluralexpression    = self::getLanguagePluralExpression($code);

			self::$language_pluralfunction = self::createPluralFunction(
				self::$language_pluralsnumber, 
				self::$language_pluralexpression
			);

			// Backwards compatibility
			self::$text_direction = self::$language_textdirection;
		}

		return self::$language_code;
	}

	/**
	 * Translate a string
	 *
	 * Returns a translated string of $singular 
	 * or $plural according to a number if it is set. 
	 * If translation is not found, returns the string.
	 *
	 * @param string $singular Singular form of the string
	 * @param string $plural Plural form of the string (optionnal)
	 * @param integer $count Context number for plural form (optionnal)
	 * @return string Translated string
	 */
	public static function trans($singular, $plural=null, $count=null)
	{
		// If no string to translate, return no string
		if ($singular == '') {

			return '';

		// If no l10n translation loaded or exists
		} elseif ((!array_key_exists('__l10n',$GLOBALS) || empty($GLOBALS['__l10n']) 
			|| !array_key_exists($singular, $GLOBALS['__l10n'])) && is_null($count)) {

			return $singular;

		// If no $plural form or if current language has no plural form return $singular translation
		} elseif ($plural === null || $count === null || self::$language_pluralsnumber == 1) {
			$t = !empty($GLOBALS['__l10n'][$singular]) ? $GLOBALS['__l10n'][$singular] : $singular;

			return is_array($t) ? $t[0] : $t;

		// Else return translation according to $count
		} else {
			$i = self::index($count);

			// If it is a plural and translation exists in "singular" form
			if ($i > 0 && !empty($GLOBALS['__l10n'][$plural])) {
			$t = $GLOBALS['__l10n'][$plural];

			return is_array($t) ? $t[0] : $t;

			// If it is plural and index exists in plurals translations
			} elseif (!empty($GLOBALS['__l10n'][$singular]) 
			&& is_array($GLOBALS['__l10n'][$singular]) 
			&& array_key_exists($i,$GLOBALS['__l10n'][$singular]) 
			&& !empty($GLOBALS['__l10n'][$singular][$i])) {

				return $GLOBALS['__l10n'][$singular][$i];

			// Else return input string according to "en" plural form
			} else {

				return $i > 0 ? $plural : $singular;
			}
		}
	}

	/**
	 * Retrieve plural index from input number
	 *
	 * @param integer $count Number to take account
	 * @return integer Index of plural form
	 */
	public static function index($count)
	{
		return call_user_func(self::$language_pluralfunction, $count);
	}

	/**
	 * Add a file
	 *
	 * Adds a l10n file in translation strings. $file should be given without
	 * extension. This method will look for $file.lang.php and $file.po (in this
	 * order) and retrieve the first one found.
	 * We not care about language (and plurals forms) of the file.
	 *
	 * @param string	$file		Filename (without extension)
	 * @return boolean True on success
	 */
	public static function set($file)
	{
		$lang_file = $file.'.lang';
		$po_file = $file.'.po';
		$php_file = $file.'.lang.php';

		if (file_exists($php_file)) {
			require $php_file;

		} elseif (($tmp = self::getPoFile($po_file)) !== false) {
			$GLOBALS['__l10n_files'][] = $po_file;
			$GLOBALS['__l10n'] = $tmp + $GLOBALS['__l10n']; // "+" erase numeric keys unlike array_merge

		} elseif (($tmp = self::getLangFile($lang_file)) !== false) {
			$GLOBALS['__l10n_files'][] = $lang_file;
			$GLOBALS['__l10n'] = $tmp + $GLOBALS['__l10n']; // "+" erase numeric keys unlike array_merge

		} else {

			return false;
		}

		return true;
	}

	/**
	 * L10N file
	 *
	 * Returns a file path for a file, a directory and a language.
	 * If $dir/$lang/$file is not found, it will check if $dir/en/$file
	 * exists and returns the result. Returns false if no file were found.
	 *
	 * @param string	$dir		Directory
	 * @param string	$file	File
	 * @param string	$lang	Language
	 * @return string|false		File path or false
	 */
	public static function getFilePath($dir, $file, $lang)
	{
		$f = $dir.'/'.$lang.'/'.$file;
		if (!file_exists($f)) {
			$f = $dir.'/en/'.$file;
		}

		return file_exists($f) ? $f : false;
	}

	/** @ignore @deprecated */
	public static function getLangFile($file)
	{
		if (!file_exists($file)) {
			return false;
		}

		$fp = @fopen($file,'r');
		if ($fp === false) {
			return false;
		}

		$res = array();
		while ($l = fgets($fp))
		{
			$l = trim($l);
			# Comment
			if (substr($l,0,1) == '#') {
				continue;
			}

			# Original text
			if (substr($l,0,1) == ';' && ($t = fgets($fp)) !== false && trim($t) != '') {
				$res[$l] = trim($t);
			}
		}
		fclose($fp);

		return $res;
	}

	/// @name Gettext PO methods
	//@{
	/**
	 * Load gettext file
	 *
	 * Returns an array of strings found in a given gettext (.po) file
	 *
	 * @param string	$file		Filename
	 * @return array|false
	 */
	public static function getPoFile($file)
	{
		if (($m = self::parsePoFile($file)) === false) {

			return false;
		}

		if (empty($m[1])) {

			return array();
		}

		// Keep singular id and translations, remove headers and comments
		$r = array();
		foreach($m[1] as $v) {
			if (isset($v['msgid']) && isset($v['msgstr'])) {
				$r[$v['msgid']] = $v['msgstr'];
			}
		}

		return $r;
	}

	/**
	* Generates a PHP file from a po file
	*
	* Return a boolean depending on success or failure
	*
	* @param string $file File
	* @return boolean true on success
	*/
	public static function generatePhpFileFromPo($file,$license_block='')
	{
		$po_file = $file.'.po';
		$php_file = $file.'.lang.php';

		$strings = self::getPoFile($po_file);
		$fcontent =
		"<?php\n".
		$license_block.
		"#\n#\n#\n".
		"#		  DOT NOT MODIFY THIS FILE !\n\n\n\n\n";
		
		foreach ($strings as $vo => $tr) {
			$vo = str_replace("'", "\\'", $vo);
			if (is_array($tr)) {
				foreach ($tr as $i => $t) {
					$t = str_replace("'", "\\'", $t);
					$fcontent .= '$GLOBALS[\'__l10n\'][\''.$vo.'\']['.$i.'] = \''.$t.'\';'."\n";
				}
			} else {
				$tr = str_replace("'", "\\'", $tr);
				$fcontent .= '$GLOBALS[\'__l10n\'][\''.$vo.'\'] = \''.$tr.'\';'."\n";
			}
		}
		
		if (($fp = fopen($php_file, 'w')) !== false) {
			fwrite($fp, $fcontent, strlen($fcontent));
			fclose($fp);
			return true;
		} else {
			return false;
		}
	}

	/**
	* Parse Po File
	*
	* Return an array of po headers and translations from a po file
	*
	* @param string $file File path
	* @return array Parsed file
	*/
	public static function parsePoFile($file)
	{
		// stop if file not exists
		if (!file_exists($file)) {

			return false;
		}

		// read file per line in array (without ending new line)
		if (false === ($lines = file($file, FILE_IGNORE_NEW_LINES))) {

			return false;
		}

		// prepare variables
		$headers = array(
			'Project-Id-Version' => '',
			'Report-Msgid-Bugs-To' => '',
			'POT-Creation-Date' => '',
			'PO-Revision-Date' => '',
			'Last-Translator' => '',
			'Language-Team' => '',
			'Content-Type' => '',
			'Content-Transfer-Encoding' => '',
			'Plural-Forms' => ''
			// there are more headers but these ones are default
		);
		$headers_searched = $headers_found = false;
		$h_line = $h_val = $h_key = '';
		$entries = $entry = $desc = array();
		$i = 0;

		// read through lines
		for($i = 0; $i < count($lines); $i++) {

			// some people like mirovinben add white space at the end of line
			$line = trim($lines[$i]);

			// jump to next line on blank one or empty comment (#)
			if (strlen($line) < 2) {
				continue;
			}

			// headers
			if (!$headers_searched && preg_match('/^msgid\s+""$/', trim($line))) {

				// headers start wih empty msgid and msgstr follow be multine
				if (!preg_match('/^msgstr\s+""$/', trim($lines[$i + 1])) 
				 || !preg_match('/^"(.*)"$/', trim($lines[$i + 2]))) {
						$headers_searched = true;
				} else {

					$l = $i + 2;
					while(false !== ($def = self::cleanPoLine('multi',$lines[$l++]))) {

						$h_line = self::cleanPoString($def[1]);

						// an header has key:val
						if (false === ($h_index = strpos($h_line, ':'))) {

							// multiline value
							if (!empty($h_key) && !empty($headers[$h_key])) {
								$headers[$h_key] = trim($headers[$h_key].$h_line);
								continue;

							// your .po file is so bad
							} else {
								$headers_searched = true;
								break;
							}
						}

						// extract key and value
						$h_key = substr($h_line, 0, $h_index);
						$h_val = substr($h_line, $h_index + 1);

						// unknow header
						if (!isset($headers[$h_key])) {
							//continue;
						}

						// ok it's an header, add it
						$headers[$h_key] = trim($h_val);
						$headers_found = true;
					}

					// headers found so stop search and clean previous comments
					if ($headers_found) {
						$headers_searched = true;
						$entry = $desc = array();
						$i = $l - 1;
						continue;
					}
				}
			}

			// comments
			if (false !== ($def = self::cleanPoLine('comment',$line))) {

				$str = self::cleanPoString($def[2]);

				switch ($def[1]) {

					// translator comments
					case ' ':
						if (!isset($desc['translator-comments'])) {
							$desc['translator-comments'] = $str;
						} else {
							$desc['translator-comments'] .= "\n" . $str;
						}
						break;

					// extracted comments
					case '.':
						if (!isset($desc['extracted-comments'])) {
							$desc['extracted-comments'] = $str;
						} else {
							$desc['extracted-comments'] .= "\n" . $str;
						}
						break;

					// reference
					case ':':
						if (!isset($desc['references'])) {
							$desc['references'] = array();
						}
						$desc['references'][] = $str;
						break;

					// flag
					case ',':
						if (!isset($desc['flags'])) {
							$desc['flags'] = array();
						}
						$desc['flags'][] = $str;
						break;

					// previous msgid, msgctxt
					case '|':
						// msgid
						if (strpos($def[2], 'msgid') === 0) {
							$desc['previous-msgid'] = $str;
						// msgcxt
						} else {
							$desc['previous-msgctxt'] = $str;
						}
						break;
				}
			}

			// msgid
			elseif (false !== ($def = self::cleanPoLine('msgid',$line))) {

				// add last translation and start new one
				if ((isset($entry['msgid']) || isset($entry['msgid_plural'])) && isset($entry['msgstr'])) {

					// save last translation and start new one
					$entries[] = $entry;
					$entry = array();

					// add comments to new translation
					if (!empty($desc)) {
						$entry = array_merge($entry, $desc);
						$desc = array();
					}
					
					// stop searching headers
					$headers_searched = true;
				}

				$str = self::cleanPoString($def[2]);

				// msgid_plural
				if (!empty($def[1])) {
					$entry['msgid_plural'] = $str;
				} else {
					$entry['msgid'] = $str;
				}
			}
			
			// msgstr
			elseif (false !== ($def = self::cleanPoLine('msgstr',$line))) {

				$str = self::cleanPoString($def[2]);

				// plural forms
				if (!empty($def[1])) {
					if (!isset($entry['msgstr'])) {
						$entry['msgstr'] = array();
					}
					$entry['msgstr'][] = $str;
				} else {
					$entry['msgstr'] = $str;
				}
			}

			// multiline
			elseif (false !== ($def = self::cleanPoLine('multi',$line))) {
			
				$str = self::cleanPoString($def[1]);

				// msgid
				if (!isset($entry['msgstr'])) {
					//msgid plural
					if (isset($entry['msgid_plural'])) {
						if (!is_array($entry['msgid_plural'])) {
							$entry['msgid_plural'] .= $str;
						} else {
							$entry['msgid_plural'][count($entry['msgid_plural']) - 1] .= $str;
						}
					} else {
						if (!is_array($entry['msgid'])) {
							$entry['msgid'] .= $str;
						} else {
							$entry['msgid'][count($entry['msgid']) - 1] .= $str;
						}
					}

				// msgstr
				} else {
					if (!is_array($entry['msgstr'])) {
						$entry['msgstr'] .= $str;
					} else {
						$entry['msgstr'][count($entry['msgstr']) - 1] .= $str;
					}
				}
			}
		}

		// Add last translation
		if (!empty($entry)) {
			if (!empty($desc)) {
				$entry = array_merge($entry, $desc);
			}
			$entries[] = $entry;
		}

		return array($headers, $entries);
	}

	/* @ignore */
	protected static function cleanPoLine($type,$_)
	{
			$patterns = array(
				'msgid' 	=> 'msgid(_plural|)\s+"(.*)"',
				'msgstr' 	=> 'msgstr(\[.*?\]|)\s+"(.*)"',
				'multi' 	=> '"(.*)"',
				'comment' 	=> '#\s*(\s|\.|:|\,|\|)\s*(.*)'
			);

			if (array_key_exists($type,$patterns) 
			 && preg_match('/^'.$patterns[$type].'$/i', trim($_), $m)) {

				return $m;
			}

			return false;
	}

	/* @ignore */
	protected static function cleanPoString($_)
	{
		return stripslashes(str_replace(array('\n','\r\n'),"\n",$_));
	}

	/**
	 * Extract nplurals and plural from po expression
	 *
	 * @param string $expression Plural form as of gettext Plural-form param
	 * @return array Number of plurals and cleaned plural expression
	 */
	public static function parsePluralExpression($expression)
	{
		return preg_match('/^\s*nplurals\s*=\s*(\d+)\s*;\s+plural\s*=\s*(.+)$/', $expression, $m) ? 
			array((integer) $m[1], trim(self::cleanPluralExpression($m[2]))) : 
			array(self::$language_pluralsnumber, self::$language_pluralexpression);
	}

	/**
	 * Create function to find plural msgstr index from gettext expression
	 *
	 * @param integer $nplurals Plurals number
	 * @param string $expression Plural expression
	 * @return function Function to extract right plural index
	 */
	public static function createPluralFunction($nplurals, $expression)
	{
		return create_function('$n', '$i = (integer) ('.str_replace('n', '$n', $expression).'); return ($i < '.$nplurals.') ? $i : '.$nplurals.' - 1;');
	}

	/* @ignore */
	protected static function cleanPluralExpression($_)
	{
		$_ .= ';';
		$r = '';
		$l = 0;

		for ($i = 0; $i < strlen($_); ++$i) {
			switch ($_[$i]) {
				case '?':
					$r .= ' ? (';
					$l++;
					break;

				case ':':
					$r .= ') : (';
					break;

				case ';':
					$r .= str_repeat(')', $l) . ';';
					$l= 0;
					break;

				default:
					$r .= $_[$i];
			}
		}

		return rtrim($r, ';');
	}
	//@}

	/// @name Languages definitions methods
	//@{
	/**
	 * Check if a language code exists
	 *
	 * @param string $code Language code
	 * @return boolean True if code exists
	 */
	public static function isCode($code)
	{
		return array_key_exists($code,self::getLanguagesName());
	}

	/**
	 * Get a language code according to a language name
	 *
	 * @param string $code Language name
	 * @return string Language code
	 */
	public static function getCode($code)
	{
		$_ = self::getLanguagesName();

		return (($index = array_search($code,$_)) !== false) ? $index : self::$language_code;
	}

	/**
	 * ISO Codes
	 *
	 * @param boolean	$flip			Flip resulting array
	 * @param boolean	$name_with_code	Prefix (code) to names
	 * @return array
	 */
	public static function getISOcodes($flip=false,$name_with_code=false)
	{
		$langs = self::getLanguagesName();
		if ($name_with_code) {
			foreach ($langs as $k => &$v) {
				$v = $k.' - '.$v;
			}
		}

		if ($flip) {
			return array_flip($langs);
		}

		return $langs;
	}

	/**
	 * Get a language name according to a lang code
	 *
	 * @param string $code Language code
	 * @return string Language name
	 */
	public static function getLanguageName($code)
	{
		$_ = self::getLanguagesName();

		return array_key_exists($code,$_) ? $_[$code] : self::$language_name;
	}

	/**
	 * Get languages names
	 *
	 * @return array List of languages names by languages codes
	 */
	public static function getLanguagesName()
	{
		if (empty(self::$languages_name)) {
			self::$languages_name = self::getLanguagesDefinitions(3);

			// Backwards compatibility
			self::$langs = self::$languages_name;
		}

		return self::$languages_name;
	}

	/**
	 * Get a text direction according to a language code
	 *
	 * @param string $code Language code
	 * @return string Text direction (rtl or ltr)
	 */
	public static function getLanguageTextDirection($code)
	{
		$_ = self::getLanguagesTextDirection();

		return array_key_exists($code,$_) ? $_[$code] : self::$language_textdirection;
	}

	/**
	 * Get languages text directions
	 *
	 * @return array List of text directions by languages codes
	 */
	public static function getLanguagesTextDirection()
	{
		if (empty(self::$languages_textdirection)) {
			self::$languages_textdirection = self::getLanguagesDefinitions(4);
		}

		return self::$languages_textdirection;
	}

	/**
	 * Text direction
	 *
	 * @deprecated
	 * @see l10n::getLanguageTextDirection()
	 *
	 * @param string	$lang	Language code
	 * @return string ltr or rtl
	 */
	public static function getTextDirection($lang)
	{
		return self::getLanguageTextDirection($lang);
	}

	/**
	 * Get a number of plurals according to a language code
	 *
	 * @param string $code Language code
	 * @return integer Number of plurals
	 */
	public static function getLanguagePluralsNumber($code)
	{
		$_ = self::getLanguagesPluralsNumber();

		return !empty($_[$code]) ? $_[$code] : self::$language_pluralsnumber;
	}

	/**
	 * Get languages numbers of plurals
	 *
	 * @return array List of numbers of plurals by languages codes
	 */
	public static function getLanguagesPluralsNumber()
	{
		if (empty(self::$languages_pluralsnumber)) {
			self::$languages_pluralsnumber = self::getLanguagesDefinitions(5);
		}

		return self::$languages_pluralsnumber;
	}

	/**
	 * Get a plural expression according to a language code
	 *
	 * @param string $code Language code
	 * @return string Plural expression
	 */
	public static function getLanguagePluralExpression($code)
	{
		$_ = self::getLanguagesPluralExpression();

		return !empty($_[$code]) ? $_[$code] : self::$language_pluralexpression;
	}

	/**
	 * Get languages plural expressions
	 *
	 * @return array List of plural expressions by languages codes
	 */
	public static function getLanguagesPluralExpression()
	{
		if (empty(self::$languages_pluralexpression)) {
			self::$languages_pluralexpression = self::getLanguagesDefinitions(6);
		}

		return self::$languages_pluralexpression;
	}

	/**
	 * Get languages definitions of a given type
	 *
	 * The list follows ISO 639.1 norm with additionnal IETF codes as pt-br
	 *
	 * Countries codes and names from
	 * {@link http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes}
	 * {@link http://www.gnu.org/software/gettext/manual/gettext.html#Language-Codes}
	 * {@link http://www.loc.gov/standards/iso639-2/php/English_list.php}
	 *
	 * Text direction from
	 * {@link http://translate.sourceforge.net/wiki/l10n/displaysettings}
	 * {@link http://meta.wikimedia.org/wiki/Template:List_of_language_names_ordered_by_code}
	 *
	 * Plural-forms taken from
	 * {@link http://translate.sourceforge.net/wiki/l10n/pluralforms}
	 *
	 * $languages_definitions types look like this:
	 * 0 = code ISO 639.1 (2 digit) + IETF add
	 * 1 = code ISO 639.2 (english 3 digit)
	 * 2 = English name
	 * 3 = natal name
	 * 4 = text direction (ltr or rtl)
	 * 5 = number of plurals (1 means no plural form)
	 * 6 = plural expression (as of gettext .po plural form)
	 *
	 * null values represent missing values
	 *
	 * @param integer $type Type of definition
	 * @param string $default Default value if definition is empty
	 * @return array List of requested definition by languages codes
	 */
	protected static function getLanguagesDefinitions($type, $default='')
	{
		if ($type < 0 || $type > 6) {

			return array();
		}

		if (empty(self::$languages_definitions)) {
			self::$languages_definitions =  array(
				array('aa', 'aar', 'Afar', 'Afaraf', 'ltr', null, null),
				array('ab', 'abk', 'Abkhazian', 'Аҧсуа', 'ltr', null, null),
				array('ae', 'ave', 'Avestan', 'Avesta', 'ltr', null, null),
				array('af', 'afr', 'Afrikaans', 'Afrikaans', 'ltr', 2, 'n != 1'),
				array('ak', 'aka', 'Akan', 'Akan', 'ltr', 2, 'n > 1)'),
				array('am', 'amh', 'Amharic', 'አማርኛ', 'ltr', 2, 'n > 1'),
				array('an', 'arg', 'Aragonese', 'Aragonés', 'ltr', 2, 'n != 1'),
				array('ar', 'ara', 'Arabic', '‫العربية', 'rtl', 6, 'n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5'),
				array('as', 'asm', 'Assamese', 'অসমীয়া', 'ltr', null, null),
				array('av', 'ava', 'Avaric', 'авар мацӀ', 'ltr', null, null),
				array('ay', 'aym', 'Aymara', 'Aymar aru', 'ltr', 1, '0'),
				array('az', 'aze', 'Azerbaijani', 'Azərbaycan dili', 'ltr', 2, 'n != 1'),

				array('ba', 'bak', 'Bashkir', 'башҡорт теле', 'ltr', null, null),
				array('be', 'bel', 'Belarusian', 'Беларуская', 'ltr', 3, 'n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2'),
				array('bg', 'bul', 'Bulgarian', 'български език', 'ltr', 2, 'n != 1'),
				array('bh', 'bih', 'Bihari languages', 'भोजपुरी', 'ltr', null, null),
				array('bi', 'bis', 'Bislama', 'Bislama', 'ltr', null, null),
				array('bm', 'bam', 'Bambara', 'Bamanankan', 'ltr', null, null),
				array('bn', 'ben', 'Bengali', 'বাংলা', 'ltr', 2, 'n != 1'),
				array('bo', 'tib', 'Tibetan', 'བོད་ཡིག', 'ltr', 1, '0'),
				array('br', 'bre', 'Breton', 'Brezhoneg', 'ltr', 2, 'n > 1'),
				array('bs', 'bos', 'Bosnian', 'Bosanski jezik', 'ltr', 3, 'n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2'),

				array('ca', 'cat', 'Catalan', 'Català', 'ltr', 2, 'n != 1'),
				array('ce', 'che', 'Chechen', 'нохчийн мотт', 'ltr', null, null),
				array('ch', 'cha', 'Chamorro', 'Chamoru', 'ltr', 3, 'n==1 ? 0 : (n>=2 && n<=4) ? 1 : 2'),
				array('co', 'cos', 'Corsican', 'Corsu', 'ltr', null, null),
				array('cr', 'cre', 'Cree', 'ᓀᐦᐃᔭᐍᐏᐣ', 'ltr', null, null),
				array('cs', 'cze', 'Czech', 'Česky', 'ltr', null, null),
				array('cu', 'chu', 'Church Slavonic', 'ѩзыкъ Словѣньскъ', 'ltr', null, null),
				array('cv', 'chv', 'Chuvash', 'чӑваш чӗлхи', 'ltr', null, null),
				array('cy', 'wel', 'Welsh', 'Cymraeg', 'ltr', 4, 'n==1 ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3'),

				array('da', 'dan', 'Danish', 'Dansk', 'ltr', 2, 'n != 1'),
				array('de', 'ger', 'German', 'Deutsch', 'ltr', 2, 'n != 1'),
				array('dv', 'div', 'Maldivian', 'ދިވެހި', 'rtl', null, null),
				array('dz', 'dzo', 'Dzongkha', 'རྫོང་ཁ', 'ltr', 1, '0'),

				array('ee', 'ewe', 'Ewe', 'Ɛʋɛgbɛ', 'ltr', null, null),
				array('el', 'gre', 'Greek', 'Ελληνικά', 'ltr', 2, 'n != 1'),
				array('en', 'eng', 'English', 'English', 'ltr', 2, 'n != 1'),
				array('eo', 'epo', 'Esperanto', 'Esperanto', 'ltr', 2, 'n != 1'),
				array('es', 'spa', 'Spanish', 'español', 'ltr', 2, 'n != 1'),
				array('es-ar', null, 'Argentinean Spanish', 'Argentinean Spanish', 'ltr', 2, 'n != 1'),
				array('et', 'est', 'Estonian', 'Eesti keel', 'ltr', 2, 'n != 1'),
				array('eu', 'baq', 'Basque', 'Euskara', 'ltr', 2, 'n != 1'),

				array('fa', 'per', 'Persian', '‫فارسی', 'rtl', 1, '0'),
				array('ff', 'ful', 'Fulah', 'Fulfulde', 'ltr', 2, 'n != 1'),
				array('fi', 'fin', 'Finnish', 'Suomen kieli', 'ltr', 2, 'n != 1'),
				array('fj', 'fij', 'Fijian', 'Vosa Vakaviti', 'ltr', null, null),
				array('fo', 'fao', 'Faroese', 'Føroyskt', 'ltr', 2, 'n != 1'),
				array('fr', 'fre', 'French', 'Français', 'ltr', 2, 'n > 1'),
				array('fy', 'fry', 'Western Frisian', 'Frysk', 'ltr', 2, 'n != 1'),

				array('ga', 'gle', 'Irish', 'Gaeilge', 'ltr', 5, 'n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4'),
				array('gd', 'gla', 'Gaelic', 'Gàidhlig', 'ltr', 4, '(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3'),
				array('gl', 'glg', 'Galician', 'Galego', 'ltr', 2, 'n != 1'),
				array('gn', 'grn', 'Guarani', "Avañe'ẽ", 'ltr', null, null),
				array('gu', 'guj', 'Gujarati', 'ગુજરાતી', 'ltr', 2, 'n != 1'),
				array('gv', 'glv', 'Manx', 'Ghaelg', 'ltr', null, null),

				array('ha', 'hau', 'Hausa', '‫هَوُسَ', 'rtl', 2, 'n != 1'),
				array('he', 'heb', 'Hebrew', '‫עברית', 'rtl', 2, 'n != 1'),
				array('hi', 'hin', 'Hindi', 'हिन्दी', 'ltr', 2, 'n != 1'),
				array('ho', 'hmo', 'Hiri Motu', 'Hiri Motu', 'ltr', null, null),
				array('hr', 'hrv', 'Croatian', 'Hrvatski', 'ltr', 3, 'n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2'),
				array('ht', 'hat', 'Haitian', 'Kreyòl ayisyen', 'ltr', null, null),
				array('hu', 'hun', 'Hungarian', 'Magyar', 'ltr', 2, 'n != 1'),
				array('hy', 'arm', 'Armenian', 'Հայերեն', 'ltr', 2, 'n != 1'),
				array('hz', 'her', 'Herero', 'Otjiherero', 'ltr', null, null),

				array('ia', 'ina', 'Interlingua', 'Interlingua', 'ltr', 2, 'n != 1'),
				array('id', 'ind', 'Indonesian', 'Bahasa Indonesia', 'ltr', 1, '0'),
				array('ie', 'ile', 'Interlingue', 'Interlingue', 'ltr', null, null),
				array('ig', 'ibo', 'Igbo', 'Igbo', 'ltr', null, null),
				array('ii', 'iii', 'Sichuan Yi', 'ꆇꉙ', 'ltr', null, null),
				array('ik', 'ipk', 'Inupiaq', 'Iñupiaq', 'ltr', null, null),
				array('io', 'ido', 'Ido', 'Ido', 'ltr', null, null),
				array('is', 'ice', 'Icelandic', 'Íslenska', 'ltr', 2, '(n%10!=1 || n%100==11) ? 1 : 0'),
				array('it', 'ita', 'Italian', 'Italiano', 'ltr', 2, 'n != 1'),
				array('iu', 'iku', 'Inuktitut', 'ᐃᓄᒃᑎᑐᑦ', 'ltr', null, null),

				array('ja', 'jpn', 'Japanese', '日本語', 'ltr', 1, '0'),
				array('jv', 'jav', 'Javanese', 'Basa Jawa', 'ltr', 2, 'n != 0'),

				array('ka', 'geo', 'Georgian', 'ქართული', 'ltr', 1, '0'),
				array('kg', 'kon', 'Kongo', 'KiKongo', 'ltr', null, null),
				array('ki', 'kik', 'Kikuyu', 'Gĩkũyũ', 'ltr', null, null),
				array('kj', 'kua', 'Kuanyama', 'Kuanyama', 'ltr', null, null),
				array('kk', 'kaz', 'Kazakh', 'Қазақ тілі', 'ltr', 1, '0'),
				array('kl', 'kal', 'Greenlandic', 'Kalaallisut', 'ltr', null, null),
				array('km', 'khm', 'Central Khmer', 'ភាសាខ្មែរ', 'ltr', 1, '0'),
				array('kn', 'kan', 'Kannada', 'ಕನ್ನಡ', 'ltr', 2, 'n != 1'),
				array('ko', 'kor', 'Korean', '한국어', 'ltr', 1, '0'),
				array('kr', 'kau', 'Kanuri', 'Kanuri', 'ltr', null, null),
				array('ks', 'kas', 'Kashmiri', 'कश्मीरी', 'rtl', null, null),
				array('ku', 'kur', 'Kurdish', 'Kurdî', 'ltr', 2, 'n!= 1'),
				array('kv', 'kom', 'Komi', 'коми кыв', 'ltr', null, null),
				array('kw', 'cor', 'Cornish', 'Kernewek', 'ltr', 4, 'n==1 ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3'),
				array('ky', 'kir', 'Kirghiz', 'кыргыз тили', 'ltr', 1, '0'),

				array('la', 'lat', 'Latin', 'Latine', 'ltr', null, null),
				array('lb', 'ltz', 'Luxembourgish', 'Lëtzebuergesch', 'ltr', 2, 'n != 1'),
				array('lg', 'lug', 'Ganda', 'Luganda', 'ltr', null, null),
				array('li', 'lim', 'Limburgan', 'Limburgs', 'ltr', null, null),
				array('ln', 'lin', 'Lingala', 'Lingála', 'ltr', 2, 'n>1'),
				array('lo', 'lao', 'Lao', 'ພາສາລາວ', 'ltr', 1, '0'),
				array('lt', 'lit', 'Lithuanian', 'Lietuvių kalba', 'ltr', 3, 'n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 or n%100>=20) ? 1 : 2'),
				array('lu', 'lub', 'Luba-Katanga', 'Luba-Katanga', 'ltr', null, null),
				array('lv', 'lav', 'Latvian', 'Latviešu valoda', 'ltr', 3, 'n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2'),

				array('mg', 'mlg', 'Malagasy', 'Malagasy fiteny', 'ltr', 2, 'n > 1'),
				array('mh', 'mah', 'Marshallese', 'Kajin M̧ajeļ', 'ltr', null, null),
				array('mi', 'mao', 'Maori', 'Te reo Māori', 'ltr', 2, 'n > 1'),
				array('mk', 'mac', 'Macedonian', 'македонски јазик', 'ltr', 2, 'n==1 || n%10==1 ? 0 : 1'),
				array('ml', 'mal', 'Malayalam', 'മലയാളം', 'ltr', 2, 'n != 1'),
				array('mn', 'mon', 'Mongolian', 'Монгол', 'ltr', 2, 'n != 1'),
				array('mo', null, 'Moldavian', 'Limba moldovenească', 'ltr', 3, 'n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2'), //cf: ro
				array('mr', 'mar', 'Marathi', 'मराठी', 'ltr', 2, 'n != 1'),
				array('ms', 'may', 'Malay', 'Bahasa Melayu', 'ltr', 1, '0'),
				array('mt', 'mlt', 'Maltese', 'Malti', 'ltr', 4, 'n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3'),
				array('my', 'bur', 'Burmese', 'ဗမာစာ', 'ltr', 1, '0'),

				array('na', 'nau', 'Nauru', 'Ekakairũ Naoero', 'ltr', null, null),
				array('nb', 'nob', 'Norwegian Bokmål', 'Norsk bokmål', 'ltr', 2, 'n != 1'),
				array('nd', 'nde', 'North Ndebele', 'isiNdebele', 'ltr', null, null),
				array('ne', 'nep', 'Nepali', 'नेपाली', 'ltr', 2, 'n != 1'),
				array('ng', 'ndo', 'Ndonga', 'Owambo', 'ltr', null, null),
				array('nl', 'dut', 'Flemish', 'Nederlands', 'ltr', 2, 'n != 1'),
				array('nl-be', null, 'Flemish', 'Nederlands (Belgium)', 'ltr', 2, 'n != 1'),
				array('nn', 'nno', 'Norwegian Nynorsk', 'Norsk nynorsk', 'ltr', 2, 'n != 1'),
				array('no', 'nor', 'Norwegian', 'Norsk', 'ltr', 2, 'n != 1'),
				array('nr', 'nbl', 'South Ndebele', 'Ndébélé', 'ltr', null, null),
				array('nv', 'nav', 'Navajo', 'Diné bizaad', 'ltr', null, null),
				array('ny', 'nya', 'Chichewa', 'ChiCheŵa', 'ltr', null, null),

				array('oc', 'oci', 'Occitan', 'Occitan', 'ltr', 2, 'n > 1'),
				array('oj', 'oji', 'Ojibwa', 'ᐊᓂᔑᓈᐯᒧᐎᓐ', 'ltr', null, null),
				array('om', 'orm', 'Oromo', 'Afaan Oromoo', 'ltr', null, null),
				array('or', 'ori', 'Oriya', 'ଓଡ଼ିଆ', 'ltr', 2, 'n != 1'),
				array('os', 'oss', 'Ossetian', 'Ирон æвзаг', 'ltr', null, null),

				array('pa', 'pan', 'Panjabi', 'ਪੰਜਾਬੀ', 'ltr', 2, 'n != 1'),
				array('pi', 'pli', 'Pali', 'पाऴि', 'ltr', null, null),
				array('pl', 'pol', 'Polish', 'Polski', 'ltr', 3, 'n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2'),
				array('ps', 'pus', 'Pushto', '‫پښتو', 'rtl', 2, 'n != 1'),
				array('pt', 'por', 'Portuguese', 'Português', 'ltr', 2, 'n != 1'),
				array('pt-br', null, 'Brazilian Portuguese', 'Português do Brasil', 'ltr', 2, 'n > 1'),

				array('qu', 'que', 'Quechua', 'Runa Simi', 'ltr', null, null),

				array('rm', 'roh', 'Romansh', 'Rumantsch grischun', 'ltr', 2, 'n != 1'),
				array('rn', 'run', 'Rundi', 'kiRundi', 'ltr', null, null),
				array('ro', 'rum', 'Romanian', 'Română', 'ltr', 3, 'n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2'),
				array('ru', 'rus', 'Russian', 'Русский', 'ltr', 3, 'n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2'),
				array('rw', 'kin', 'Kinyarwanda', 'IKinyarwanda', 'ltr', 2, 'n != 1'),

				array('sa', 'san', 'Sanskrit', 'संस्कृतम्', 'ltr', null, null),
				array('sc', 'srd', 'Sardinian', 'sardu', 'ltr', null, null),
				array('sd', 'snd', 'Sindhi', 'सिन्धी', 'ltr', 2, 'n != 1'),
				array('se', 'sme', 'Northern Sami', 'Davvisámegiella', 'ltr', null, null),
				array('sg', 'sag', 'Sango', 'Yângâ tî sängö', 'ltr', null, null),
				array('sh', null, null, 'SrpskoHrvatski', 'ltr', null, null), //!
				array('si', 'sin', 'Sinhalese', 'සිංහල', 'ltr', 2, 'n != 1'),
				array('sk', 'slo', 'Slovak', 'Slovenčina', 'ltr', 3, '(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2'),
				array('sl', 'slv', 'Slovenian', 'Slovenščina', 'ltr', 4, 'n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0'),
				array('sm', 'smo', 'Samoan', "Gagana fa'a Samoa", 'ltr', null, null),
				array('sn', 'sna', 'Shona', 'chiShona', 'ltr', null, null),
				array('so', 'som', 'Somali', 'Soomaaliga', 'ltr', 2, 'n != 1'),
				array('sq', 'alb', 'Albanian', 'Shqip', 'ltr', 2, 'n != 1'),
				array('sr', 'srp', 'Serbian', 'српски језик', 'ltr', 3, 'n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2'),
				array('ss', 'ssw', 'Swati', 'SiSwati', 'ltr', null, null),
				array('st', 'sot', 'Southern Sotho', 'seSotho', 'ltr', null, null),
				array('su', 'sun', 'Sundanese', 'Basa Sunda', 'ltr', 1, '0'),
				array('sv', 'swe', 'Swedish', 'Svenska', 'ltr', 2, 'n != 1'),
				array('sw', 'swa', 'Swahili', 'Kiswahili', 'ltr', 2, 'n != 1'),

				array('ta', 'tam', 'Tamil', 'தமிழ்', 'ltr', 2, 'n != 1'),
				array('te', 'tel', 'Telugu', 'తెలుగు', 'ltr', 2, 'n != 1'),
				array('tg', 'tgk', 'Tajik', 'тоҷикӣ', 'ltr', 2, 'n > 1'),
				array('th', 'tha', 'Thai', 'ไทย', 'ltr', 1, '0'),
				array('ti', 'tir', 'Tigrinya', 'ትግርኛ', 'ltr', 2, 'n > 1'),
				array('tk', 'tuk', 'Turkmen', 'Türkmen', 'ltr', 2, 'n != 1'),
				array('tl', 'tlg', 'Tagalog', 'Tagalog', 'ltr', null, null),
				array('tn', 'tsn', 'Tswana', 'seTswana', 'ltr', null, null),
				array('to', 'ton', 'Tonga', 'faka Tonga', 'ltr', null, null),
				array('tr', 'tur', 'Turkish', 'Türkçe', 'ltr', 2, 'n > 1'),
				array('ts', 'tso', 'Tsonga', 'xiTsonga', 'ltr', null, null),
				array('tt', 'tat', 'Tatar', 'татарча', 'ltr', 1, '0'),
				array('tw', 'twi', 'Twi', 'Twi', 'ltr', null, null),
				array('ty', 'tah', 'Tahitian', 'Reo Mā`ohi', 'ltr', null, null),

				array('ug', 'uig', 'Uighur', 'Uyƣurqə', 'ltr', 1, '0'),
				array('uk', 'ukr', 'Ukrainian', 'Українська', 'ltr', 3, 'n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2'),
				array('ur', 'urd', 'Urdu', '‫اردو', 'rtl', 2, 'n != 1'),
				array('uz', 'uzb', 'Uzbek', "O'zbek", 'ltr', 2, 'n > 1'),

				array('ve', 'ven', 'Venda', 'tshiVenḓa', 'ltr', null, null),
				array('vi', 'vie', 'Vietnamese', 'Tiếng Việt', 'ltr', 1, '0'),
				array('vo', 'vol', 'Volapük', 'Volapük', 'ltr', null, null),

				array('wa', 'wln', 'Walloon', 'Walon', 'ltr', 2, 'n > 1'),
				array('wo', 'wol', 'Wolof', 'Wollof', 'ltr', 1, '0'),

				array('xh', 'xho', 'Xhosa', 'isiXhosa', 'ltr', null, null),

				array('yi', 'yid', 'Yiddish', '‫ייִדיש', 'rtl', null, null),
				array('yo', 'yor', 'Yoruba', 'Yorùbá', 'ltr', 2, 'n != 1'),

				array('za', 'zha', 'Chuang', 'Saɯ cueŋƅ', 'ltr', null, null),
				array('zh', 'zhi', 'Chinese', '中文', 'ltr', 1, '0'),
				array('zh-hk', null, 'Honk Kong Chinese', '中文 (香港)', 'ltr', 1, '0'),
				array('zh-tw', null, 'Taiwan Chinese', '中文 (臺灣)', 'ltr', 1, '0'),
				array('zu', 'zul', 'Zulu', 'isiZulu', 'ltr', null, null),
			);
		}

		$r = array();
		foreach(self::$languages_definitions as $_) {
			$r[$_[0]] = empty($_[$type]) ? $default : $_[$type];
		}

		return $r;
	}
	//@}
}
?>