Lang.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <?php
  2. namespace ForkBB\Core;
  3. use ForkBB\Core\Container;
  4. use RuntimeException;
  5. class Lang
  6. {
  7. /**
  8. * Контейнер
  9. * @var Container
  10. */
  11. protected $c;
  12. /**
  13. * Массив переводов
  14. * @var array
  15. */
  16. protected $tr = [];
  17. /**
  18. * Загруженные переводы
  19. * @var array
  20. */
  21. protected $loaded = [];
  22. /**
  23. * Конструктор
  24. *
  25. * @param Container $container
  26. */
  27. public function __construct(Container $container)
  28. {
  29. $this->c = $container;
  30. }
  31. /**
  32. * Ищет сообщение в загруженных переводах
  33. *
  34. * @param string $message
  35. * @param string $lang
  36. *
  37. * @return string|array
  38. */
  39. public function get($message, $lang = null)
  40. {
  41. if ($lang && isset($this->tr[$lang][$message])) {
  42. return $this->tr[$lang][$message];
  43. }
  44. foreach ($this->tr as $lang) {
  45. if (isset($lang[$message])) {
  46. return $lang[$message];
  47. }
  48. }
  49. return $message;
  50. }
  51. /**
  52. * Загрузка языкового файла
  53. *
  54. * @param string $name
  55. * @param string $lang
  56. * @param string $path
  57. */
  58. public function load($name, $lang = null, $path = null)
  59. {
  60. if ($lang) {
  61. if (isset($this->loaded[$name][$lang])) {
  62. return;
  63. }
  64. } elseif (isset($this->loaded[$name])) {
  65. return;
  66. }
  67. $lang = $lang ?: $this->c->user->language;
  68. $path = $path ?: $this->c->DIR_LANG;
  69. do {
  70. $flag = true;
  71. $fullPath = $path . '/'. $lang . '/' . $name . '.po';
  72. if (\is_file($fullPath)) {
  73. $file = \file_get_contents($fullPath);
  74. if (isset($this->tr[$lang])) {
  75. $this->tr[$lang] += $this->arrayFromStr($file);
  76. } else {
  77. $this->tr[$lang] = $this->arrayFromStr($file);
  78. }
  79. $flag = false;
  80. } elseif ($lang === 'en') {
  81. $flag = false;
  82. }
  83. $lang = 'en';
  84. } while ($flag);
  85. $this->loaded[$name][$lang] = true;
  86. }
  87. /**
  88. * Получение массива перевода из строки (.po файла)
  89. *
  90. * @param string $str
  91. *
  92. * @throws RuntimeException
  93. *
  94. * @return array
  95. */
  96. protected function arrayFromStr($str)
  97. {
  98. $lines = \explode("\n", $str);
  99. $count = \count($lines);
  100. $result = [];
  101. $cur = [];
  102. $curComm = null;
  103. $curVal = '';
  104. $nplurals = 2;
  105. $plural = '($n != 1);';
  106. for ($i = 0; $i < $count; ++$i) {
  107. $line = \trim($lines[$i]);
  108. // пустая строка
  109. if (! isset($line[0])) {
  110. // промежуточные данные
  111. if (isset($curComm)) {
  112. $cur[$curComm] = $curVal;
  113. }
  114. // ошибка формата
  115. if (! isset($cur['msgid'])) {
  116. throw new RuntimeException('File format error');
  117. }
  118. // заголовки
  119. if (! isset($cur['msgid'][0])) {
  120. if (\preg_match('%Plural\-Forms:\s+nplurals=(\d+);\s*plural=([^;\n\r]+;)%i', $cur[0], $v)) {
  121. $nplurals = (int) $v[1];
  122. $plural = \str_replace('n', '$n', \trim($v[2]));
  123. $plural = \str_replace(':', ': (', $plural, $curVal);
  124. $plural = \str_replace(';', \str_repeat(')', $curVal). ';', $plural);
  125. }
  126. // перевод
  127. } else {
  128. // множественный
  129. if (isset($cur['msgid_plural'][0]) || isset($cur[1][0])) {
  130. if (! isset($cur[1][0])) {
  131. $cur[1] = $cur['msgid_plural'];
  132. }
  133. if (! isset($cur[0][0])) {
  134. $cur[0] = $cur['msgid'];
  135. }
  136. $curVal = [];
  137. for ($v = 0; $v < $nplurals; ++$v) {
  138. if (! isset($cur[$v][0])) {
  139. $curVal = null;
  140. break;
  141. }
  142. $curVal[$v] = $cur[$v];
  143. }
  144. if (isset($curVal)) {
  145. $curVal['plural'] = $plural;
  146. $result[$cur['msgid']] = $curVal;
  147. }
  148. // одиночный
  149. } elseif (isset($cur[0])) { // [0]
  150. $result[$cur['msgid']] = $cur[0];
  151. }
  152. }
  153. $curComm = null;
  154. $curVal = '';
  155. $cur = [];
  156. continue;
  157. // комментарий
  158. } elseif ($line[0] == '#') {
  159. continue;
  160. // многострочное содержимое
  161. } elseif ($line[0] == '"') {
  162. if (isset($curComm)) {
  163. $curVal .= $this->originalLine($line);
  164. }
  165. continue;
  166. // промежуточные данные
  167. } elseif (isset($curComm)) {
  168. $cur[$curComm] = $curVal;
  169. }
  170. // выделение команды
  171. $v = \explode(' ', $line, 2);
  172. $command = $v[0];
  173. $v = isset($v[1]) ? $this->originalLine(\trim($v[1])) : '';
  174. switch ($command) {
  175. case 'msgctxt':
  176. case 'msgid':
  177. case 'msgid_plural':
  178. $curComm = $command;
  179. $curVal = $v;
  180. break;
  181. case 'msgstr':
  182. case 'msgstr[0]':
  183. $curComm = 0;
  184. $curVal = $v;
  185. break;
  186. case 'msgstr[1]':
  187. $curComm = 1;
  188. $curVal = $v;
  189. break;
  190. case 'msgstr[2]':
  191. $curComm = 2;
  192. $curVal = $v;
  193. break;
  194. case 'msgstr[3]':
  195. $curComm = 3;
  196. $curVal = $v;
  197. break;
  198. case 'msgstr[4]':
  199. $curComm = 4;
  200. $curVal = $v;
  201. break;
  202. case 'msgstr[5]':
  203. $curComm = 5;
  204. $curVal = $v;
  205. break;
  206. default:
  207. throw new RuntimeException('File format error');
  208. }
  209. }
  210. return $result;
  211. }
  212. /**
  213. * Получение оригинальной строки с удалением кавычек
  214. * и преобразованием спецсимволов
  215. *
  216. * @param string $line
  217. *
  218. * @return string
  219. */
  220. protected function originalLine($line)
  221. {
  222. if (isset($line[1]) && $line[0] == '"' && $line[\strlen($line) - 1] == '"') {
  223. $line = \substr($line, 1, -1);
  224. }
  225. return \str_replace(
  226. ['\\n', '\\t', '\\"', '\\\\'],
  227. ["\n", "\t", '"', '\\'],
  228. $line
  229. );
  230. }
  231. }