i18n.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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))
  52. {
  53. return $messageId;
  54. }
  55. if (count(self::$_translations) === 0) {
  56. self::loadTranslations();
  57. }
  58. if (!array_key_exists($messageId, self::$_translations))
  59. {
  60. self::$_translations[$messageId] = $messageId;
  61. }
  62. $args = func_get_args();
  63. $args[0] = self::$_translations[$messageId];
  64. return call_user_func_array('sprintf', $args);
  65. }
  66. /**
  67. * loads translations
  68. *
  69. * From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
  70. *
  71. * @access protected
  72. * @static
  73. * @return void
  74. */
  75. public static function loadTranslations()
  76. {
  77. // find a matching translation file
  78. $availableLanguages = array();
  79. $path = PUBLIC_PATH . DIRECTORY_SEPARATOR . 'i18n';
  80. $i18n = dir($path);
  81. while (false !== ($file = $i18n->read()))
  82. {
  83. if (preg_match('/^([a-z]{2}).json$/', $file, $match) === 1)
  84. {
  85. $availableLanguages[] = $match[1];
  86. }
  87. }
  88. $match = self::_getMatchingLanguage(
  89. self::getBrowserLanguages(), $availableLanguages
  90. );
  91. // load translations
  92. if ($match != 'en')
  93. {
  94. self::$_translations = json_decode(
  95. file_get_contents($path . DIRECTORY_SEPARATOR . $match . '.json'),
  96. true
  97. );
  98. }
  99. }
  100. /**
  101. * detect the clients supported languages and return them ordered by preference
  102. *
  103. * From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
  104. *
  105. * @return array
  106. */
  107. public static function getBrowserLanguages()
  108. {
  109. $languages = array();
  110. if (array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER))
  111. {
  112. $languageRanges = explode(',', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']));
  113. foreach ($languageRanges as $languageRange) {
  114. if (preg_match(
  115. '/(\*|[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})))?/',
  116. trim($languageRange), $match
  117. ))
  118. {
  119. if (!isset($match[2]))
  120. {
  121. $match[2] = '1.0';
  122. }
  123. else
  124. {
  125. $match[2] = (string) floatval($match[2]);
  126. }
  127. if (!isset($languages[$match[2]]))
  128. {
  129. $languages[$match[2]] = array();
  130. }
  131. $languages[$match[2]][] = strtolower($match[1]);
  132. }
  133. }
  134. krsort($languages);
  135. }
  136. return $languages;
  137. }
  138. /**
  139. * compares two language preference arrays and returns the preferred match
  140. *
  141. * From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
  142. *
  143. * @param array $acceptedLanguages
  144. * @param array $availableLanguages
  145. * @return string
  146. */
  147. protected static function _getMatchingLanguage($acceptedLanguages, $availableLanguages) {
  148. $matches = array();
  149. $any = false;
  150. foreach ($acceptedLanguages as $acceptedQuality => $acceptedValues) {
  151. $acceptedQuality = floatval($acceptedQuality);
  152. if ($acceptedQuality === 0.0) continue;
  153. foreach ($availableLanguages as $availableValue)
  154. {
  155. $availableQuality = 1.0;
  156. foreach ($acceptedValues as $acceptedValue)
  157. {
  158. if ($acceptedValue === '*')
  159. {
  160. $any = true;
  161. }
  162. $matchingGrade = self::_matchLanguage($acceptedValue, $availableValue);
  163. if ($matchingGrade > 0)
  164. {
  165. $q = (string) ($acceptedQuality * $availableQuality * $matchingGrade);
  166. if (!isset($matches[$q]))
  167. {
  168. $matches[$q] = array();
  169. }
  170. if (!in_array($availableValue, $matches[$q]))
  171. {
  172. $matches[$q][] = $availableValue;
  173. }
  174. }
  175. }
  176. }
  177. }
  178. if (count($matches) === 0 && $any)
  179. {
  180. if (count($availableLanguages) > 0)
  181. {
  182. $matches['1.0'] = $availableLanguages;
  183. }
  184. }
  185. if (count($matches) === 0)
  186. {
  187. return 'en';
  188. }
  189. krsort($matches);
  190. $topmatches = current($matches);
  191. return current($topmatches);
  192. }
  193. /**
  194. * compare two language IDs and return the degree they match
  195. *
  196. * From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
  197. *
  198. * @param string $a
  199. * @param string $b
  200. * @return float
  201. */
  202. protected static function _matchLanguage($a, $b) {
  203. $a = explode('-', $a);
  204. $b = explode('-', $b);
  205. for ($i=0, $n=min(count($a), count($b)); $i<$n; $i++)
  206. {
  207. if ($a[$i] !== $b[$i]) break;
  208. }
  209. return $i === 0 ? 0 : (float) $i / count($a);
  210. }
  211. }