tp 多语言实现的一种方式
思路:
1,将模板中需要翻译的内容使用特定标签包裹。在加载模板的时候对制定标签进行翻译操作
2, 建立翻译数据库,对要翻译的内容首先使用第三方api翻译并入库,对不合理的地方,进行人工更新。
code:
1,session中存储语言信息
/** * 语言信息初始化 * author liuxiaodong * date 2018/7/12 09:57 * @throws Exception */ protected function _initLauInfo() { //添加类映射 Think::addMap( [ 'translate\Translate' => dirname( THINK_PATH ) . DIRECTORY_SEPARATOR . 'Lib' . DIRECTORY_SEPARATOR . 'Extend' . DIRECTORY_SEPARATOR . 'Translate' . DIRECTORY_SEPARATOR . 'Translate' . EXT ] ); if( !($this->translate instanceof Translate ) ) $this->translate = new Translate(); $this->_initLauguage(); $this->_initLauArr(); //初始化语言数组信息 } /** * 设置语言信息 * @param bool $force 是否强制刷新语言 */ protected function _initLauguage( $language = Translate::ZH, $force = false ) { if( $force ) $this->setSessInfo( $this->lanSessName, null ); $tmpLau = $this->getSessInfo( $this->lanSessName ); if( !$tmpLau ){ $tmpLau = $language; $this->setSessInfo( $this->lanSessName, $tmpLau ); } $this->language = $tmpLau; }
/** * 设置语言数组信息 */ private function _initLauArr() { $this->lanShowArr = [ Translate::ZH => '中文', Translate::EN => 'Englist', Translate::KOR => '한국어', Translate::CHT => '繁体字', Translate::JP => '日本语' ]; }
2,改写模板的一些基础方法
/** * 重写display。加入模板语言过滤 */ protected function display($templateFile='',$charset='',$contentType='',$content='',$prefix='') { $this->assign('lanShowArr', $this->language, false ); $this->assign( 'lau', $this->lanShowArr, false ); $this->assign( 'title', ( $this->title ? $this->title . ' · ' : '' ) . '马卡龙·管理后台' ); //title设置 $this->assign( 'user', $this->getSessInfo( $this->userSessName ), false ); $this->assign( 'menu', $this->getSessInfo( $this->menuSessName ), true ); $this->assign( 'curl', strtolower( MODULE_NAME . '/' . CONTROLLER_NAME . '/' ) ); $this->assign( 'fullurl', strtolower( MODULE_NAME . '/' . CONTROLLER_NAME . '/' . ACTION_NAME ) ); define( 'THEME_PATH', '.' . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . MODULE_NAME . DIRECTORY_SEPARATOR ); // 视图开始标签 Hook::listen('view_begin',$templateFile); // 解析并获取模板内容 $content = $this->view->fetch($templateFile,$content,$prefix); // 输出模板内容 $this->_render($this->_translate( $content ),$charset,$contentType); // 视图结束标签 Hook::listen('view_end'); } /** * 解析需要翻译的中文内容 */ private function _translate( $content ) { $start_sign = C('START_CN');//定义标签信息 $end_sign = C('END_CN'); $pattern = "/{$start_sign}(.*?){$end_sign}/is"; $content = preg_replace_callback( $pattern, function( $matchs ) { if( $matchs && $matchs[1] ) { return $this->translate->run( $matchs[1], $this->language ); } }, $content ); return $content; } /** * 输出内容文本可以包括Html * @access private * @param string $content 输出内容 * @param string $charset 模板输出字符集 * @param string $contentType 输出类型 * @return mixed */ private function _render($content,$charset='',$contentType=''){ if(empty($charset)) $charset = C('DEFAULT_CHARSET'); if(empty($contentType)) $contentType = C('TMPL_CONTENT_TYPE'); // 网页字符编码 header('Content-Type:'.$contentType.'; charset='.$charset); header('Cache-control: '.C('HTTP_CACHE_CONTROL')); // 页面缓存控制 header('X-Powered-By:ThinkCMF'); // 输出模板文件 echo $content; } /** * 重写assign方法, * @param mixed $name * @param string $value * @param bool $translate 是否翻译,默认否 * @return \Think\Action|void */ protected function assign( $name, $value ='', $translate = false ) { if( $value && $translate ) { $this->_translateAssign( $value ); } parent::assign($name,$value); } /** * assign 翻译 * @param mixed $data * @throws * @return void */ private function _translateAssign( &$data ) { if( is_array( $data ) ) { array_walk_recursive( $data, function( &$val ) { if( is_string( $val ) && ( strlen( $val ) != mb_strlen( $val, 'utf8' ) ) ) { $val = $this->translate->run( $val, $this->language ); } } ); }else { if( strlen( $data ) != mb_strlen( $data ) ) $data = $this->translate->run( $data, $this->language ); } } /** * 重写ajaxReturn */ protected function ajaxReturn( $data, $type='', $translate = true) { if( $data && $translate ) $this->_translateAssign( $data ); parent::ajaxReturn( $data, $type ); }
<?php /** * Created by PhpStorm. * User: lxd * Date: 2018/7/3 * Time: 16:15 */ /** * 多语言表 create table mkl_translate ( id int unsigned auto_increment, zh varchar(100) not null default '' comment '中文', en varchar(1000) not null default '' comment '英文', jp varchar(1000) not null default '' comment '日文', kor varchar(1000) not null default '' comment '韩文', cht varchar(1000) not null default '' comment '繁体中文', zh_md5 varchar(32) not null default '' comment '中文md5', primary key (`id`), unique key (`zh_md5`) ) engine innodb charset utf8 comment '多语言对应表'; */ namespace translate; use Think\Exception; use Think\Log; use Think\Model; use Think\Think; class Translate { protected $db = null; protected $redis = null; protected $sdk = null; //来自百度api const ZH = 'zh'; //中文 const EN = 'en'; //英文 const JP = 'jp'; //日文 const KOR = 'kor'; //韩语 const CHT = 'cht'; //繁体中文 //对应数据库字段信息 public static function getAllType( $isTrans = false ) { //是否partner应用请求并且要求翻译 if( $isTrans ) { return [ self::ZH => '<{中文}>', self::EN => '<{英文}>', self::JP => '<{日文}>', self::KOR => '<{韩语}>', self::CHT => '<{繁体中文}>', ]; } return [ self::ZH => '中文', self::EN => '英文', self::JP => '日文', self::KOR => '韩语', self::CHT => '繁体中文', ]; } public function __construct() { $this->db = new Model( 'translate' ); if( !( $this->db instanceof Model) ) throw new Exception( '初始化数据库失败~' ); } /** * 转换语言 * 1,查缓存,redis,hash * 2,查数据库,有,入缓存 * 3,查接口,入库,入缓存 * @param string $str 要转换的语言 * @param string $type 要转换成的语言类型 * @return mixed/exception */ public function run( $str, $type = self::EN, $from = self::ZH ) { if( $type == self::ZH || empty( $str ) || !in_array( $type, array_keys( self::getAllType() ) ) ) return $str; if( $from != self::ZH ) throw new Exception( '很抱歉,当前仅支持中文转换为其他文字' ); return preg_replace_callback( '/([\x{4e00}-\x{9fa5}]+)/u', function( $match ) use ( $type, $from ) { return $this->selectDb( $match[1], $type, $from ); }, $str ); } /** * 查库 * @param string $force 是否强制读库 */ protected function selectDb( $str, $to, $from, $force = false ) { if( $from != self::ZH ) throw new Exception( '很抱歉,当前仅支持中文转换为其他文字' ); $str_md5 = md5( $str ); $res = ''; //redis 暂放 $redisRes = false; if( !$redisRes || $force ) { $info = $this->db->field( $to )->where( ['zh_md5' => $str_md5] )->find(); if( $info && isset( $info[$to] ) && $info[$to] ) { //更新redis return $info[$to]; } import( 'Lib.Extend.Translate.sdk.Baidu', dirname( THINK_PATH ) ); $this->sdk = new Baidu(); $str = addslashes( $str ); $res = $this->sdk->run( $str, $to, $from ); $res = addslashes( $res ); if( $res ) { $sql = "INSERT INTO {$this->db->getTableName()} (`{$from}`,`{$to}`,`zh_md5`) VALUES ( '{$str}', '{$res}', '{$str_md5}' ) ON DUPLICATE KEY UPDATE `{$to}` = '{$res}'"; $dbres = $this->db->execute( $sql ); if( !$dbres ) Log::record( '翻译结果入库失败,sql= ' . var_export( $sql, true ) ); //redis 更新 } } return $res; } }
{__NOLAYOUT__} <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta charset="utf-8" /> <title><{登陆页面}></title> <meta name="description" content="User login page" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" /> <!-- bootstrap & fontawesome --> <link rel="stylesheet" href="__CSS__/bootstrap.min.css" /> <link rel="stylesheet" href="__FONT_AWESOME__/4.5.0/css/font-awesome.min.css" /> <link rel="stylesheet" href="__CSS__/anti-bot-style.css" /> <!-- text fonts --> <!--<link rel="stylesheet" href="__CSS__/fonts.googleapis.com.css" />--> <!-- ace styles --> <link rel="stylesheet" href="__CSS__/ace.min.css" /> <!--[if lte IE 9]> <link rel="stylesheet" href="__CSS__/ace-part2.min.css" /> <![endif]--> <link rel="stylesheet" href="__CSS__/ace-rtl.min.css" /> <!--[if lte IE 9]> <link rel="stylesheet" href="__CSS__/ace-ie.min.css" /> <![endif]--> <!-- HTML5shiv and Respond.js for IE8 to support HTML5 elements and media queries --> <style> .help-block {color:red;font-size: 12px;} </style> <!--[if lte IE 8]> <script src="__JS__/html5shiv.min.js"></script> <script src="__JS__/respond.min.js"></script> <![endif]--> </head> <body class="login-layout light-login"> <div class="main-container"> <div class="main-content"> <div class="row"> <div class="col-sm-10 col-sm-offset-1"> <div class="login-container"> <div class="center" style="margin-bottom: 30px;"> <h1> <i class="ace-icon fa fa-leaf green"></i> <span class="red"><{??}></span> <span class="grey" id="id-text2" style="font-size: 16px;"><{管理后台}></span> <select class="setLau" style="height:19px;font-size:12px;text-align: center;"> <foreach name="lau" item="vo"> <option value="{$key}" <if condition="$lanShowArr eq $key">selected</if>>{$vo}</option> </foreach> </select> </h1> </div> <div class="space-6"></div> <div class="position-relative"> <div id="login-box" class="login-box visible widget-box no-border"> <div class="widget-body"> <div class="widget-main"> <h4 class="header blue lighter bigger"> <i class="ace-icon fa fa-coffee green"></i> <{请输入以下信息}>: </h4> <div class="space-6"></div> <form id="validation-form" method="post" action="{:U('login')}"> <fieldset> <label class="block clearfix"> <span class="block input-icon input-icon-right"> <input type="text" name="username" id="username" class="form-control" placeholder="<{用户名}>" /> <i class="ace-icon fa fa-user"></i> </span> </label> <label class="block clearfix"> <span class="block input-icon input-icon-right"> <input type="password" name="password" id="password" class="form-control" placeholder="<{密码}>" /> <i class="ace-icon fa fa-lock"></i> </span> </label> <label class=" clearfix verify" style="display: none;"> <div id="anti_bot_frame" class="anti-bot-frame-large"></div> <input type="hidden" name="verify" id="anti_bot_frame_hidden" value="" /> </label> <div class="space"></div> <div class="clearfix"> <input type="submit" class="width-35 pull-right btn btn-sm btn-primary" value="<{登陆}>"/> </div> <div class="space-4"></div> </fieldset> </form> <div class="space-6"></div> </div><!-- /.widget-main --> </div><!-- /.widget-body --> </div><!-- /.login-box --> </div><!-- /.position-relative --> </div> </div><!-- /.col --> </div><!-- /.row --> </div><!-- /.main-content --> </div><!-- /.main-container --> <!-- basic scripts --> <!--[if !IE]> --> <script src="__JS__/jquery-2.1.4.min.js"></script> <!-- <![endif]--> <!--[if IE]> <script src="__JS__/jquery-1.11.3.min.js"></script> <![endif]--> <script src="__JS__/jquery.validate.min.js"></script> <script type="text/javascript"> anti_bot_verification_path = '{:U('Partner/public/getImage')}'; anti_bot_verification_init = '{:U('Partner/public/initVerify')}'; anti_bot_verification_verify = '{:U('Partner/public/verify')}'; if('ontouchstart' in document.documentElement) document.write("<script src='__JS__/jquery.mobile.custom.min.js'>"+"<"+"/script>"); $('.setLau').on('change',function() { var _this = $(this); $.get( '{:U('Public/setLau')}', {'lau':_this.val()}, function( res ) { if(res.msg == 'ok') window.location.reload(); }, 'json'); }); $('#validation-form').validate({ ignore: [], errorElement: 'div', errorClass: 'help-block', rules:{ username: "required", password: { required: true, minlength: 6 }, verify : "required" }, messages:{ username: "<{用户名不能为空}>", password: { required: "<{密码不能为空}>", minlength: "<{密码最少6个字符}>" }, verify : "<{请拖动图标完成验证}>" } }); $('input[name="password"]').on( 'keyup', function() { var _val = $(this).val(); if( _val.length >= 6 ) { $('.verify').show(); } }) </script> <script src="__JS__/verify.js"></script> </body> </html>
3, 建立后台翻译列表
4,问题
1,翻译每次都是单次请求,没有做批量请求
2,要读库或者redis,不如直接在文本中写的速度快
感觉优点就是开发快,而且后期对语言 的维护性好些,直接后台改就行
效果:
后台: