软件国际化,主要有两个方面:
1,软件界面静态内容的国际化,如标签,按钮,菜单等文本的多语言显示
2,软件动态内容的国际化,如动态消息,错误提示,确认信息,日志等的多语言显示,这些动态内容往往伴随着一些额外的参数,如删除确认信息,往往需要同时展示带删除内容的相关信息。
综合上面的特征,可以通过Key-Value配置文件的形式保存国际化的内容,Key是固定项,而Value根据不同的语言,配置不同的文本模板,同时根据需要,可以配置额外的参数标记符,显示时通过Key获取具体语言的文本模板,加上可选的额外参数进行格式化,生成最终的语言文本。
如下面代码所示的中文和英文的语言配置:
// 英语配置 AppConf.LoadFail = Load [{0}] fail: {1} AeadRepConf.Password = AeadRepConf.KeyGens = Password Key Generate algorithms AeadRepConf.MasterKeySize = Master Key Size AeadRepConf.FileIdSize = File Id Size AeadRepConf.BlockSize = Block Size AeadRepConf.KeyDerive = Key Derive algorithm AeadRepConf.DataCrypt = Data Encryption AeadRepConf.DirCrypt = Directory Encryption // 中文配置 AppConf.LoadFail = 读取配置[{0}]出错:{1} AeadRepConf.Password = 加密库访问密码 AeadRepConf.KeyGens = 密码哈希算法 AeadRepConf.MasterKeySize = 加密库主密钥长度 AeadRepConf.FileIdSize = 加密文件标识符长度 AeadRepConf.BlockSize = 加密文件数据分组大小 AeadRepConf.KeyDerive = 密钥衍生算法 AeadRepConf.DataCrypt = 文件数据加密算法 AeadRepConf.DirCrypt = 目录加密算法
语言配置文件放置到单独的目录中如[lang]目录,<地区代码/名称>.lang作为文件名,<地区代码/名称>使用操作系统定义的全球通用代码或名称,如windows系统下中文的代码和名称分别为:zh-CN, 中文(中国), Chinese (Simplified, China),使用这样的名称时,可以在程序启动时通过检测当前运行环境,自动选择合适的显示语言。
下面通过C#来展示国际化的实现方法,其他编程语言也能进行类似的实现。
初始化,加载程序默认语言或者用户选择的语言:
public static void init() { dir.trydo(() => { var code = defaultCodes().first(c => langPath(c).fileExist()); if (code != null) loadLang(code); }); }
动态文本翻译,通过Key将本文翻译为当前语言文本:
public static string trans(this object obj, string item, params object[] args) { return trans(getKey(obj.GetType().Name, ref item), args); } public static string trans(this Type type, string item, params object[] args) { return trans(getKey(type.Name, ref item), args); } static string getKey(string cls, ref string name) { if (name.Length > 0 && char.IsLower(name[0])) name = $"{char.ToUpper(name[0])}{name.Substring(1)}"; return $"{cls}.{name}"; } public static string trans(this string key, params object[] args) { if (values.TryGetValue(key, out string value)) { if (args.Length > 0) value = key.tryget(() => format(value, args)); if (value != null) return value; } value = args.Length > 0 ? $"{key}({string.Join(",", args)})" : key; if (trace) value.msg(); return value; }
翻译的方法除了直接通过Key翻译外,还扩展了使用object/Type+item作为参数的方法,使用object时,会将通过object.GetType将object转换为Type,最终使用Type.Name即类名+item的形式,即Key = Type.Name+"."+item,这样程序调用时可以简单的通过this.trans("OpenBtn", ...)这样的形式,而不用额外代码组装Key。
静态内容翻译,软件界面的按钮、菜单等的翻译:
public static void trans(this Control ui) { ui.layoutOnce(() => { var cls = ui.GetType().Name; foreach (var fld in ui.GetType().GetFields()) { var obj = fld.GetValue(ui); if (obj is UserControl uc) trans(uc); else if (obj is ToolStripItem tb) { tb.Text = transUIFld(cls, fld.Name, out var key); tb.ToolTipText = values.TryGetValue($"{key}Tip", out var tip) ? tip : tb.Text; } else if (obj is Control ct) ct.Text = transUIFld(cls, fld.Name); else if (obj is ColumnHeader ch) ch.Text = transUIFld(cls, fld.Name); } if (ui is Form form) form.Text = trans($"{cls}.Title"); }); }
这个翻译方法能处理所以继承自Control类型的UI控件,包括Form,UserControl,Panel等,该方法通过反射获取公共的Control成员变量,并根据变量类型,使用不同的方法翻译这些控件的文本,所以需要将Form或者UserControl的待翻译变量设置为公共成员才能翻译,同时可以根据需要,将一些不需要翻译的控件设置为私有成员,而实现忽略翻译的功能。
绑定语言菜单,当程序需要提供给用户选择语言时,通过语言菜单扩展函数,可以将可用语言显示到菜单列表上:
public static void initLang(this ToolStripMenuItem menu, Action update) { menu.trydo(() => { menu.Text = "语言(Language)"; menu.Tag = update; menu.DropDownOpening += menuOpen; foreach (var p in Directory.EnumerateFiles(dir, "*.lang")) { var code = Path.GetFileNameWithoutExtension(p).Trim(); menu.addLangItem(code, code); } }); }
update会在用户通过菜单改变语言的时候被调用,这样程序可以通过调用Control.trans()来更新用户选择的语言。
异常消息处理,程序的异常消息国际化通过扩展异常类实现:
public class Error : Exception { public Type type; public string item; public object[] args; public Error(object obj, string act, params object[] args) { this.type = obj.GetType(); this.item = act; this.args = args; } public Error(Type type, string act, params object[] args) { this.type = type; this.item = act; this.args = args; } public override string Message => type.trans(item, args); public string Json => new ErrorJson { code = $"{type.Name}.{item}", args = args, }.json(); }
多语言的异常类类似动态本文翻译,其内部保存了key和相关参数,获取异常消息时,会自动调用trans来返回翻译后的文本。
效果图:
完整代码:Lang.cs
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Threading; using System.Windows.Forms; using util.ext; namespace util { public static class Lang { public static string dir = $"{Dir.AppDir}/lang"; public static string current = null; public static Dictionary<string, string> values = new Dictionary<string, string>(); public static bool trace = false; static string CurrentPath => $"{dir}/current"; static IEnumerable<string> defaultCodes() { if (CurrentPath.fileExist()) yield return dir.tryget(() => CurrentPath.readText().Trim()); var cu = CultureInfo.CurrentCulture; yield return cu.NativeName; yield return cu.EnglishName; yield return cu.Name; } static string langPath(string code) => $"{dir}/{code}.lang"; public static void init() { dir.trydo(() => { var code = defaultCodes().first(c => langPath(c).fileExist()); if (code != null) loadLang(code); }); } public static string trans(this object obj, string item, params object[] args) { return trans(getKey(obj.GetType().Name, ref item), args); } public static string trans(this Type type, string item, params object[] args) { return trans(getKey(type.Name, ref item), args); } static string getKey(string cls, ref string name) { if (name.Length > 0 && char.IsLower(name[0])) name = $"{char.ToUpper(name[0])}{name.Substring(1)}"; return $"{cls}.{name}"; } public static string trans(this string key, params object[] args) { if (values.TryGetValue(key, out string value)) { if (args.Length > 0) value = key.tryget(() => format(value, args)); if (value != null) return value; } value = args.Length > 0 ? $"{key}({string.Join(",", args)})" : key; if (trace) value.msg(); return value; } static string format(string value, object[] args) => string.Format(value, args); public static void trans(this Control ui) { ui.layoutOnce(() => { var cls = ui.GetType().Name; foreach (var fld in ui.GetType().GetFields()) { var obj = fld.GetValue(ui); if (obj is UserControl uc) trans(uc); else if (obj is ToolStripItem tb) { tb.Text = transUIFld(cls, fld.Name, out var key); tb.ToolTipText = values.TryGetValue($"{key}Tip", out var tip) ? tip : tb.Text; } else if (obj is Control ct) ct.Text = transUIFld(cls, fld.Name); else if (obj is ColumnHeader ch) ch.Text = transUIFld(cls, fld.Name); } if (ui is Form form) form.Text = trans($"{cls}.Title"); }); } static string transUIFld(string cls, string name) => transUIFld(cls, name, out var key); public static string transUIFld(string cls, string name, out string key) { key = getKey(cls, ref name); if (values.TryGetValue(name, out var value)) return value; if (values.TryGetValue(key, out value)) return value; if (trace) key.msg(); return defaultUIText(name); } static string defaultUIText(string name) { int idx = name.Length - 1; while (idx >= 0 && !char.IsUpper(name[idx])) { idx--; } return idx < 0 ? name : name.Substring(0, idx); } public static void initLang(this ToolStripMenuItem menu, Action update) { menu.trydo(() => { menu.Text = "语言(Language)"; menu.Tag = update; menu.DropDownOpening += menuOpen; foreach (var p in Directory.EnumerateFiles(dir, "*.lang")) { var code = Path.GetFileNameWithoutExtension(p).Trim(); menu.addLangItem(code, code); } }); } static void addLocaleItem(this ToolStripMenuItem menu) { var item = new ToolStripMenuItem() { Text = GenLocales, }; item.Click += localeClick; menu.DropDownItems.Add(item); } const string GenLocales = "地区列表(Locales)"; static void localeClick(object s, EventArgs e) { e.trydo(() => { var path = $"{dir}/locales.txt"; using (var fout = File.CreateText(path)) { fout.WriteLine($"Code,\tNativeName,\tEnglishName"); foreach(var c in CultureInfo.GetCultures(CultureTypes.SpecificCultures)) { fout.WriteLine($"{c.Name},\t{c.NativeName},\t{c.EnglishName}"); } } $"{GenLocales}: {path}".msg(); }); } static void addLangItem(this ToolStripMenuItem menu, string code, string text) { var item = new ToolStripMenuItem() { Text = text, Tag = code, }; item.Click += langClick; menu.DropDownItems.Add(item); } static void menuOpen(object s, EventArgs e) { (s as ToolStripMenuItem).DropDownItems .each<ToolStripMenuItem>(it => it.Checked = (it.Tag as string) == current); } static void langClick(object s, EventArgs e) { e.trydo(() => { var menu = s as ToolStripMenuItem; var code = menu.Tag as string; if (code == Lang.current) return; loadLang(code); var notify = menu.OwnerItem.Tag as Action; notify(); File.WriteAllText(CurrentPath, code); }); } static void loadLang(string code) { try { Lang.values = langPath(code).kvLoad(); Lang.current = code; } catch (Exception err) { string msg = $"加载语言[{code}]失败(Fail to load language): {err.Message}"; throw new Exception(msg); } } } }View Code
Github:
https://github.com/bsmith-zhao/vfs/blob/main/util/Lang.cs
标签:国际化,string,args,Value,code,static,Key,var,public From: https://www.cnblogs.com/bsmith/p/17766969.html