前言
以前使用过ecshop,语言包,需要先去定义映射,然后才能使用,这样开发起来非常繁琐,现在的项目需要用到语言包用到的一个方案,记录一下。
大致思路
1.用中文作为键,翻译的语言包作为值。定义语言包文件
2.写一个递归函数来翻译变量。
3.php 中在向前端分配变量的时候,统一调用这个函数,这样保证php所有渲染过的数据,都是被翻译过的。
4.html中同样用这个递归函数去翻译中文
注意的地方。
在我们开发项目的时候,中文全部使用单引号,包括js,php。 这样,我们可以用扫描脚本的方式,将中文导出,不需要人工去查找。
PHP中,代码如下
<?php /**
* 语言包对象,单例
* Created by PhpStorm.
* User: cy
* Date: 18-8-3
* Time: 下午12:45
*/
class Lang
{
const LANG_COOKIE_NAME = 'lang_cookie_name';//语言包名称
const REPLACE_STRING = 'XX';//需要替换的文字
public static $langData;//语言包数据
public $outData = [];//存放导出中文的数据php文件中的数据
public $outHtml = [];//存放导出中文的数据html文件中的数据
private $local;//语言环境
private $fileHtml;//为html页面提供的语言包文件
private static $instance;//自身实例
private function __construct($local = '')
{
//初始化语言
$this->initLocal($local);
}
/**
* 直接访问属性
* @param $name
* @return mixed
*/
public function __get($name)
{
return $this->$name;
}
/**
* 获取实例
*
* @return object
*/
public static function getInstance()
{
if (!self::$instance instanceof self) {
self::$instance = new self;
}
return self::$instance;
}
/**
* 为html页面提供语言支持
*/
public function langHtml($key, $replace = null)
{
$str = val(self::$langData, trim($key), $key);
//转数组
$replaceArr=is_array($replace)?$replace:[$replace];
if(!is_null($replace)){
foreach($replaceArr as $v){
$str = preg_replace('/'.self::REPLACE_STRING.'/', $v, $str,1);
}
}
return $str;
}
/**
* 递归处理数组翻译
* @param $arr
* @param int $mode 0=翻译key和value,1=翻译value,2=翻译key
*/
public function langArray($arr,$mode=0)
{
if(is_array($arr)){
foreach ($arr as $k=>$v){
if($mode==1){
//只翻译value
$arr[$k]=$this->langArray($v,$mode);
}else{
//翻译key
$kLang=langHtml($k);
$arr[$kLang]=$this->langArray($v,$mode);
if($kLang!=$k) unset($arr[$k]);
}
}
}else{
if(in_array($mode,[0,1])){
//需要翻译value
$arr=$this->langHtml($arr,$mode);
}
}
return $arr;
}
/**
* 将csv文件导成前端语言包文件,生成in-en-html.php
*/
public function makeLangHtmlFileFromCsv()
{
self::$langData=[];
$arrFile = ['operate_manage.csv', 'app_api.csv','caiwu.csv'];//html csv源文件
foreach ($arrFile as $v) {
$file = fopen(dirname(__FILE__) . '/in-en/' . $v, "r");
while (!feof($file)) {
$tmp = fgetcsv($file);
if (empty($tmp[0]) || empty($tmp[1])) continue;
//文件理没有出现过,且没有被翻译过
self::$langData[$tmp[0]] = $tmp[1];
}
fclose($file);
}
$content = '<?php ' . PHP_EOL . '//前端页面语言包 印尼文' . PHP_EOL . 'return ';
$content .= var_export(self::$langData, true);
$content .= ';';
file_put_contents(dirname(__FILE__) . '/in-en/in-en-html.php', $content);
}
/**
* 设置当前语言包环境
*/
public function setLangToCookie($lang)
{
GetOB('Util.Cookie')->addCookie(self::LANG_COOKIE_NAME, $lang);
}
/**
* 获取当前的语言环境
* @return string
* @throws Exception
*/
public function getLangFromCookie()
{
return GetOB('Util.Cookie')->getCookie(self::LANG_COOKIE_NAME);
}
//防止克隆
private function __clone()
{
}
/**
* 扫描目录,导出需要翻译的中文
* @param bool $diff
*
*/
public function scanDir($diff = true)
{
$projectDir = $this->getProjectRoot();//项目目录
$scanDir = ['/api/OB', '/account_manage/App','/operate_manage/App'];//需要文件目录'operate_manage/App'
foreach ($scanDir as $v) {
$dir = $projectDir . $v;
$this->scanFile($dir);
}
if ($diff) {
//去掉翻译过的
$inEnData = include dirname(__FILE__) . '/in-en/in-en-html.php';
$keyInEnData = array_keys($inEnData);
foreach ($this->outData as $k => $v) {
if (in_array($v, $keyInEnData)) {
unset($this->outData[$k]);
}
}
foreach ($this->outHtml as $k => $v) {
if (in_array($v, $keyInEnData)) {
unset($this->outHtml[$k]);
}
}
}
$data=[$this->outData,$this->outHtml];
foreach ($data as $k=>$v){
$v= array_unique($v);
switch ($k){
case 0: $filename='zh-cn-p.csv';break;
case 1: $filename='zh-cn-h.csv';break;
}
$fileDir=dirname(__FILE__)."/tmp";
if(!is_dir($fileDir))mkdir($fileDir);
$zhcnFile = fopen($fileDir. "/$filename", "w");
//组装导出数据
foreach ($v as $key => $value) {
fputcsv($zhcnFile, [$value], ',');
}
fclose($zhcnFile);
}
die;
}
/**
* 递归扫描文件
* @param $dir
* @return bool
*/
private function scanFile($dir)
{
$projectRoot = $this->getProjectRoot();
$skipDir = [$projectRoot . '/api/OB/Lang'];//跳过的文件目录
$skipFile = [
$projectRoot . '/api/OB/Util/MathUtil.class.php',
$projectRoot . '/api/OB/Util/DateUtil.class.php',
$projectRoot . '/api/OB/Util/Sms.class.php',
$projectRoot . '/api/OB/Util/SendMail.class.php',
$projectRoot . '/api/OB/Api/BluePay.class.php',
$projectRoot . '/api/OB/Api/Auth.class.php',
$projectRoot . '/api/OB/Api/ApiAction.class.php',
$projectRoot . '/operate_manage/App/View/useradmin/show.html',
$projectRoot . '/operate_manage/App/View/index/modal.html',
];//跳过的文件
if (!is_dir($dir) || in_array($dir, $skipDir)) return false;
$handle = opendir($dir);
if ($handle) {
while (($fl = readdir($handle)) !== false) {
$temp = $dir . DIRECTORY_SEPARATOR . $fl;
//如果不加 $fl!='.' && $fl != '..' 则会造成把$dir的父级目录也读取出来
if (is_dir($temp) && $fl != '.' && $fl != '..') {
$this->scanFile($temp);
} else {
if ($fl != '.' && $fl != '..' && !in_array($temp, $skipFile)) {
if (strpos($temp, '.php')) {
$content = file_get_contents($temp);
if (preg_match_all("/['\"]([\x{4e00}-\x{9fa5}!!,,a-zA-Z_]+[\x{4e00}-\x{9fa5}]+[\x{4e00}-\x{9fa5}!!,,a-zA-Z_0-9]*)['\"]/u", $content, $outArr)) {
$this->outData = array_merge($this->outData, $outArr[1]);
}
} elseif (strpos($temp, '.html')) {
$content = file_get_contents($temp);
if (preg_match_all("/([\x{4e00}-\x{9fa5}!!,,a-zA-Z_\/\/\\%,\[\],\(\)a-zA-Z_0-9\&\;\s:]*)/u", $content, $outArr)) {
foreach ($outArr[1] as $k => $v) {
//去掉注释
if (preg_match('/[\/\/\<!--\s]+/', $v)) {
unset($outArr[1][$k]);
}
}
$this->outHtml = array_merge($this->outHtml, $outArr[1]);
}
}
}
}
}
}
}
/**
* 设置语言
*/
private function initLocal($local = null)
{
//读取配置文件
$this->local = defined('LOCAL_LANG') ? LOCAL_LANG : LOCAL_LANG_DEFAULT;
//传值优先
if ($local) {
$this->local = $local;
} elseif ($this->getLangFromCookie()) {
//cookie设置值
$this->local = $this->getLangFromCookie();
} else {
//根据前端邀请返回相应语言
$controller = SystemParams::get('OB');
if (!empty($controller->input['language']) && in_array($controller->input['language'], [LOCAL_LANG_CHINA, LOCAL_LANG_INDONESIA])) {
$this->local = $controller->input['language'];
}
}
//获取语言包数据
$this->initData();
}
/**
* 初始化语言包数据
*/
private function initData()
{
$this->fileHtml = dirname(__FILE__) . '/' . $this->local . '/' . $this->local . '-html.php';
if (!file_exists($this->fileHtml)) {
self::$langData = [];
} else {
self::$langData = require_once $this->fileHtml;
}
}
/**
* 获取项目根目录,由于入口文件位置没有定义,所以在这里定义一个函数取一下
*/
private function getProjectRoot()
{
return str_replace('/api/OB/Lang','',__DIR__);
}
}
js中,我们从后台接口获取翻译数据源
用ajax,进行异步加载,然后翻译
//待翻译的对象
var langTrans={
transBtns:['确定','取消'],
transTitle:'提示',
transMsgConfirm:'信息确认',
transConfirmCancle:'确定要取消?',
transChoiceData:'请选择数据',
transQueryError:'请求出错!',
transReceConfirm:'收款确认',
transIsDelMember:'是否删除当前成员',
transChoiceOne:'至少选择一项',
};
//翻译对象
function language(needTrans) {
var language=$(this);
//存放语言包数据
language.langData={};
//请求后台语言包数据
language.getLangData=function () {
$.ajax({
type: "GET",
url: "/index/langJson",
dataType: "json",
success: function(data){
language.langData=data.data;
needTrans=language.langHtml(needTrans);
}
});
};
//递归翻译函数
language.langHtml=function(key){
if(typeof(key)=="string"){
if (key in language.langData) {
key=eval('language.langData.'+key);
}
}else{
for (var value in key){
key[value]=language.langHtml(key[value]);
}
}
return key;
};
//构造数据
language.getLangData();
return language;
}
language(langTrans);