Подтвердить что ты не робот

Умный способ добавить '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: Да, я специально ищу синтаксический сахар, а не метод определения того, какие единственные/множественные формы.

4b9b3361

Ответ 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);
}