C#6.0字符串插值本地化

C#6.0字符串插值本地化,c#,.net,c#-6.0,C#,.net,C# 6.0,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}" 而不是字符串。格式表示法: "My name

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}" 
而不是字符串。格式表示法:

"My name is {0} {1} {2}"
如何使用字符串插值进行.NET本地化?是否有办法将$“..”放入资源文件?或者字符串应该像“…{name}”那样存储并以某种方式动态插入


另外,这个问题不是关于“如何进行string.formatt扩展”(有很多这样的库,所以答案等等)。这个问题是关于“本地化”上下文中“字符串插值”的Roslyn扩展(两者都是MS.NET词汇表中的术语)或Dylan建议的动态用法。

如果格式字符串不在C源代码中,C#6.0字符串插值将帮不上忙。在这种情况下,您必须使用其他解决方案,例如。

插值字符串将花括号之间的块作为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();
    }
}
回答你的问题:

框架目前没有提供在运行时评估插值字符串的机制。因此,您不能存储字符串并在开箱即用的情况下进行插值

存在处理字符串运行时插值的库。

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

字符串插值可能比String.Format或concatenation更整洁、更易于调试

然而,这个例子是可疑的。大多数专业程序员都不会写作 代码中面向用户的字符串。相反,出于本地化的原因,它们将把这些字符串存储在资源(.resw、.resx或.xlf)中。所以这里的字符串插值似乎没有多大用处


插值字符串无法从其(变量)范围中重构,因为使用了其中的嵌入变量

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

string.Format(“带placeholers的文本”,参数)

或者一些高级库(插值运行时),但使用相同的概念(传递参数)


然后,您可以将
“literal with placeholers”
重构为一个资源。

正如前面的回答中所述:您当前无法在运行时(例如从资源文件)加载格式字符串进行字符串插值,因为它是在编译时使用的

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

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

字符串插值很难与本地化结合起来,因为编译器更喜欢将其转换为不支持本地化的
String.Format(…)
。然而,有一个技巧可以将本地化和字符串插值结合起来;这一点在本章末尾进行了描述

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

问题是,如果可能,编译器更喜欢调用
string.Format
,因此如果接受
FormattableString
Localized()
重载,它将无法与字符串插值一起工作,因为C#编译器会忽略它[因为存在接受普通字符串的重载]。实际上,比这更糟糕的是:编译器在调用扩展方法时也拒绝使用
FormattableString

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

static class Loca
{
    public static string lize(this FormattableString message)
        { return message.Format.Localized(message.GetArguments()); }
}
然后你可以这样使用它:

var txt = "Hello {name} on {day:yyyy-MM-dd}!".StringFormat(new Dictionary<string, object>
            {
                ["name"] = "Joe",
                ["day"] = DateTime.Now,
            });
public class Program
{
    public static void Main(string[] args)
    {
        Localize.UseResourceManager(Resources.ResourceManager);

        var name = "Dave";
        Console.WriteLine(Loca.lize($"Hello, {name}"));
    }
}
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
}
认识到编译器将
$“…”
字符串转换为旧式格式字符串是很重要的。因此,在本例中,
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);
结果显然是:

My name is John W Bloggs
现在更改文本分配以获取翻译:

var text = Translate($"My name is {name} {middlename} {surname}");
Translate
的实现方式如下:

var txt = "Hello {name} on {day:yyyy-MM-dd}!".StringFormat(new Dictionary<string, object>
            {
                ["name"] = "Joe",
                ["day"] = DateTime.Now,
            });
public class Program
{
    public static void Main(string[] args)
    {
        Localize.UseResourceManager(Resources.ResourceManager);

        var name = "Dave";
        Console.WriteLine(Loca.lize($"Hello, {name}"));
    }
}
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
实现;它将收到一个字符串,如
“我的名字是{0}{1}{2}”
,并且应该使用GetText或resources或类似工具来查找并返回合适的翻译,或者只返回原始参数来跳过翻译

您仍然需要为翻译人员记录参数数字的含义;原始代码字符串中使用的文本在运行时不存在

例如,如果在本例中,
GetTranslation
返回了
“{2}.{0}{2},{1}。不要磨损它。”
(嘿,本地化不仅仅是语言!),那么整个程序的输出将是:

Bloggs.  John Bloggs, W.  Don't wear it out.

话虽如此,虽然使用这种风格的翻译很容易开发,但实际上很难进行翻译,因为字符串隐藏在代码中,在运行时只会浮出水面。除非你有一个可以
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}.";
}