在.Net(语法糖)中添加复数forms的聪明方法

我想能够input如下所示的内容:

Console.WriteLine("You have {0:life/lives} left.", player.Lives); 

代替

 Console.WriteLine("You have {0} {1} left.", player.Lives, player.Lives == 1 ? "life" : "lives"); 

所以对于player.Lives == 1 ,输出将是: You have 1 life left.
对于player.Lives != 1You have 5 lives left.

要么

 Console.WriteLine("{0:day[s]} till doomsday.", tillDoomsdayTimeSpan); 

有些系统具有内置的。 我可以接近C#中的那个符号吗?

编辑:是的,我正在寻找语法糖,而不是一个方法来确定什么单数/复数forms。

您可以创build一个自定义的格式化程序,

 public class PluralFormatProvider : IFormatProvider, ICustomFormatter { public object GetFormat(Type formatType) { return this; } public string Format(string format, object arg, IFormatProvider formatProvider) { string[] forms = format.Split(';'); int value = (int)arg; int form = value == 1 ? 0 : 1; return value.ToString() + " " + forms[form]; } } 

Console.WriteLine方法没有使用自定义格式化程序的重载,因此您必须使用String.Format

 Console.WriteLine(String.Format( new PluralFormatProvider(), "You have {0:life;lives} left, {1:apple;apples} and {2:eye;eyes}.", 1, 0, 2) ); 

输出:

 You have 1 life left, 0 apples and 2 eyes. 

注意:这是使格式器工作的最低限度,所以它不处理任何其他格式或数据types。 理想情况下,它会检测格式和数据types,如果string中有其他格式或数据types,则将格式传递给默认格式化程序。

您可以检出属于.NET 4.0框架的PluralizationService类:

 string lives = "life"; if (player.Lives != 1) { lives = PluralizationService .CreateService(new CultureInfo("en-US")) .Pluralize(lives); } Console.WriteLine("You have {0} {1} left", player.Lives, lives); 

值得注意的是,目前只有英文是支持的。 警告,这不适用于Net Framework 4.0 客户端configuration文件

你也可以写一个扩展方法:

 public static string Pluralize(this string value, int count) { if (count == 1) { return value; } return PluralizationService .CreateService(new CultureInfo("en-US")) .Pluralize(value); } 

接着:

 Console.WriteLine( "You have {0} {1} left", player.Lives, "life".Pluralize(player.Lives) ); 

使用@Darin Dimitrov解决scheme,我会创build一个扩展string….

 public static Extentions { public static string Pluralize(this string str,int n) { if ( n != 1 ) return PluralizationService.CreateService(new CultureInfo("en-US")) .Pluralize(str); return str; } } string.format("you have {0} {1} remaining",liveCount,"life".Pluralize()); 
 string message = string.format("You have {0} left.", player.Lives == 1 ? "life" : "lives"); 

当然这假定你有一个有限数量的值来复数化。

我写了一个名为SmartFormat的开源库,完全是这样! 它用C#编写,位于GitHub上: http : //github.com/scottrippey/SmartFormat

虽然它支持多种语言,但英文“复数规则”是默认的。 这里是语法:

 var output = Smart.Format("You have {0} {0:life:lives} left.", player.Lives); 

它也支持“零”数量和嵌套的占位符,所以你可以这样做:

 var output = Smart.Format("You have {0:no lives:1 life:{0} lives} left.", player.Lives); 

请参阅Castle ActiveRecord的一部分的Inflector类。 它是根据Apache许可证授权的。

它有一套正则expression式规则来定义单词如何复数化。 我使用的版本在这些规则中有一些错误,例如它有一个“病毒”→“virii”规则。

