C#6.0字符串插值本地化


67

C#6.0具有字符串插值-一个很好的功能,用于格式化字符串,例如:

 var name = "John";
 WriteLine($"My name is {name}");

该示例转换为

 var name = "John";
 WriteLine(String.Format("My name is {0}", name));

从本地化的角度来看,最好存储类似以下的字符串:

"My name is {name} {middlename} {surname}" 

比String.Format表示法:

"My name is {0} {1} {2}"

如何使用字符串插值进行.NET本地化?是否有一种将$“ ...”放入资源文件的方法?还是应该像“ ... {name}”那样存储字符串并以某种方式即时插值?

PS这个问题不是关于“如何制作string.FormatIt扩展名”(有很多这样的库,SO答案等)。这个问题是关于Roslyn扩展(在“本地化”上下文中用于“字符串插值”)(都是MS .NET词汇中的术语)或动态用法(例如Dylan提出)的问题。


你能澄清你的问题吗?您是在问如何在C#6.0中执行此操作,还是在早期版本的C#中如何执行此操作?
BJ迈尔斯

我不会将这些字符串存储在文件中以供以后读取。您仍然应该创建一个存储这些字段的类。读入它们,然后将它们在本地通过字符串插值。
t3dodson

@nintendojunkie我在问C#6.0。关于是否可以使用C#6.0字符串插值进行本地化以及如何进行定位。
MajesticRa 2015年

1
尚无回应,只是我正在罗斯林的叉子上提供TranslatableStrings
Monoman,2015年

Answers:


2

使用Microsoft.CodeAnalysis.CSharp.Scripting包,您可以实现此目的。

您将需要创建一个对象来存储数据,该对象位于使用动态对象的下方。您还可以使用所需的所有属性创建一个特定的类。将动态对象包装在类中的原因在此处描述。

public class DynamicData
{
    public dynamic Data { get; } = new ExpandoObject();
}

然后可以如下所示使用它。

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);

这是在编译代码片段并执行它,因此它是真正的C#字符串插值。虽然您必须考虑到它的性能,因为它实际上是在运行时编译和执行代码的。要避免这种性能下降,可以使用CSharpScript.Create编译和缓存代码。


3
我不确定它是否实用,但至少它很有趣,可以回答问题!我希望@Monoman能够完成Roslyn扩展程序
MajesticRa

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);

类似地:

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();
    }
}

要回答您的问题:

框架没有提供当前机制来在运行时评估插值字符串。因此,您不能立即存储字符串并进行插值。

有一些库可以处理字符串的运行时内插。


我同意插值与许多库的format(“ ... {name}”)不同。您的帖子仅说明已在另一个级别完成。是真的。但是.NET,C#和Roslyn为您提供了如此丰富的可能性,以至于我不同意“您不能实时存储字符串和进行插值”。你绝对可以。问题是:如何执行,是否有人实施,有人有想法,是否值得花时间在某种程度上实施。等等
MajesticRa

我相信您是对的,因为可以为运行时字符串插值编写ros​​lyn扩展名。我已经更新了答案,以反映该框架不提供支持。
达斯汀·金根

插值库:我最喜欢的是HenriFormatter,概述在haacked.com/archive/2009/01/14/named-formats-redux.aspx上。它将允许您编写WriteLine(“我的名字是{name}。”。.HenriFormat(new {name =“ fred”}))。
马丁

15

根据Roslyn Codeplex网站上的讨论,字符串插值可能与资源文件不兼容(重点是我的):

字符串内插比String.Format或串联更整洁且更容易调试...

Dim y = $"Robot {name} reporting
{coolant.name} levels are {coolant.level}
{reactor.name} levels are {reactor.level}"

但是,这个例子很糟糕。大多数专业程序员不会在代码中编写面向用户的字符串。相反,出于本地化的原因,它们会将这些字符串存储在资源(.resw,.resx或.xlf)中。因此,这里的字符串插值似乎没什么用。


2
我看过帖子。它看起来更像是讨论,而不是规格或开发人员的愿景。是时候阅读未定义字符串插值格式的文章了。您可以在同一论坛上找到其他文章,讨论本地化如何从字符串插值中受益
MajesticRa 2015年

6

如前面的答案中已经说过的:当前,您无法在运行时(例如从资源文件中)加载格式字符串以进行字符串插值,因为它是在编译时使用的。

如果您不关心编译时功能,而只想命名占位符,则可以使用以下扩展方法:

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);
}

请注意,您不能有内联表达式,例如 {i+1}此处,并且这不是具有最佳性能的代码。

您可以将其与从资源文件或内联加载的字典一起使用,如下所示:

var txt = "Hello {name} on {day:yyyy-MM-dd}!".StringFormat(new Dictionary<string, object>
            {
                ["name"] = "Joe",
                ["day"] = DateTime.Now,
            });

感谢你的回答。但这并不能回答问题。不幸的是,您花了时间回答这个问题,而没有花费您的时间仔细周到地阅读。
MajesticRa's

2
@MajesticRa他回答了您的问题,甚至很友善地举了一个如何实现该问题的示例。如果您想使用其他答案,请提出更准确的问题。您询问是否将有一种使用字符串插值进行本地化的方法。答案是否定的,特别是因为它似乎是编译器功能而不是语言功能(即语法糖)。字符串是否应该像“ {name}”那样存储并即时插值?是。怎么样?机顶盒建议上述合理的方法。
约翰·

