Lang.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: liu21st <liu21st@gmail.com>
  10. // +----------------------------------------------------------------------
  11. declare (strict_types = 1);
  12. namespace think;
  13. /**
  14. * 多语言管理类
  15. * @package think
  16. */
  17. class Lang
  18. {
  19. protected $app;
  20. /**
  21. * 配置参数
  22. * @var array
  23. */
  24. protected $config = [
  25. // 默认语言
  26. 'default_lang' => 'zh-cn',
  27. // 允许的语言列表
  28. 'allow_lang_list' => [],
  29. // 是否使用Cookie记录
  30. 'use_cookie' => true,
  31. // 扩展语言包
  32. 'extend_list' => [],
  33. // 多语言cookie变量
  34. 'cookie_var' => 'think_lang',
  35. // 多语言header变量
  36. 'header_var' => 'think-lang',
  37. // 多语言自动侦测变量名
  38. 'detect_var' => 'lang',
  39. // Accept-Language转义为对应语言包名称
  40. 'accept_language' => [
  41. 'zh-hans-cn' => 'zh-cn',
  42. ],
  43. // 是否支持语言分组
  44. 'allow_group' => false,
  45. ];
  46. /**
  47. * 多语言信息
  48. * @var array
  49. */
  50. private $lang = [];
  51. /**
  52. * 当前语言
  53. * @var string
  54. */
  55. private $range = 'zh-cn';
  56. /**
  57. * 构造方法
  58. * @access public
  59. * @param array $config
  60. */
  61. public function __construct(App $app, array $config = [])
  62. {
  63. $this->config = array_merge($this->config, array_change_key_case($config));
  64. $this->range = $this->config['default_lang'];
  65. $this->app = $app;
  66. }
  67. public static function __make(App $app, Config $config)
  68. {
  69. return new static($app, $config->get('lang'));
  70. }
  71. /**
  72. * 获取当前语言配置
  73. * @access public
  74. * @return array
  75. */
  76. public function getConfig(): array
  77. {
  78. return $this->config;
  79. }
  80. /**
  81. * 设置当前语言
  82. * @access public
  83. * @param string $lang 语言
  84. * @return void
  85. */
  86. public function setLangSet(string $lang): void
  87. {
  88. $this->range = $lang;
  89. }
  90. /**
  91. * 获取当前语言
  92. * @access public
  93. * @return string
  94. */
  95. public function getLangSet(): string
  96. {
  97. return $this->range;
  98. }
  99. /**
  100. * 获取默认语言
  101. * @access public
  102. * @return string
  103. */
  104. public function defaultLangSet()
  105. {
  106. return $this->config['default_lang'];
  107. }
  108. /**
  109. * 切换语言
  110. * @access public
  111. * @param string $langset 语言
  112. * @return void
  113. */
  114. public function switchLangSet(string $langset)
  115. {
  116. if (empty($langset)) {
  117. return;
  118. }
  119. // 加载系统语言包
  120. $this->load([
  121. $this->app->getThinkPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.php',
  122. ]);
  123. // 加载系统语言包
  124. $files = glob($this->app->getAppPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*');
  125. $this->load($files);
  126. // 加载扩展(自定义)语言包
  127. $list = $this->app->config->get('lang.extend_list', []);
  128. if (isset($list[$langset])) {
  129. $this->load($list[$langset]);
  130. }
  131. }
  132. /**
  133. * 加载语言定义(不区分大小写)
  134. * @access public
  135. * @param string|array $file 语言文件
  136. * @param string $range 语言作用域
  137. * @return array
  138. */
  139. public function load($file, $range = ''): array
  140. {
  141. $range = $range ?: $this->range;
  142. if (!isset($this->lang[$range])) {
  143. $this->lang[$range] = [];
  144. }
  145. $lang = [];
  146. foreach ((array) $file as $name) {
  147. if (is_file($name)) {
  148. $result = $this->parse($name);
  149. $lang = array_change_key_case($result) + $lang;
  150. }
  151. }
  152. if (!empty($lang)) {
  153. $this->lang[$range] = $lang + $this->lang[$range];
  154. }
  155. return $this->lang[$range];
  156. }
  157. /**
  158. * 解析语言文件
  159. * @access protected
  160. * @param string $file 语言文件名
  161. * @return array
  162. */
  163. protected function parse(string $file): array
  164. {
  165. $type = pathinfo($file, PATHINFO_EXTENSION);
  166. switch ($type) {
  167. case 'php':
  168. $result = include $file;
  169. break;
  170. case 'yml':
  171. case 'yaml':
  172. if (function_exists('yaml_parse_file')) {
  173. $result = yaml_parse_file($file);
  174. }
  175. break;
  176. case 'json':
  177. $data = file_get_contents($file);
  178. if (false !== $data) {
  179. $data = json_decode($data, true);
  180. if (json_last_error() === JSON_ERROR_NONE) {
  181. $result = $data;
  182. }
  183. }
  184. break;
  185. }
  186. return isset($result) && is_array($result) ? $result : [];
  187. }
  188. /**
  189. * 判断是否存在语言定义(不区分大小写)
  190. * @access public
  191. * @param string|null $name 语言变量
  192. * @param string $range 语言作用域
  193. * @return bool
  194. */
  195. public function has(string $name, string $range = ''): bool
  196. {
  197. $range = $range ?: $this->range;
  198. if ($this->config['allow_group'] && strpos($name, '.')) {
  199. [$name1, $name2] = explode('.', $name, 2);
  200. return isset($this->lang[$range][strtolower($name1)][$name2]);
  201. }
  202. return isset($this->lang[$range][strtolower($name)]);
  203. }
  204. /**
  205. * 获取语言定义(不区分大小写)
  206. * @access public
  207. * @param string|null $name 语言变量
  208. * @param array $vars 变量替换
  209. * @param string $range 语言作用域
  210. * @return mixed
  211. */
  212. public function get(string $name = null, array $vars = [], string $range = '')
  213. {
  214. $range = $range ?: $this->range;
  215. if (!isset($this->lang[$range])) {
  216. $this->switchLangSet($range);
  217. }
  218. // 空参数返回所有定义
  219. if (is_null($name)) {
  220. return $this->lang[$range] ?? [];
  221. }
  222. if ($this->config['allow_group'] && strpos($name, '.')) {
  223. [$name1, $name2] = explode('.', $name, 2);
  224. $value = $this->lang[$range][strtolower($name1)][$name2] ?? $name;
  225. } else {
  226. $value = $this->lang[$range][strtolower($name)] ?? $name;
  227. }
  228. // 变量解析
  229. if (!empty($vars) && is_array($vars)) {
  230. /**
  231. * Notes:
  232. * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0
  233. * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数
  234. */
  235. if (key($vars) === 0) {
  236. // 数字索引解析
  237. array_unshift($vars, $value);
  238. $value = call_user_func_array('sprintf', $vars);
  239. } else {
  240. // 关联索引解析
  241. $replace = array_keys($vars);
  242. foreach ($replace as &$v) {
  243. $v = "{:{$v}}";
  244. }
  245. $value = str_replace($replace, $vars, $value);
  246. }
  247. }
  248. return $value;
  249. }
  250. /**
  251. * 自动侦测设置获取语言选择
  252. * @deprecated
  253. * @access public
  254. * @param Request $request
  255. * @return string
  256. */
  257. public function detect(Request $request): string
  258. {
  259. // 自动侦测设置获取语言选择
  260. $langSet = '';
  261. if ($request->get($this->config['detect_var'])) {
  262. // url中设置了语言变量
  263. $langSet = strtolower($request->get($this->config['detect_var']));
  264. } elseif ($request->header($this->config['header_var'])) {
  265. // Header中设置了语言变量
  266. $langSet = strtolower($request->header($this->config['header_var']));
  267. } elseif ($request->cookie($this->config['cookie_var'])) {
  268. // Cookie中设置了语言变量
  269. $langSet = strtolower($request->cookie($this->config['cookie_var']));
  270. } elseif ($request->server('HTTP_ACCEPT_LANGUAGE')) {
  271. // 自动侦测浏览器语言
  272. $match = preg_match('/^([a-z\d\-]+)/i', $request->server('HTTP_ACCEPT_LANGUAGE'), $matches);
  273. if ($match) {
  274. $langSet = strtolower($matches[1]);
  275. if (isset($this->config['accept_language'][$langSet])) {
  276. $langSet = $this->config['accept_language'][$langSet];
  277. }
  278. }
  279. }
  280. if (empty($this->config['allow_lang_list']) || in_array($langSet, $this->config['allow_lang_list'])) {
  281. // 合法的语言
  282. $this->range = $langSet;
  283. }
  284. return $this->range;
  285. }
  286. /**
  287. * 保存当前语言到Cookie
  288. * @deprecated
  289. * @access public
  290. * @param Cookie $cookie Cookie对象
  291. * @return void
  292. */
  293. public function saveToCookie(Cookie $cookie)
  294. {
  295. if ($this->config['use_cookie']) {
  296. $cookie->set($this->config['cookie_var'], $this->range);
  297. }
  298. }
  299. }