C#6.0有一个字符串插值 - 一个很好的功能来格式化字符串,如:
var name = "John";
WriteLine($"My name is {name}");
Run Code Online (Sandbox Code Playgroud)
该示例转换为
var name = "John";
WriteLine(String.Format("My name is {0}", name));
Run Code Online (Sandbox Code Playgroud)
从本地化的角度来看,存储字符串更好:
"My name is {name} {middlename} {surname}"
Run Code Online (Sandbox Code Playgroud)
而不是String.Format表示法:
"My name is {0} {1} {2}"
Run Code Online (Sandbox Code Playgroud)
如何使用字符串插值进行.NET本地化?是否有办法将$"..."放入资源文件?或者字符串应该像"...... {name}"一样存储,并以某种方式在飞行中进行插值?
PS这个问题不是关于"如何制作string.FormatIt扩展"(有很多这样的库,SO答案等).这个问题是关于"本地化"上下文中的"字符串插值"的Roslyn扩展(两者都是MS .NET词汇表中的术语),或者像Dylan提出的动态用法.
Dus*_*gen 35
内插串评估大括号作为C#表达之间的块(例如{expression},{1 + 1},{person.FirstName}).
这意味着插值字符串中的表达式必须引用当前上下文中的名称.
例如,此语句将无法编译:
var nameFormat = $"My name is {name}"; // Cannot use *name*
// before it is declared
var name = "Fred";
WriteLine(nameFormat);
Run Code Online (Sandbox Code Playgroud)
同理:
class Program
{
const string interpolated = $"{firstName}"; // Name *firstName* does not exist
// in the current context
static void Main(string[] args)
{
var firstName = "fred";
Console.WriteLine(interpolated);
Console.ReadKey();
}
}
Run Code Online (Sandbox Code Playgroud)
回答你的问题:
框架没有提供当前机制来在运行时评估插值字符串.因此,您无法动态存储字符串并进行插值.
存在处理字符串的运行时插值的库.
BJ *_*ers 15
根据Roslyn codeplex网站上的讨论,字符串插值可能与资源文件不兼容(强调我的):
字符串插值可以比String.Format或连接更整洁,更容易调试...
Dim y = $"Robot {name} reporting
{coolant.name} levels are {coolant.level}
{reactor.name} levels are {reactor.level}"
Run Code Online (Sandbox Code Playgroud)
但是,这个例子很可疑.大多数专业程序员不会在代码中编写面向用户的字符串.相反,他们会将这些字符串存储在资源(.resw,.resx或.xlf)中,以便进行本地化.所以这里没有太多用于字符串插值的东西.
如前面的答案中已经说过的:您当前无法在运行时(例如从资源文件中)加载格式字符串以进行字符串插值,因为它是在编译时使用的。
如果您不关心编译时功能,而只想命名占位符,则可以使用以下扩展方法:
public static string StringFormat(this string input, Dictionary<string, object> elements)
{
int i = 0;
var values = new object[elements.Count];
foreach (var elem in elements)
{
input = Regex.Replace(input, "{" + Regex.Escape(elem.Key) + "(?<format>[^}]+)?}", "{" + i + "${format}}");
values[i++] = elem.Value;
}
return string.Format(input, values);
}
Run Code Online (Sandbox Code Playgroud)
请注意,您不能具有像{i+1}此处这样的内联表达式,并且这不是具有最佳性能的代码。
您可以将其与从资源文件或内联加载的字典一起使用,如下所示:
var txt = "Hello {name} on {day:yyyy-MM-dd}!".StringFormat(new Dictionary<string, object>
{
["name"] = "Joe",
["day"] = DateTime.Now,
});
Run Code Online (Sandbox Code Playgroud)
字符串插值很难与本地化结合起来,因为编译器更喜欢将其转换为string.Format(...)不支持本地化的 。然而,有一个技巧可以将本地化和字符串插值结合起来;本文临近结尾处对此进行了描述。
通常字符串插值被转换为
string.Format,其行为无法定制。然而,就像 lambda 方法有时变成表达式树一样,如果目标方法接受对象,编译器将从 切换string.Format到(.NET 4.6 方法) 。FormattableStringFactory.CreateSystem.FormattableString问题是,编译器更愿意
string.Format在可能的情况下进行调用,因此如果存在Localized()接受的重载FormattableString,则它将无法使用字符串插值,因为 C# 编译器会简单地忽略它[因为有一个接受纯字符串的重载]。FormattableString实际上,情况比这更糟糕:编译器在调用扩展方法时也拒绝使用。如果您使用非扩展方法,它可以工作。例如:
Run Code Online (Sandbox Code Playgroud)static class Loca { public static string lize(this FormattableString message) { return message.Format.Localized(message.GetArguments()); } }然后你可以像这样使用它:
Run Code Online (Sandbox Code Playgroud)public class Program { public static void Main(string[] args) { Localize.UseResourceManager(Resources.ResourceManager); var name = "Dave"; Console.WriteLine(Loca.lize($"Hello, {name}")); } }重要的是要认识到编译器将字符串转换
$"..."为老式格式字符串。所以在这个例子中,Loca.lize实际上接收的"Hello, {0}"是格式字符串,而不是"Hello, {name}".
假设您的问题更多是关于如何在源代码中本地化内插字符串,而不是如何处理内插字符串资源...
鉴于示例代码:
var name = "John";
var middlename = "W";
var surname = "Bloggs";
var text = $"My name is {name} {middlename} {surname}";
Console.WriteLine(text);
Run Code Online (Sandbox Code Playgroud)
输出显然是:
My name is John W Bloggs
Run Code Online (Sandbox Code Playgroud)
现在更改文本分配以获取翻译:
var text = Translate($"My name is {name} {middlename} {surname}");
Run Code Online (Sandbox Code Playgroud)
Translate 是这样实现的:
public static string Translate(FormattableString text)
{
return string.Format(GetTranslation(text.Format),
text.GetArguments());
}
private static string GetTranslation(string text)
{
return text; // actually use gettext or whatever
}
Run Code Online (Sandbox Code Playgroud)
您需要提供自己的实现GetTranslation;它将接收一个类似的字符串,"My name is {0} {1} {2}"并且应该使用 GetText 或资源或类似的来定位并返回一个合适的翻译,或者只返回原始参数以跳过翻译。
您仍然需要为您的翻译人员记录参数编号的含义;原始代码字符串中使用的文本在运行时不存在。
例如,如果在这种情况下GetTranslation返回"{2}. {0} {2}, {1}. Don't wear it out."(嘿,本地化不仅仅是关于语言!)那么完整程序的输出将是:
Bloggs. John Bloggs, W. Don't wear it out.
Run Code Online (Sandbox Code Playgroud)
话虽如此,虽然使用这种翻译风格很容易开发,但实际翻译却很难,因为字符串隐藏在代码中,只有在运行时才会浮出水面。除非您有一个工具可以静态地探索您的代码并提取所有可翻译的字符串(而不必在运行时点击该代码路径),否则您最好使用更传统的 resx 文件,因为它们本质上为您提供了一个文本表被翻译。
使用Microsoft.CodeAnalysis.CSharp.Scripting包您可以实现此目的。
您将需要创建一个对象来存储数据,下面使用动态对象。您还可以创建一个具有所需所有属性的特定类。此处描述了将动态对象包装在类中的原因。
public class DynamicData
{
public dynamic Data { get; } = new ExpandoObject();
}
Run Code Online (Sandbox Code Playgroud)
然后您可以如下所示使用它。
var options = ScriptOptions.Default
.AddReferences(
typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).GetTypeInfo().Assembly,
typeof(System.Runtime.CompilerServices.DynamicAttribute).GetTypeInfo().Assembly);
var globals = new DynamicData();
globals.Data.Name = "John";
globals.Data.MiddleName = "James";
globals.Data.Surname = "Jamison";
var text = "My name is {Data.Name} {Data.MiddleName} {Data.Surname}";
var result = await CSharpScript.EvaluateAsync<string>($"$\"{text}\"", options, globals);
Run Code Online (Sandbox Code Playgroud)
这是编译代码片段并执行它,因此它是真正的 C# 字符串插值。尽管您必须考虑它的性能,因为它实际上是在运行时编译和执行您的代码。为了解决这个性能问题,您可以使用 CSharpScript.Create 来编译和缓存代码。
| 归档时间: |
|
| 查看次数: |
13598 次 |
| 最近记录: |