unity多語言本地化


簡介

嗯...一般來說做游戲啥的都不會只發一個國家,但是每個國家語言不同,就存在多語言本地化的問題,然后直接用過一個通過xml完成本地化的東東,然后策划反饋不會修改xml,扔給我一個excel讓我自己把字段填進去,然后我就自己寫個csv的本地化工具....
PS:至於為啥用csv不用xls或者xlsx,因為!csv格式簡單啊,自己就可以寫解析器,excel的解析去又需要一大堆第三方庫啥的

用法

先在Assets/Resources文件夾下創建一個LTLocalization文件夾,然后在里面創建一個Localization.csv文件
這里寫圖片描述
然后通過wps或者excel編輯
像這樣:
這里寫圖片描述
然后將csv文件轉換成UTF-8編碼格式

// 直接使用,會自動判斷當前系統語言
LTLocalization.GetText(Key);
// 手動指定語言使用
LTLocalization.ManualSetLanguage(SystemLanguage.language);
LTLocalization.GetText(Key);

這個用法還算簡單吧...

原理

1.通過csv解析器將CSV文件整體讀到內存中
2.轉換為相應的table表格
3.根據需要的語言將對應的數據提取出來形成一個dictionary
4.通過dictionary需要需要的本地化語言

代碼

CSV解析器

using System.Collections.Generic;
using System.IO;
using System.Text;

// col是豎行,row是橫排,防止我忘了
public class LTCSVLoader
{

    private TextReader inStream = null;

    private List<string> vContent;

    private List<List<string>> table;

    /// <summary>
    /// 只支持GBK2312的編碼(WPS直接保存的編碼支持,暫時不可用)
    /// </summary>
    /// <param name="fileName"></param>
    private void ReadFile(string fileName)
    {
        inStream = new StreamReader(fileName, Encoding.GetEncoding("GBK"));
        table = new List<List<string>>();
        List<string> temp = this.getLineContentVector();
        while (null != temp)
        {
            List<string> tempList = new List<string>();
            for (int i = 0; i < temp.Count; ++i)
            {
                tempList.Add(temp[i]);
            }
            table.Add(tempList);
            temp = this.getLineContentVector();
        }
    }

    /// <summary>
    /// 目前只支持UTF-8的編碼(WPS直接保存的編碼不支持)
    /// </summary>
    /// <param name="str"></param>
    public void ReadMultiLine(string str)
    {
        inStream = new StringReader(str);

        table = new List<List<string>>();
        List<string> temp = this.getLineContentVector();
        while (null != temp)
        {
            List<string> tempList = new List<string>();
            for (int i = 0; i < temp.Count; ++i)
            {
                tempList.Add(temp[i]);
            }
            table.Add(tempList);
            temp = this.getLineContentVector();
        }
    }

    private int containsNumber(string parentStr, string parameter)
    {
        int containNumber = 0;
        if (parentStr == null || parentStr.Equals(""))
        {
            return 0;
        }
        if (parameter == null || parameter.Equals(""))
        {
            return 0;
        }
        for (int i = 0; i < parentStr.Length; i++)
        {
            i = parentStr.IndexOf(parameter, i);
            if (i > -1)
            {
                i = i + parameter.Length;
                i--;
                containNumber = containNumber + 1;
            }
            else
            {
                break;
            }
        }
        return containNumber;
    }

    private bool isQuoteAdjacent(string p_String)
    {
        bool ret = false;
        string temp = p_String;
        temp = temp.Replace("\"\"", "");
        if (temp.IndexOf("\"") == -1)
        {
            ret = true;
        }
        return ret;
    }

    private bool isQuoteContained(string p_String)
    {
        bool ret = false;
        if (p_String == null || p_String.Equals(""))
        {
            return false;
        }
        if (p_String.IndexOf("\"") > -1)
        {
            ret = true;
        }
        return ret;
    }