我有三个扩展方法包装了Inflector,其中第一个可能就在你的街道上:

  /// <summary> /// Pluralises the singular form word specified. /// </summary> /// <param name="this">The singular form.</param> /// <param name="count">The count.</param> /// <returns>The word, pluralised if necessary.</returns> public static string Pluralise(this string @this, long count) { return (count == 1) ? @this : Pluralise(@this); } /// <summary> /// Pluralises the singular form word specified. /// </summary> /// <param name="this">The singular form word.</param> /// <returns>The plural form.</returns> public static string Pluralise(this string @this) { return Inflector.Pluralize(@this); } /// <summary> /// Singularises the plural form word. /// </summary> /// <param name="this">The plural form word.</param> /// <returns>Th singular form.</returns> public static string Singularise(this string @this) { return Inflector.Singularize(@this); } 

用新颖的内插string,我只是使用这样的东西:

 // n is the number of connection attempts Console.WriteLine($"Needed {n} attempt{(n!=1 ? "s" : "")} to connect..."); 

我想最简单的方法是创build一个接口IPlural有一个方法.ToString(int quantity) ,它返回单数forms时, quantity == 1一个复数forms所有其他时间。

我用PluralizationService做了一些工作,然后想出来了。 我只是使PluralizationService static性能和组合所有。