因为“字符串插值”和“本地化”都是在MS .NET词汇中具有明确含义的术语。但是尽管如此,在格式化扩展库和其他以一种或另一种方式提出的扩展库中,已经有了答案。所有讨论都描述了为什么这个问题不相关。SO上有大量的“如何进行字符串格式扩展”质量检查。因此,这显然与上下文无关。但是,正如您所说,好的,我已经澄清了最新信息……
MajesticRa

5

假设您的问题更多是关于如何在源代码中本地化插值字符串,而不是如何处理插值字符串资源...

给定示例代码:

var name = "John";
var middlename = "W";
var surname = "Bloggs";
var text = $"My name is {name} {middlename} {surname}";
Console.WriteLine(text);

输出显然是:

My name is John W Bloggs

现在,更改文本分配以获取翻译:

var text = Translate($"My name is {name} {middlename} {surname}");

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
}

您需要提供自己的实现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.

话虽如此,使用这种翻译风格虽然易于开发,但实际上却很难翻译,因为字符串被埋在了代码中,并且仅在运行时浮出水面。除非您有一个可以静态浏览代码并提取所有可翻译字符串的工具(无需在运行时点击该代码路径),否则最好使用更多传统的resx文件,因为它们固有地会为您提供文本表被翻译。


4

如果格式字符串不在您的C#源代码中,则C#6.0字符串插值将无法为您提供帮助。在这种情况下,您将不得不使用其他解决方案,例如this library


我知道,有几个……我可以说,很多这样的库和扩展。但是字符串插值是另一种野兽,就像@Romoku在他的回答中指出的那样。有趣的是,是否可以使用此工具。
MajesticRa

或者我最喜欢的HenriFormatter在haacked.com/archive/2009/01/14/named-formats-redux.aspx概述。它将允许您编写WriteLine(“我的名字是{name}。”。.HenriFormat(new {name =“ fred”}))。
马丁

1

字符串插值很难与本地化结合使用,因为编译器更喜欢将其转换为string.Format(...),这不支持本地化。但是,有一个技巧可以将本地化和字符串插值结合起来。在本文结尾处对此进行了描述。

通常,字符串插值转换为string.Format,其行为无法自定义。但是,与lambda方法有时成为表达式树的方式几乎相同,如果目标方法接受对象,则编译器将从切换string.FormatFormattableStringFactory.Create(.NET 4.6方法)System.FormattableString

问题是,编译器更喜欢称string.Format如果可能的话,所以如果有过载Localized()接受的FormattableString,它不会与串插工作,因为C#编译器将其忽略[因为是接受纯字符串过载。实际上,这还比这更糟:编译器FormattableString在调用扩展方法时也拒绝使用。

如果您使用非扩展方法,它可以工作。例如:

static class Loca
{
    public static string lize(this FormattableString message)
        { return message.Format.Localized(message.GetArguments()); }
}

然后,您可以像这样使用它:

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}"


这需要更多的投票。我喜欢新的$“”格式,但由于无法使用资源而感到失望。使用您的示例,我能够将GetFormattedString(FormattableString str)添加到我们的资源查找代码中,该代码将str.Format传递给GetString(string str),然后返回String.Format(translatedFormat,str.GetArguments())。现在,我可以使用$“我的格式正确的字符串”调用该函数,并获取翻译后的+格式的字符串。
BOB

1

如果我们使用插值,那么我们是在考虑方法而不是常量。在这种情况下,我们可以将翻译定义为方法:

public abstract class InterpolatedText
{
    public abstract string GreetingWithName(string firstName, string lastName);
}

public class InterpolatedTextEnglish : InterpolatedText
{
    public override string GreetingWithName(string firstName, string lastName) =>
        $"Hello, my name is {firstName} {lastName}.";
}

然后,我们可以加载 InterpolatedText特定文化的实现。这也提供了一种实现回退的方法,因为一个实现可以从另一个实现继承。如果英语是默认语言,并且其他实现继承自英语,则至少要显示一些内容,直到提供翻译。

这似乎有些不合常规,但具有一些好处:

首先,用于插值的字符串始终存储在带有明确指定参数的强类型方法中。

鉴于此:"Hello, my name is {0} {1}"我们可以确定占位符按该顺序代表名字和姓氏吗?总会有一种将值与占位符匹配的方法,但是当插值字符串及其参数存储在一起时,混淆的空间就更少了。

同样,如果我们将翻译字符串存储在一个位置,然后在另一位置使用它们,则可以用一种破坏代码的方式修改它们。我们可以添加{2}一个字符串,该字符串将在其他地方使用,并且该代码将在运行时失败。

使用字符串插值是不可能的。如果我们的翻译字符串与可用参数不匹配,它甚至不会编译。


尽管我发现很难维持任何解决方案,但也有缺点。

最大的是可移植性。如果您的翻译是用C#编码并切换的,则导出所有翻译不是最容易的事情。

这也意味着,如果您希望将翻译分发给不同的人(除非您有一个会说一切的人),则翻译人员必须修改代码。这是简单的代码,但是仍然可以。


0

由于使用了内嵌的变量,因此无法将插值的字符串从其(变量)范围中重构出来。

重新定位字符串文字部分的唯一方法是将范围绑定变量作为参数传递给其他位置,并使用特殊的占位符标记它们在字符串中的位置。但是,此解决方案已经被“发明”了:

string.Format("literal with placeholers", parameters);

或某些高级库(内插运行时),但使用的概念完全相同(传递参数)。

然后,您可以将重构"literal with placeholers"为资源。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.