i18n.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <?php
  2. /**
  3. * ZeroBin
  4. *
  5. * a zero-knowledge paste bin
  6. *
  7. * @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
  8. * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  9. * @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
  10. * @version 0.20
  11. */
  12. /**
  13. * i18n
  14. *
  15. * provides internationalization tools like translation, browser language detection, etc.
  16. */
  17. class i18n
  18. {
  19. /**
  20. * translation cache
  21. *
  22. * @access private
  23. * @static
  24. * @var array
  25. */
  26. private static $_translations = array();
  27. /**
  28. * translate a string, alias for translate()
  29. *
  30. * @access public
  31. * @static
  32. * @param string $messageId
  33. * @param mixed $args one or multiple parameters injected into placeholders
  34. * @return string
  35. */
  36. public static function _($messageId)
  37. {
  38. return call_user_func_array(array('i18n', 'translate'), func_get_args());
  39. }
  40. /**
  41. * translate a string
  42. *
  43. * @access public
  44. * @static
  45. * @param string $messageId
  46. * @param mixed $args one or multiple parameters injected into placeholders
  47. * @return string
  48. */
  49. public static function translate($messageId)
  50. {
  51. if (empty($messageId)) return $messageId;
  52. if (count(self::$_translations) === 0) self::loadTranslations();
  53. if (!array_key_exists($messageId, self::$_translations))
  54. {
  55. self::$_translations[$messageId] = $messageId;
  56. }
  57. $args = func_get_args();
  58. $args[0] = self::$_translations[$messageId];
  59. return call_user_func_array('sprintf', $args);
  60. }
  61. /**
  62. * loads translations
  63. *
  64. * From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
  65. *
  66. * @access protected
  67. * @static
  68. * @return void
  69. */
  70. public static function loadTranslations()
  71. {
  72. // find a matching translation file
  73. $availableLanguages = array();
  74. $path = PUBLIC_PATH . DIRECTORY_SEPARATOR . 'i18n';
  75. $i18n = dir($path);
  76. while (false !== ($file = $i18n->read()))
  77. {
  78. if (preg_match('/^([a-z]{2}).json$/', $file, $match) === 1)
  79. {
  80. $availableLanguages[] = $match[1];
  81. }
  82. }
  83. $match = self::_getMatchingLanguage(
  84. self::getBrowserLanguages(), $availableLanguages
  85. );
  86. // load translations
  87. if ($match != 'en')
  88. {
  89. self::$_translations = json_decode(
  90. file_get_contents($path . DIRECTORY_SEPARATOR . $match . '.json'),
  91. true
  92. );
  93. }
  94. }
  95. /**
  96. * detect the clients supported languages and return them ordered by preference
  97. *
  98. * From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
  99. *
  100. * @return array
  101. */
  102. public static function getBrowserLanguages()
  103. {
  104. $languages = array();
  105. if (array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER))
  106. {
  107. $languageRanges = explode(',', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']));
  108. foreach ($languageRanges as $languageRange) {
  109. if (preg_match(
  110. '/(\*|[a-zA-Z0-9]{1,8}(?:-[a-zA-Z0-9]{1,8})*)(?:\s*;\s*q\s*=\s*(0(?:\.\d{0,3})|1(?:\.0{0,3})))?/',
  111. trim($languageRange), $match
  112. ))
  113. {
  114. if (!isset($match[2]))
  115. {
  116. $match[2] = '1.0';
  117. }
  118. else
  119. {
  120. $match[2] = (string) floatval($match[2]);
  121. }
  122. if (!isset($languages[$match[2]]))
  123. {
  124. $languages[$match[2]] = array();
  125. }
  126. $languages[$match[2]][] = strtolower($match[1]);
  127. }
  128. }
  129. krsort($languages);
  130. }
  131. return $languages;
  132. }
  133. /**
  134. * compares two language preference arrays and returns the preferred match
  135. *
  136. * From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
  137. *
  138. * @param array $acceptedLanguages
  139. * @param array $availableLanguages
  140. * @return string
  141. */
  142. protected static function _getMatchingLanguage($acceptedLanguages, $availableLanguages) {
  143. $matches = array();
  144. $any = false;
  145. foreach ($acceptedLanguages as $acceptedQuality => $acceptedValues) {
  146. $acceptedQuality = floatval($acceptedQuality);
  147. if ($acceptedQuality === 0.0) continue;
  148. foreach ($availableLanguages as $availableValue)
  149. {
  150. $availableQuality = 1.0;
  151. foreach ($acceptedValues as $acceptedValue)
  152. {
  153. if ($acceptedValue === '*')
  154. {
  155. $any = true;
  156. }
  157. $matchingGrade = self::_matchLanguage($acceptedValue, $availableValue);
  158. if ($matchingGrade > 0)
  159. {
  160. $q = (string) ($acceptedQuality * $availableQuality * $matchingGrade);
  161. if (!isset($matches[$q]))
  162. {
  163. $matches[$q] = array();
  164. }
  165. if (!in_array($availableValue, $matches[$q]))
  166. {
  167. $matches[$q][] = $availableValue;
  168. }
  169. }
  170. }
  171. }
  172. }
  173. if (count($matches) === 0 && $any)
  174. {
  175. if (count($availableLanguages) > 0)
  176. {
  177. $matches['1.0'] = $availableLanguages;
  178. }
  179. }
  180. if (count($matches) === 0)
  181. {
  182. return 'en';
  183. }
  184. krsort($matches);
  185. $topmatches = current($matches);
  186. return current($topmatches);
  187. }
  188. /**
  189. * compare two language IDs and return the degree they match
  190. *
  191. * From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
  192. *
  193. * @param string $a
  194. * @param string $b
  195. * @return float
  196. */
  197. protected static function _matchLanguage($a, $b) {
  198. $a = explode('-', $a);
  199. $b = explode('-', $b);
  200. for ($i=0, $n=min(count($a), count($b)); $i<$n; $i++)
  201. {
  202. if ($a[$i] !== $b[$i]) break;
  203. }
  204. return $i === 0 ? 0 : (float) $i / count($a);
  205. }
  206. }