I want to be able to type something like:
Console.WriteLine(\"You have {0:life/lives} left.\", player.Lives);
instead of
Consol
For C# 6.0 onwards, you can use Interpolated Strings to do this tricks.
Example:
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")}.");
}
Output:
--- For REGULAR NOUNS ---
I have 1 apple.
I have 5 apples.
--- For IRREGULAR NOUNS ---
He has 1 leaf.
He has 5 leaves.
You can play around on my .NET Fiddle.
For more details, go to Interpolated String documentation.
Looking at how strings are typically written that account for simultaneous single and multiple values, e.g.
"Expected {0} file(s), but found {1} file(s)."
The approach I took was to keep the same string, but add some parsing to determine whether the (s)
should be removed completely, or to keep the s
inside the round brackets. For irregular words, a slash can separate the singular and plural form.
Other examples:
- "Had {0:n2} child(ren)".Pluralize(1.0) => "Had 1.00 child"
- "Had {0} cherry(ies)".Pluralize(2) => "Had 2 cherries"
- "Had {0} calf/calves".Pluralize(1) => "Had 1 calf"
- "Had {0} son(s)-in-law".Pluralize(2) => "Had 2 sons-in-law"
- "Had {0} able seaman/seamen".Pluralize(1) => "Had 1 able seaman"
- "Had {0} sheep, {1} goat(s)".Pluralize(1, 2) => "Had 1 sheep, 2 goats"
///<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;
}
string message = string.format("You have {0} left.", player.Lives == 1 ? "life" : "lives");
Of course this assumes that you have a finite number of values to pluralize.
You may checkout the PluralizationService class which is part of the .NET 4.0 framework:
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);
It is worth noting that only English is supported for the moment. Warning, this don't work on the Net Framework 4.0 Client Profile!
You could also write an extension method:
public static string Pluralize(this string value, int count)
{
if (count == 1)
{
return value;
}
return PluralizationService
.CreateService(new CultureInfo("en-US"))
.Pluralize(value);
}
And then:
Console.WriteLine(
"You have {0} {1} left", player.Lives, "life".Pluralize(player.Lives)
);
I did a little bit of work with PluralizationService
and came up with. I just made the PluralizationService
static
for performance and combined all.
Reference 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];
}
}
I am using this extension method with .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);
}