I want to be able to type something like:
Console.WriteLine(\"You have {0:life/lives} left.\", player.Lives);
instead of
Consol
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"
///
/// Examples:
/// "{0} file(s)".Pluralize(1); -> "1 file"
/// "{0} file(s)".Pluralize(2); -> "2 files"
///
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;
}