    private string[] readAtomString(string lineStr)
    {
        string atomString = "";// 要讀取的原子字符串
        string orgString = "";// 保存第一次讀取下一個逗號時的未經任何處理的字符串
        string[] ret = new string[2];// 要返回到外面的數組
        bool isAtom = false;// 是否是原子字符串的標志
        string[] commaStr = lineStr.Split(new char[] { ',' });
        while (!isAtom)
        {
            foreach (string str in commaStr)
            {
                if (!atomString.Equals(""))
                {
                    atomString = atomString + ",";
                }
                atomString = atomString + str;
                orgString = atomString;
                if (!isQuoteContained(atomString))
                {
                    // 如果字符串中不包含引號,則為正常,返回
                    isAtom = true;
                    break;
                }
                else
                {
                    if (!atomString.StartsWith("\""))
                    {
                        // 如果字符串不是以引號開始,則表示不轉義,返回
                        isAtom = true;
                        break;
                    }
                    else if (atomString.StartsWith("\""))
                    {
                        // 如果字符串以引號開始,則表示轉義
                        if (containsNumber(atomString, "\"") % 2 == 0)
                        {
                            // 如果含有偶數個引號
                            string temp = atomString;
                            if (temp.EndsWith("\""))
                            {
                                temp = temp.Replace("\"\"", "");
                                if (temp.Equals(""))
                                {
                                    // 如果temp為空
                                    atomString = "";
                                    isAtom = true;
                                    break;
                                }
                                else
                                {
                                    // 如果temp不為空,則去掉前后引號
                                    temp = temp.Substring(1, temp.LastIndexOf("\""));
                                    if (temp.IndexOf("\"") > -1)
                                    {
                                        // 去掉前后引號和相鄰引號之后,若temp還包含有引號
                                        // 說明這些引號是單個單個出現的
                                        temp = atomString;
                                        temp = temp.Substring(1);
                                        temp = temp.Substring(0, temp.IndexOf("\""))
                                                + temp.Substring(temp.IndexOf("\"") + 1);
                                        atomString = temp;
                                        isAtom = true;
                                        break;
                                    }
                                    else
                                    {
                                        // 正常的csv文件
                                        temp = atomString;
                                        temp = temp.Substring(1, temp.LastIndexOf("\""));
                                        temp = temp.Replace("\"\"", "\"");
                                        atomString = temp;
                                        isAtom = true;
                                        break;
                                    }
                                }
                            }
                            else
                            {
                                // 如果不是以引號結束,則去掉前兩個引號
                                temp = temp.Substring(1, temp.IndexOf('\"', 1))
                                        + temp.Substring(temp.IndexOf('\"', 1) + 1);
                                atomString = temp;
                                isAtom = true;
                                break;
                            }
                        }
                        else
                        {
                            // 如果含有奇數個引號
                            if (!atomString.Equals("\""))
                            {
                                string tempAtomStr = atomString.Substring(1);
                                if (!isQuoteAdjacent(tempAtomStr))
                                {
                                    // 這里做的原因是,如果判斷前面的字符串不是原子字符串的時候就讀取第一個取到的字符串
                                    // 后面取到的字符串不計入該原子字符串
                                    tempAtomStr = atomString.Substring(1);
                                    int tempQutoIndex = tempAtomStr.IndexOf("\"");
                                    // 這里既然有奇數個quto,所以第二個quto肯定不是最后一個
                                    tempAtomStr = tempAtomStr.Substring(0, tempQutoIndex)
                                            + tempAtomStr.Substring(tempQutoIndex + 1);
                                    atomString = tempAtomStr;
                                    isAtom = true;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
        // 先去掉之前讀取的原字符串的母字符串
        if (lineStr.Length > orgString.Length)
        {
            lineStr = lineStr.Substring(orgString.Length);
        }
        else
        {
            lineStr = "";
        }
        // 去掉之后,判斷是否以逗號開始,如果以逗號開始則去掉逗號
        if (lineStr.StartsWith(","))
        {
            if (lineStr.Length > 1)
            {
                lineStr = lineStr.Substring(1);
            }
            else
            {
                lineStr = "";
            }
        }
        ret[0] = atomString;
        ret[1] = lineStr;
        return ret;
    }

    private bool readCSVNextRecord()
    {
        // 如果流未被初始化則返回false
        if (inStream == null)
        {
            return false;
        }
        // 如果結果向量未被初始化,則初始化
        if (vContent == null)
        {
            vContent = new List<string>();
        }
        // 移除向量中以前的元素
        vContent.Clear();
        // 聲明邏輯行
        string logicLineStr = "";
        // 用於存放讀到的行
        StringBuilder strb = new StringBuilder();
        // 聲明是否為邏輯行的標志,初始化為false
        bool isLogicLine = false;
        while (!isLogicLine)
        {
            string newLineStr = inStream.ReadLine();
            if (newLineStr == null)
            {
                strb = null;
                vContent = null;
                isLogicLine = true;
                break;
            }
            if (newLineStr.StartsWith("#"))
            {
                // 去掉注釋
                continue;
            }
            if (!strb.ToString().Equals(""))
            {
                strb.Append("\r\n");
            }
            strb.Append(newLineStr);
            string oldLineStr = strb.ToString();
            if (oldLineStr.IndexOf(",") == -1)
            {
                // 如果該行未包含逗號
                if (containsNumber(oldLineStr, "\"") % 2 == 0)
                {
                    // 如果包含偶數個引號
                    isLogicLine = true;
                    break;
                }
                else
                {
                    if (oldLineStr.StartsWith("\""))
                    {
                        if (oldLineStr.Equals("\""))
                        {
                            continue;
                        }
                        else
                        {
                            string tempOldStr = oldLineStr.Substring(1);
                            if (isQuoteAdjacent(tempOldStr))
                            {
                                // 如果剩下的引號兩兩相鄰,則不是一行
                                continue;
                            }
                            else
                            {
                                // 否則就是一行
                                isLogicLine = true;
                                break;
                            }
                        }
                    }
                }
            }
            else
            {
                // quotes表示復數的quote
                string tempOldLineStr = oldLineStr.Replace("\"\"", "");
                int lastQuoteIndex = tempOldLineStr.LastIndexOf("\"");
                if (lastQuoteIndex == 0)
                {
                    continue;
                }
                else if (lastQuoteIndex == -1)
                {
                    isLogicLine = true;
                    break;
                }
                else
                {
                    tempOldLineStr = tempOldLineStr.Replace("\",\"", "");
                    lastQuoteIndex = tempOldLineStr.LastIndexOf("\"");
                    if (lastQuoteIndex == 0)
                    {
                        continue;
                    }
                    if (tempOldLineStr[lastQuoteIndex - 1] == ',')
                    {
                        continue;
                    }
                    else
                    {
                        isLogicLine = true;
                        break;
                    }
                }
            }
        }
        if (strb == null)
        {
            // 讀到行尾時為返回
            return false;
        }
        // 提取邏輯行
        logicLineStr = strb.ToString();
        if (logicLineStr != null)
        {
            // 拆分邏輯行,把分離出來的原子字符串放入向量中
            while (!logicLineStr.Equals(""))
            {
                string[] ret = readAtomString(logicLineStr);
                string atomString = ret[0];
                logicLineStr = ret[1];
                vContent.Add(atomString);
            }
        }
        return true;
    }

    private List<string> getLineContentVector()
    {
        if (this.readCSVNextRecord())
        {
            return this.vContent;
        }
        return null;
    }

    private List<string> getVContent()
    {
        return this.vContent;
    }

    public int GetRow()
    {
        if (null == table)
        {
            throw new System.Exception("table尚未初始化,請檢查是否成功讀取");
        }
        return table.Count;
    }

    public int GetCol()
    {
        if (null == table)
        {
            throw new System.Exception("table尚未初始化,請檢查是否成功讀取");
        }
        if (table.Count == 0)
        {
            throw new System.Exception("table內容為空");
        }
        return table[0].Count;
    }

    public int GetFirstIndexAtCol(string str, int col)
    {
        if (null == table)
        {
            throw new System.Exception("table尚未初始化,請檢查是否成功讀取");
        }
        if (table.Count == 0)
        {
            throw new System.Exception("table內容為空");
        }
        if (col >= table[0].Count)
        {
            throw new System.Exception("參數錯誤:col大於最大行");
        }
        for (int i = 0; i < table.Count; ++i)
        {
            if (table[i][col].Equals(str))
            {
                return i;
            }
        }
        return -1;
    }

    public int GetFirstIndexAtRow(string str, int row)
    {
        if (null == table)
        {
            throw new System.Exception("table尚未初始化,請檢查是否成功讀取");
        }
        if (table.Count == 0)
        {
            throw new System.Exception("table內容為空");
        }
        if (row >= table.Count)
        {
            throw new System.Exception("參數錯誤:cow大於最大列");
        }
        int tempCount = table[0].Count;
        for (int i = 0; i < tempCount; ++i)
        {
            if (table[row][i].Equals(str))
            {
                return i;
            }
        }
        return -1;
    }

    public int[] GetIndexsAtCol(string str, int col)
    {
        if (null == table)
        {
            throw new System.Exception("table尚未初始化,請檢查是否成功讀取");
        }
        if (table.Count == 0)
        {
            throw new System.Exception("table內容為空");
        }
        if (col >= table[0].Count)
        {
            throw new System.Exception("參數錯誤:col大於最大行");
        }
        List<int> tempList = new List<int>();
        for (int i = 0; i < table.Count; ++i)
        {
            if (table[i][col].Equals(str))
            {
                // 增加
                tempList.Add(i);
            }
        }
        return tempList.ToArray();
    }

    public int[] GetIndexsAtRow(string str, int row)
    {
        if (null == table)
        {
            throw new System.Exception("table尚未初始化,請檢查是否成功讀取");
        }
        if (table.Count == 0)
        {
            throw new System.Exception("table內容為空");
        }
        if (row >= table.Count)
        {
            throw new System.Exception("參數錯誤:cow大於最大列");
        }
        int tempCount = table[0].Count;
        List<int> tempList = new List<int>();
        for (int i = 0; i < tempCount; ++i)
        {
            if (table[row][i].Equals(str))
            {
                tempList.Add(i);
            }
        }
        return tempList.ToArray();
    }

    public string GetValueAt(int col, int row)
    {
        if (null == table)
        {
            throw new System.Exception("table尚未初始化,請檢查是否成功讀取");
        }
        if (table.Count == 0)
        {
            throw new System.Exception("table內容為空");
        }
        if (row >= table.Count)
        {
            throw new System.Exception("參數錯誤:row大於最大列");
        }
        if (col >= table[0].Count)
        {
            throw new System.Exception("參數錯誤:col大於最大行");
        }
        return table[row][col];
    }

}

本地化工具類

using UnityEngine;
using System.Collections.Generic;
using System.IO;

public class LTLocalization
{

    public const string LANGUAGE_ENGLISH = "EN";
    public const string LANGUAGE_CHINESE = "CN";
    public const string LANGUAGE_JAPANESE = "JP";
    public const string LANGUAGE_FRENCH = "FR";
    public const string LANGUAGE_GERMAN = "GE";
    public const string LANGUAGE_ITALY = "IT";
    public const string LANGUAGE_KOREA = "KR";
    public const string LANGUAGE_RUSSIA = "RU";
    public const string LANGUAGE_SPANISH = "SP";

    private const string KEY_CODE = "KEY";
    private const string FILE_PATH = "LTLocalization/localization";

    private SystemLanguage language = SystemLanguage.Chinese;
    private Dictionary<string, string> textData = new Dictionary<string, string>();

    private static LTLocalization mInstance;

    private LTLocalization()
    {
    }

    private static string GetLanguageAB(SystemLanguage language)
    {
        switch (language)
        {
            case SystemLanguage.Afrikaans:
            case SystemLanguage.Arabic:
            case SystemLanguage.Basque:
            case SystemLanguage.Belarusian:
            case SystemLanguage.Bulgarian:
            case SystemLanguage.Catalan:
                return LANGUAGE_ENGLISH;
            case SystemLanguage.Chinese:
            case SystemLanguage.ChineseTraditional:
            case SystemLanguage.ChineseSimplified:
                return LANGUAGE_CHINESE;
            case SystemLanguage.Czech:
            case SystemLanguage.Danish:
            case SystemLanguage.Dutch:
            case SystemLanguage.English:
            case SystemLanguage.Estonian:
            case SystemLanguage.Faroese:
            case SystemLanguage.Finnish:
                return LANGUAGE_ENGLISH;
            case SystemLanguage.French:
                return LANGUAGE_FRENCH;
            case SystemLanguage.German:
                return LANGUAGE_GERMAN;
            case SystemLanguage.Greek:
            case SystemLanguage.Hebrew:
            case SystemLanguage.Icelandic:
            case SystemLanguage.Indonesian:
                return LANGUAGE_ENGLISH;
            case SystemLanguage.Italian:
                return LANGUAGE_ITALY;
            case SystemLanguage.Japanese:
                return LANGUAGE_JAPANESE;
            case SystemLanguage.Korean:
                return LANGUAGE_KOREA;
            case SystemLanguage.Latvian:
            case SystemLanguage.Lithuanian:
            case SystemLanguage.Norwegian:
            case SystemLanguage.Polish:
            case SystemLanguage.Portuguese:
            case SystemLanguage.Romanian:
                return LANGUAGE_ENGLISH;
            case SystemLanguage.Russian:
                return LANGUAGE_RUSSIA;
            case SystemLanguage.SerboCroatian:
            case SystemLanguage.Slovak:
            case SystemLanguage.Slovenian:
                return LANGUAGE_ENGLISH;
            case SystemLanguage.Spanish:
                return LANGUAGE_SPANISH;
            case SystemLanguage.Swedish:
            case SystemLanguage.Thai:
            case SystemLanguage.Turkish:
            case SystemLanguage.Ukrainian:
            case SystemLanguage.Vietnamese:
            case SystemLanguage.Unknown:
                return LANGUAGE_ENGLISH;
        }
        return LANGUAGE_CHINESE;
    }

    private void ReadData()
    {
        textData.Clear();
        string fileName = Application.dataPath + "/Resources/LTLocalization/localization.csv";
        string csvStr = ((TextAsset)Resources.Load(FILE_PATH, typeof(TextAsset))).text;
        LTCSVLoader loader = new LTCSVLoader();
        // loader.ReadFile(fileName);
        loader.ReadMultiLine(csvStr);
        int languageIndex = loader.GetFirstIndexAtRow(GetLanguageAB(language), 0);
        if (-1 == languageIndex)
        {
            Debug.LogError("未讀取到" + language + "任何數據,請檢查配置表");
            return;
        }
        int tempRow = loader.GetRow();
        for (int i = 0; i < tempRow; ++i)
        {
            textData.Add(loader.GetValueAt(0, i), loader.GetValueAt(languageIndex, i));
        }
    }

    private void SetLanguage(SystemLanguage language)
    {
        this.language = language;
    }

    public static void Init()
    {
        mInstance = new LTLocalization();
        mInstance.SetLanguage(SystemLanguage.Chinese);
        mInstance.ReadData();
    }


    public static void ManualSetLanguage(SystemLanguage setLanguage)
    {
        if (null == mInstance)
        {
            mInstance = new LTLocalization();
        }
        mInstance.SetLanguage(setLanguage);
        mInstance.ReadData();
    }

    public static string GetText(string key)
    {
        if (null == mInstance)
        {
            Init();
        }
        if (mInstance.textData.ContainsKey(key))
        {
            return mInstance.textData[key];
        }
        return "[NoDefine]" + key;
    }

}

缺陷

1.讀取csv的時候是一次性全部讀取到內存中,對於大文件可能會出問題
2.對於有中文的文件只支持UTF-8的格式,需要手動轉換成UTF-8編碼

總結

其實還是高估的自己的代碼突破能力,本來想通過File去讀取的
結果發現unity打包支持的文件無法進行正常的File讀取
只能使用unity自帶的TextAsset文件類型去支持,然后就只支持UTF-8了
以后有機會的話再優化吧,先用着...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM