语言包解决方案

前言

以前使用过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);
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,454评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,553评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,921评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,648评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,770评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,950评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,090评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,817评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,275评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,592评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,724评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,409评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,052评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,815评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,043评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,503评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,627评论 2 350

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,093评论 1 32
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 27,450评论 1 45
  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,146评论 0 3
  • 就在刚刚,我辞掉了一份兼职。 我缺钱吗,缺,很缺,工资水平一般,生活捉襟见肘。想要给孩子更好的教育机会...
    彩云清扬阅读 309评论 1 8
  • 亲子阅读打卡第287天(1月30日) 今天晚上给崽崽讲了一个《大兔子和小兔子》的故事。故事讲的是小兔子要上床睡...
    vv167阅读 311评论 0 0