扩展@FrancescoLorenzetti84 的答案,我过去为使其更易于维护而做过的一种方法是将数据库检索包装在 ResourceString 类中,以便您可以执行以下操作:
private static readonly ResourceString res = "The value";
然后在代码中引用它。在幕后,ResourceString 类完成了这项工作。这是一个例子:
namespace ResString
{
public interface IResourceResolver
{
string Resolve(string key, string defaultValue);
}
public class ResourceString
{
public ResourceString(string value)
{
this.defaultValue = value;
GetOwner();
}
public string Value
{
get
{
if (!resolved)
Resolve();
return value;
}
}
public override string ToString()
{
return Value;
}
public static implicit operator string(ResourceString rhs)
{
return rhs.Value;
}
public static implicit operator ResourceString(string rhs)
{
return new ResourceString(rhs);
}
protected virtual void Resolve()
{
if (Resolver != null)
{
if (key == null)
key = GetKey();
value = Resolver.Resolve(key, defaultValue);
}
else
{
value = defaultValue;
}
resolved = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
protected virtual void GetOwner()
{
StackTrace trace = new StackTrace();
StackFrame frame = null;
int i = 1;
while (i < trace.FrameCount && (owner == null || typeof(ResourceString).IsAssignableFrom(owner)))
{
frame = trace.GetFrame(i);
MethodBase meth = frame.GetMethod();
owner = meth.DeclaringType;
i++;
}
}
protected virtual string GetKey()
{
string result = owner.FullName;
FieldInfo field = owner.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).Where(f =>
typeof(ResourceString).IsAssignableFrom(f.FieldType) && f.GetValue(null) == this
).FirstOrDefault();
if (field != null)
result += "." + field.Name;
return result;
}
public static IResourceResolver Resolver { get; set; }
private string defaultValue;
private string value;
private bool resolved;
private string key;
private Type owner;
}
}
还有一个示例程序:
namespace ResString
{
class Program
{
/// <summary>
/// Description for the first resource.
/// </summary>
private static readonly ResourceString firstRes = "First";
/// <summary>
/// Description for the second resource.
/// </summary>
private static readonly ResourceString secondRes = "Second";
/// <summary>
/// Description for the format string.
/// </summary>
private static readonly ResourceString format = "{0} {1}";
static void Main(string[] args)
{
ResourceString.Resolver = new French();
Console.WriteLine(String.Format(format, firstRes, secondRes));
}
private class French : IResourceResolver
{
public string Resolve(string key, string defaultValue)
{
switch (key)
{
case "ResString.Program.firstRes":
return "Premier";
case "ResString.Program.secondRes":
return "Deuxième";
case "ResString.Program.format":
return "{1} {0}";
}
return defaultValue;
}
}
}
}
如果你运行它,它将输出: Deuxième Premier
注释掉 Resolver assignment,你会得到: First Second
在 UI 中使用字符串的任何地方,都应使用已声明的 ResourceString。
在解析字符串值后更改解析器不会改变它们的值,因为这些值只被检索一次。您当然需要编写一个从数据库中提取的真正解析器。
然后,您需要一个实用程序来运行已编译的类并提取 ResourceString 声明并将键和默认值放入数据库或文本文件中,以便可以翻译它们。这还应该通过为每个程序集生成的帮助 XML 文件,并为 ResourceString 声明提取注释,以便翻译器有一些上下文可以使用。键声明还将提供上下文,因为您可以轻松地按 UI 类对资源进行分组。
将此添加到构建脚本中,以确保它定期更新。
您可以对图像等使用相同的方法。