简介

嗯...一般来说做游戏啥的都不会只发一个国家,但是每个国家语言不同,就存在多语言本地化的问题,然后直接用过一个通过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了

以后有机会的话再优化吧,先用着...

unity多语言本地化的更多相关文章

  1. Unity多语言本地化改进版

    简介 之前捣鼓过一个通过csv配置游戏多语言支持的小工具,但是发现使用过程中,通过notepad++去进行转码很不方便,并且直接将配置的csv不加密的放在游戏中心里感觉不是很踏实 于是乎~~ 新的方案 ...

  2. 黄聪:让WordPress主题支持语言本地化(使用poedit软件实现中文翻译功能)

    如果你的WordPress主题要提交到WordPress官方主题库,使用者来自世界各地的多种语言,那么,你就要让你的WordPress主题支持语言本地化,方便使用者进行语言翻译和制作语言包. 让Wor ...

  3. IOS应用程序多语言本地化解决方案

    最近要对一款游戏进行多语言本地化,在网上找了一些方案,加上自己的一点点想法整理出一套方案和大家分享! 多语言在应用程序中一般有两种做法:一.程序中提供给用户自己选择的机会:二.根据当前用户当前移动设备 ...

  4. [Abp 源码分析]十三、多语言(本地化)处理

    0.简介 如果你所开发的需要走向世界的话,那么肯定需要针对每一个用户进行不同的本地化处理,有可能你的客户在日本,需要使用日语作为显示文本,也有可能你的客户在美国,需要使用英语作为显示文本.如果你还是一 ...

  5. iOS - 实现语言本地化/国际化

      实现iOS语言本地化/国际化(图文详解) 前言 语言本地化,又叫做语言国际化.是指根据用户操作系统的语言设置,自动将应用程序的语言设置为和用户操作系统语言一致的语言.往往一些应用程序需要提供给多个 ...

  6. [转]IOS应用程序多语言本地化解决方案

    最近要对一款游戏进行多语言本地化,在网上找了一些方案,加上自己的一点点想法整理出一套方案和大家分享! 多语言在应用程序中一般有两种做法:一.程序中提供给用户自己选择的机会:二.根据当前用户当前移动设备 ...

  7. 3分钟实现iOS语言本地化/国际化(图文详解)

    前言 语言本地化,又叫做语言国际化. 是指根据用户操作系统的语言设置,自动将应用程序的语言设置为和用户操作系统语言一致的语言. 往往一些应用程序需要提供给多个国家的人群使用,或者一个国家有多种语言,这 ...

  8. iOS语言本地化,中文显示

    尽管一直相信xcode肯定提供有语言本地化的设置地方,可是一直也没凑着去改.非常多的汉化,还是使用代码去控制:比方navagition的return使用代码改动为"返回"! 近期在 ...

  9. 【转】iOS多语言本地化(国际化)设置

    原文网址:http://www.jianshu.com/p/2b7743ae9c90 讨论的iOS应用中的多语言设置,Ok 一般是两种情况: 1.根据当前设备语言自动切换显示 2.在应用中可进行语言设 ...

随机推荐

  1. redis—持久化

    Redis持久化 概述 Redis的强大性能很大程度上都是因为所有数据都是存储在内存中的,然而当Redis重启后,所有存储在内存中的数据将会丢失,在很多情况下是无法容忍这样的事情的.所以,我们需要将内 ...

  2. Webstorm如何配置自动补全前缀--autoprefixer

    我们在写样式代码时,对不同平台会有不同的兼容性写法,会在代码前加前缀,但是手动加前缀很费时间而且很容易弄错.Webstorm编辑器是有自带补全前缀功能的,那为什么还要写这篇配置博客,因为Webstor ...

  3. String类的获取功能

    /* * String类的获取功能: * int length():获取字符串的长度,其实也就是字符个数 * char charAt(int index):获取指定索引处的字符 * int index ...

  4. Leetcode 22.生成括号对数

    生成括号对数 给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合. 例如,给出 n =3,生成结果为: [ "((()))", "( ...

  5. codevs4419 FFF 团卧底的菊花

    题目描述 Description FFF 团卧底在这次出题后就知道他的菊花可能有巨大的危险,于是他提前摆布好了菊花阵,现在菊花阵里有若干朵菊花,出现次数最多的那一朵就是出题人的,你的任务是需要找出出题 ...

  6. 洛谷—— P1122 最大子树和

    https://www.luogu.org/problem/show?pid=1122 题目描述 小明对数学饱有兴趣,并且是个勤奋好学的学生,总是在课后留在教室向老师请教一些问题.一天他早晨骑车去上课 ...

  7. MAPZONE GIS SDK接入Openlayers3之一——矢量数据集接入

    在选择开源前端GIS框架的时候,定下来MapBox和Openlayers3.起初被MapBox美观的地图显示效果所吸引,研究后发现其实现机制与MAPZONE GIS SDK相差深远,不如Openlay ...

  8. C语言实现单链表节点的删除(带头结点)

    我在之前一篇博客<C语言实现单链表节点的删除(不带头结点)>中具体实现了怎样在一个不带头结点的单链表的删除一个节点,在这一篇博客中我改成了带头结点的单链表.代码演示样例上传至 https: ...

  9. HDU 5015 233Matrix (构造矩阵)

    233 Matrix Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Tota ...

  10. [javase学习笔记]-7.7 thiskeyword的细节与应用

    这一节我们接着上一节来继续学习thiskeyword. 我们之前在7.5节中的构造函数应注意的细节中提到过一个细节就是构造函数能够调用一般函数,但一般函数不能直接调用构造函数.可是我们没有深究构造函数 ...