Умный способ добавить 's' для формы множественного числа в .Net (синтаксический сахар) - PullRequest
45 голосов
/ 06 октября 2010

Я хочу иметь возможность напечатать что-то вроде:

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

Некоторые системы имеют такую ​​встроенную функцию. Как близко я могу добраться до этой записи в C #?

РЕДАКТИРОВАТЬ: Да, я специально ищу синтаксический сахар, а не метод, чтобы определить, что такое формы единственного / множественного числа.

Ответы [ 14 ]

73 голосов
/ 06 октября 2010

Вы можете получить класс 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)
);
36 голосов
/ 06 октября 2010

Вы можете создать собственный форматировщик, который делает это:

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.

Примечание. Это необходимый минимум для работы форматера, поэтому он не обрабатывает никакие другие форматы или типы данных. В идеале он должен определять формат и тип данных и передавать форматирование в форматировщик по умолчанию, если в строке есть другие типы форматирования или типы данных.

6 голосов
/ 06 октября 2010

используя решение @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());
5 голосов
/ 27 августа 2011

Я написал библиотеку с открытым исходным кодом под названием 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);
5 голосов
/ 06 октября 2010
string message = string.format("You have {0} left.", player.Lives == 1 ? "life" : "lives");

Конечно, это предполагает, что у вас есть конечное число значений для множественного числа.

4 голосов
/ 06 октября 2010

См. Класс Inflector, который является частью Castle ActiveRecord . Он лицензируется по лицензии Apache.

У него есть набор правил регулярных выражений, которые определяют, как слова используются во множественном числе. Версия, которую я использовал, имеет некоторые ошибки в этих правилах, например, у него есть правило «вирус» → «вирус».

У меня есть три метода расширения, которые обертывают 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);
    }
3 голосов
/ 02 декабря 2016

С новомодными интерполированными строками я просто использую что-то вроде этого:

// 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 $@"{number} {word}{(number != 1 ? pluralSuffix : singularSuffix)}";
    }
}

void Main()
{
    int lords = 0;
    int partridges = 1;
    int geese = 1;
    int ladies = 8;
    Console.WriteLine($@"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($@"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
1 голос
/ 07 ноября 2017

Если ваша заявка на английском языке, вы можете использовать все решения, показанные здесь.Однако, если вы планируете локализовать приложение, сообщение с поддержкой множественного числа должно быть сделано правильно.Это означает, что вам может потребоваться несколько шаблонов (от 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 шаблонов.

1 голос
/ 21 июня 2017

Для C # 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 .Для получения более подробной информации перейдите к документации Интерполированная строка .

1 голос
/ 21 мая 2016

Я немного поработал с 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];
    }
  }
...