参考System.Data.Entity.Design

  using System.Data.Entity.Design.PluralizationServices; using System.Reflection; public static class Strings { private static PluralizationService pluralizationService = PluralizationService.CreateService(System.Globalization.CultureInfo.CurrentUICulture); public static string Pluralize(this MemberInfo memberInfo)//types, propertyinfos, ect { return Pluralize(memberInfo.Name.StripEnd()); } public static string Pluralize(this string name) { return pluralizationService.Pluralize(name); // remove EF type suffix, if any } public static string StripEnd(this string name) { return name.Split('_')[0]; } } 

对于C#6.0以后,您可以使用插值string来做这个技巧。

例:

  Console.WriteLine("\n --- For REGULAR NOUNS --- \n"); { int count1 = 1; Console.WriteLine($"I have {count1} apple{(count1 == 1 ? "" : "s")}."); int count2 = 5; Console.WriteLine($"I have {count2} apple{(count2 == 1 ? "" : "s")}."); } Console.WriteLine("\n --- For IRREGULAR NOUNS --- \n"); { int count1 = 1; Console.WriteLine($"He has {count1} {(count1 == 1 ? "leaf" : "leaves")}."); int count2 = 5; Console.WriteLine($"He has {count2} {(count2 == 1 ? "leaf" : "leaves")}."); } 

输出:

  --- For REGULAR NOUNS --- I have 1 apple. I have 5 apples. --- For IRREGULAR NOUNS --- He has 1 leaf. He has 5 leaves. 

你可以玩我的.NET小提琴 。
有关更多详细信息,请转到插值string文档 。

晚会有点晚,但是我写了一个名为MessageFormat.NET的库来处理这个问题。

 var str = @"You have {lives, plural, zero {no lives} one {one life} other {# lives} } left."; var result = MessageFormatter.Format(str, new { lives = 1337 }); 

围绕文本的string中的空格不是必需的,而仅仅是为了便于阅读。

在翻译时这很好,因为语言在复数化时有不同的规则。

看看通常写入的string是如何解释同时存在的单个值和多个值的,例如

“预期的{0}个文件,但find{1}个文件”。

我采取的方法是保持相同的string,但添加一些parsing,以确定是否应该完全删除,或保留在圆括号内的s 。 对于不规则的词,斜线可以分开单数和复数forms。

其他例子:

  • “{0:n2} child(ren)”。Pluralize(1.0)=>“有1.00的孩子”
  • “有樱桃”(2)=>“有2个樱桃”
  • “有{小} /小牛”.Pluralize(1)=>“有1个小牛”
  • “{0}儿子在法律中”。普遍化(2)=>“有两个女婿”
  • “有能力的海员/海员”。普遍化(1)=>“有一个能干的海员”
  • “有{0}只羊,{1}只山羊”。复数(1,2)=>“有1只羊,2只山羊”
 ///<summary> /// Examples: /// "{0} file(s)".Pluralize(1); -> "1 file" /// "{0} file(s)".Pluralize(2); -> "2 files" ///</summary> public static String Pluralize(this String s, params Object[] counts) { String[] arr = s.Split(new [] { ' ' }, StringSplitOptions.None); for (int i = 0; i < arr.Length; i++) { String t = arr[i]; if (t.Length == 0 || t[0] != '{' || t[t.Length - 1] != '}') continue; int w = 1; while (w < t.Length) { char c = t[w]; if (c < '0' || c > '9') break; w++; } if (w == 1) continue; int n = int.Parse(t.Substring(1, w-1)); if (n >= counts.Length) continue; Object o = counts[n]; if (o == null) continue; bool isSingle = false; if (o is int) isSingle = 1 == (int) o; else if (o is double) isSingle = 1 == (double) o; else if (o is float) isSingle = 1 == (float) o; else if (o is decimal) isSingle = 1 == (decimal) o; else if (o is byte) isSingle = 1 == (byte) o; else if (o is sbyte) isSingle = 1 == (sbyte) o; else if (o is short) isSingle = 1 == (short) o; else if (o is ushort) isSingle = 1 == (ushort) o; else if (o is uint) isSingle = 1 == (uint) o; else if (o is long) isSingle = 1 == (long) o; else if (o is ulong) isSingle = 1 == (ulong) o; else continue; for (int j = i + 1; j < arr.Length && j < i + 4; j++) { String u = arr[j]; if (u.IndexOf('{') >= 0) break; // couldn't find plural word and ran into next token int b1 = u.IndexOf('('); int b2 = u.IndexOf(')', b1 + 1); if (b1 >= 0 && b2 >= 0) { String u1 = u.Substring(0, b1); String u2 = u.Substring(b2+1); char last = (u1.Length > 0 ? u1[u1.Length - 1] : ' '); String v = (isSingle ? "" : u.Substring(b1+1, (b2 - b1) - 1)); if ((last == 'y' || last == 'Y') && String.Compare(v, "ies", true) == 0) u1 = u1.TrimEnd('y', 'Y'); arr[j] = u1 + v + u2; break; } int s1 = u.IndexOf('/'); if (s1 >= 0) { arr[j] = (isSingle ? u.Substring(0, s1) : u.Substring(s1 + 1)); break; } } } s = String.Join(" ", arr); s = String.Format(s, counts); return s; } 

我在.NET 4.6中使用这种扩展方法

 public static string Pluralize(this string @string) { if (string.IsNullOrEmpty(@string)) return string.Empty; var service = new EnglishPluralizationService(); return service.Pluralize(@string); } 

如果你的申请是英文的,你可以使用这里显示的所有解决scheme。 但是,如果您打算对应用程序进行本地化,则必须以适当的方式完成复数启用的消息。 这意味着您可能需要多种模式(从1到6),具体取决于语言和规则,以select使用的模式取决于语言。

例如在英语中,你会有两种模式

“你有{0}现场离开”“你有{0}剩下的生活”

然后你将有一个Format函数,在这里你用liveAmountvariables传递这两个模式。

 Format("You have {0} live left", "You have {0} lives left", liveAmount); 

在一个真正的应用程序中,你不会硬编码string,但会使用资源string。

格式将知道活动语言是什么,如果使用英语

 if (count == 1) useSingularPattern else usePluralPattern 

要实现这一点你自己有点复杂,你不需要。 你可以为我做一个开源项目

https://github.com/jaska45/I18N

通过使用它,你可以很容易地获得string

 var str = MultiPattern.Format("one;You have {0} live left;other;You have {0} lives left", liveAmount); 

而已。 根据传递的liveAmount参数,库知道使用什么样的模式。 规则已经从CLDR中提取到库.cs文件中。

如果您想本地化应用程序,只需将多模式string放入.resx文件中,然后让翻译工具翻译它。 根据目标语言,多模式string可能包含1,2,3,4,5或6个模式。