Умный способ добавить 's' для множественной формы в .Net(синтаксический сахар)
Я хочу, чтобы набрать что-то вроде:
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 != 1
: You have 5 lives left.
или
Console.WriteLine("{0:day[s]} till doomsday.", tillDoomsdayTimeSpan);
В некоторых системах есть встроенный. Как близко я могу перейти к этой нотации в С#?
EDIT: Да, я специально ищу синтаксический сахар, а не метод определения того, какие единственные/множественные формы.
Ответы
Ответ 1
Вы можете создать собственный форматировщик, который делает это:
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.
Примечание. Это минимальный размер, чтобы заставить работу форматирования работать, поэтому он не обрабатывает другие форматы или типы данных. В идеале он будет определять формат и тип данных и передавать форматирование по умолчанию, если в строке есть другое форматирование или типы данных.
Ответ 2
Вы можете проверить класс PluralizationService, который является частью платформы .NET 4.0:
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 Профиль клиента!
Вы также можете написать метод расширения:
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)
);
Ответ 3
используя решение @Darin Dimitrov, я бы создал расширение для строки....
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());
Ответ 4
string message = string.format("You have {0} left.", player.Lives == 1 ? "life" : "lives");
Конечно, это предполагает, что у вас есть конечное число значений для плюрализации.
Ответ 5
Я написал библиотеку с открытым исходным кодом под названием SmartFormat, которая делает именно это! Он написан на С# и находится на 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);
Ответ 6
См. класс Inflector
, который является частью Замок ActiveRecord. Он лицензируется по лицензии Apache.
Он содержит набор правил регулярного выражения, которые определяют, как слова плюрализованы. Версия, которую я использовал, имеет некоторые ошибки в этих правилах, хотя, например, он имеет правило "вирус" → "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);
}
Ответ 7
С новомодными интерполированными строками я просто использую что-то вроде этого:
// n is the number of connection attempts
Console.WriteLine($"Needed {n} attempt{(n!=1 ? "s" : "")} to connect...");
РЕДАКТИРОВАТЬ: просто наткнулся на этот ответ снова - с тех пор, как я первоначально опубликовал это, я использовал метод расширения, который делает его еще проще. Этот пример упорядочен по особенностям:
static class Extensions {
/// <summary>
/// Pluralize: takes a word, inserts a number in front, and makes the word plural if the number is not exactly 1.
/// </summary>
/// <example>"{n.Pluralize("maid")} a-milking</example>
/// <param name="word">The word to make plural</param>
/// <param name="number">The number of objects</param>
/// <param name="pluralSuffix">An optional suffix; "s" is the default.</param>
/// <param name="singularSuffix">An optional suffix if the count is 1; "" is the default.</param>
/// <returns>Formatted string: "number word[suffix]", pluralSuffix (default "s") only added if the number is not 1, otherwise singularSuffix (default "") added</returns>
internal static string Pluralize(this int number, string word, string pluralSuffix = "s", string singularSuffix = "")
{
return [email protected]"{number} {word}{(number != 1 ? pluralSuffix : singularSuffix)}";
}
}
void Main()
{
int lords = 0;
int partridges = 1;
int geese = 1;
int ladies = 8;
Console.WriteLine([email protected]"Have {lords.Pluralize("lord")}, {partridges.Pluralize("partridge")}, {ladies.Pluralize("lad", "ies", "y")}, and {geese.Pluralize("", "geese", "goose")}");
lords = 1;
partridges = 2;
geese = 6;
ladies = 1;
Console.WriteLine([email protected]"Have {lords.Pluralize("lord")}, {partridges.Pluralize("partridge")}, {ladies.Pluralize("lad", "ies", "y")}, and {geese.Pluralize("", "geese", "goose")}");
}
(форматы одинаковые). Выход:
Have 0 lords, 1 partridge, 8 ladies, and 1 goose
Have 1 lord, 2 partridges, 1 lady, and 6 geese
Ответ 8
Я думаю, что самый простой способ сделать это - создать интерфейс IPlural
, у которого есть метод .ToString(int quantity)
, который возвращает единственную форму, когда quantity == 1
- множественная форма все остальные времена.
Ответ 9
Я немного поработал с 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];
}
}
Ответ 10
Для С# 6.0 и далее вы можете использовать Interpolated Strings для выполнения этих трюков.
Пример:
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 Fiddle.
Для получения дополнительной информации перейдите в документация с интерполированной строкой.
Ответ 11
Если ваше приложение является английским, вы можете использовать все приведенные здесь решения. Однако, если вы планируете локализовать приложение, сообщение с множественным включением должно быть выполнено надлежащим образом. Это означает, что вам может потребоваться несколько шаблонов (от 1 до 6) в зависимости от языка и правила, чтобы выбрать, какой шаблон используется, зависит от языка.
Например, на английском языке у вас будет два шаблона
"У вас {0} осталось влево"
"У вас осталось {0} жизнь"
Тогда у вас будет функция Format, в которой вы передадите эти два шаблона с переменной liveAmount.
Format("You have {0} live left", "You have {0} lives left", liveAmount);
В реальном приложении вы не будете жестко закодировать строку, но будете использовать строки ресурсов.
Формат будет знать, что такое активный язык, и если английский будет использовать
if (count == 1)
useSingularPattern
else
usePluralPattern
Чтобы реализовать это, вы немного сложны, вам это не нужно. Вы можете создать проект с открытым исходным кодом, который я сделал
https://github.com/jaska45/I18N
Используя его, вы можете легко получить строку
var str = MultiPattern.Format("one;You have {0} live left;other;You have {0} lives left", liveAmount);
Что это. Библиотека знает, какой шаблон использовать в зависимости от переданного параметра liveAmount. Правила были извлечены из CLDR в файл библиотеки .cs.
Если вы хотите локализовать приложение, вы просто поместите строку с несколькими шаблонами в .resx и дайте переводчику ее перевести. В зависимости от целевого языка строка с несколькими шаблонами может содержать 1, 2, 3, 4, 5 или 6 паттернов.
Ответ 12
Немного поздно для вечеринки, но Я написал библиотеку с именем 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
});
Пробел в строке, окружающей текст, не требуется, а просто для удобочитаемости.
Это отлично при переводе, поскольку языки имеют разные правила, когда речь идет о плюрализации.
Ответ 13
Посмотрите, как обычно записываются строки, которые учитывают одновременные одно и несколько значений, например
"Ожидаемый файл {0}, но найден {1} файл (ы)."
Подход, который я взял, состоял в том, чтобы сохранить одну и ту же строку, но добавьте некоторый синтаксический анализ, чтобы определить, нужно ли полностью удалять (s)
или сохранить s
внутри круглых скобок. Для нерегулярных слов косая черта может разделять единственную и множественную форму.
Другие примеры:
- "Had {0: n2} child (ren)". Pluralize (1.0) = > "Имел 1,00 ребенка"
- "Если бы {0} вишня (ы)". Плюрализуйте (2) = > "Если бы две вишни"
- "Если бы {0} теленок/телята".Pluralize(1) = > "Было 1 теленок"
- "Имел {0} сын -в-закон". Плурализовать (2) = > "Имел 2 зятья"
- "Если бы {0} смог моряк/моряк". Плурализовать (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;
}
Ответ 14
Я использую этот метод расширения с .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);
}