web-dev-qa-db-fra.com

Méthode intelligente pour ajouter au pluriel en .Net (sucre syntaxique)

Je veux être capable de taper quelque chose comme:

Console.WriteLine("You have {0:life/lives} left.", player.Lives);

au lieu de

Console.WriteLine("You have {0} {1} left.", player.Lives, player.Lives == 1 ? "life" : "lives");

de sorte que pour player.Lives == 1 la sortie serait: You have 1 life left.
pour player.Lives != 1: You have 5 lives left.

ou

Console.WriteLine("{0:day[s]} till doomsday.", tillDoomsdayTimeSpan);

Certains systèmes ont cela intégré. À quel point puis-je me rapprocher de cette notation en C #?

EDIT: Oui, je recherche spécifiquement du sucre syntaxique, et non une méthode permettant de déterminer ce que sont les formes singulier/pluriel.

45
THX-1138

Vous pouvez créer un formateur personnalisé qui fait cela:

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];
  }

}

La méthode Console.WriteLine n'a pas de surcharge qui nécessite un formateur personnalisé, vous devez donc utiliser String.Format:

Console.WriteLine(String.Format(
  new PluralFormatProvider(),
  "You have {0:life;lives} left, {1:Apple;apples} and {2:eye;eyes}.",
  1, 0, 2)
);

Sortie:

You have 1 life left, 0 apples and 2 eyes.

Remarque: il s'agit du minimum nécessaire pour faire fonctionner un formateur. Il ne gère donc aucun autre format ou type de données. Idéalement, il devrait détecter le format et le type de données et transmettre le formatage à un formateur par défaut s'il existe d'autres types de formatage ou de données dans la chaîne.

36
Guffa

Vous pouvez accéder à la classe PluralizationService qui fait partie du framework .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);

Il est à noter que seul l'anglais est pris en charge pour le moment. Attention, cela ne fonctionne pas sur Net Framework 4.0 Profil client !

Vous pouvez aussi écrire une méthode d'extension:

public static string Pluralize(this string value, int count)
{
    if (count == 1)
    {
        return value;
    }
    return PluralizationService
        .CreateService(new CultureInfo("en-US"))
        .Pluralize(value);
}

Et alors:

Console.WriteLine(
    "You have {0} {1} left", player.Lives, "life".Pluralize(player.Lives)
);
72
Darin Dimitrov

en utilisant la solution @Darin Dimitrov, je créerais une extension pour string ....

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());
6
Muad'Dib
string message = string.format("You have {0} left.", player.Lives == 1 ? "life" : "lives");

Bien entendu, cela suppose que vous ayez un nombre fini de valeurs à pluraliser.

5
Tim Medora

J'ai écrit une bibliothèque open-source appelée SmartFormat qui fait exactement cela! Il est écrit en C # et est sur GitHub: http://github.com/scottrippey/SmartFormat

Bien qu'il prenne en charge plusieurs langues, les "règles du pluriel" en anglais sont les valeurs par défaut. Voici la syntaxe:

var output = Smart.Format("You have {0} {0:life:lives} left.", player.Lives);

Il prend également en charge la quantité "zéro" et les espaces réservés imbriqués. Vous pouvez ainsi:

var output = Smart.Format("You have {0:no lives:1 life:{0} lives} left.", player.Lives);
5
Scott Rippey

Voir la classe Inflector qui fait partie de Castle ActiveRecord . Il est sous licence Apache.

Il contient un ensemble de règles d’expression régulière qui définissent la pluralité des mots. La version que j'ai utilisée comporte quelques erreurs dans ces règles, par exemple. il a une règle 'virus' → 'virii'.

J'ai trois méthodes d'extension qui englobent Inflector, la première pouvant être directement dans votre rue:

    /// <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);
    }
4
Paul Ruane

Avec les nouvelles chaînes interpolées, j'utilise quelque chose comme ceci:

// n is the number of connection attempts
Console.WriteLine($"Needed {n} attempt{(n!=1 ? "s" : "")} to connect...");

EDIT: je viens juste de retrouver cette réponse - depuis que j’ai posté cette annonce, j’utilise une méthode d’extension qui la rend encore plus facile. Cet exemple est ordonné par particularité:

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

(les formats sont les mêmes). La sortie est:

Have 0 lords, 1 partridge, 8 ladies, and 1 goose
Have 1 lord, 2 partridges, 1 lady, and 6 geese
3
UweBaemayr

Je pense que le moyen le plus simple de le faire est de créer une interface IPlural qui a une méthode .ToString(int quantity) qui renvoie la forme singulière lorsque quantity == 1 est la forme plurielle toutes les autres fois.

1
Brad

À partir de C # 6.0, vous pouvez utiliser des chaînes interpolées pour effectuer ces astuces.

Exemple:

    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")}.");
    }

Sortie:

 --- For REGULAR NOUNS --- 

I have 1 Apple.
I have 5 apples.

 --- For IRREGULAR NOUNS --- 

He has 1 leaf.
He has 5 leaves.

Vous pouvez jouer sur mon .NET Fiddle .
Pour plus de détails, allez à Documentation de Interpolated String .

1
sky91

J'ai fait un peu de travail avec PluralizationService et proposé. Je viens de faire la PluralizationServicestatic pour la performance et tout combiné.

Référence 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];
    }
  }
1
toddmo

Si votre application est en anglais, vous pouvez utiliser toutes les solutions présentées ici. Toutefois, si vous envisagez de localiser l'application, le message activé au pluriel doit être envoyé correctement. Cela signifie que vous pouvez avoir besoin de plusieurs modèles (de 1 à 6) en fonction de la langue et que la règle pour choisir le type de modèle utilisé dépend de la langue.

Par exemple, en anglais, vous auriez deux modèles

"Il vous reste {0} vivre" "Il vous reste {0} vies"

Ensuite, vous auriez une fonction Format où vous passerez ces deux modèles avec la variable liveAmount.

Format("You have {0} live left", "You have {0} lives left", liveAmount);

Dans une application réelle, vous ne coderiez pas la chaîne en dur mais utiliseriez des chaînes de ressources.

Format saurait quelle est la langue active et si l'anglais serait utilisé

if (count == 1)
  useSingularPattern
else
  usePluralPattern

Implémenter cela vous-même est un peu complexe, mais vous n'avez pas à le faire. Vous pouvez vous un projet open source que j'ai fait

https://github.com/jaska45/I18N

En l'utilisant, vous pouvez facilement obtenir la chaîne

var str = MultiPattern.Format("one;You have {0} live left;other;You have {0} lives left", liveAmount);

C'est tout. La bibliothèque sait quel modèle utiliser en fonction du paramètre liveAmount passé. Les règles ont été extraites de CLDR dans le fichier .cs de la bibliothèque.

Si vous souhaitez localiser l'application, il vous suffit de placer la chaîne de modèles multiples en .resx et de laisser le traducteur le traduire. En fonction de la langue cible, la chaîne de modèles multiples peut contenir 1, 2, 3, 4, 5 ou 6 modèles.

1
Jaska

J'utilise cette méthode d'extension avec .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);
}
0
Patrick Michalina

En examinant la manière dont les chaînes sont généralement écrites pour prendre en compte des valeurs uniques et multiples simultanées, par ex.

"Fichier (s) attendu (s) {0}, mais fichier {1} trouvé (s)."

L’approche que j’ai choisie était de conserver la même chaîne, mais d’ajouter une analyse syntaxique pour déterminer si le (s) devait être complètement supprimé ou conserver la s entre les parenthèses. Pour les mots irréguliers, une barre oblique peut séparer les formes singulière et plurielle.

Autres exemples:

  • "Avait {0: n2} enfant (s)". Pluralize (1.0) => "Avait 1.00 enfant"
  • "Avait {0} cerises (s)". Pluralize (2) => "Avait 2 cerises"
  • "Avait {0} veau/veaux" .Pluralize (1) => "Avait 1 veau"
  • "Avait {0} fils (s) -ous-loi" .Pluralize (2) => "Avait 2 beaux-fils"
  • "Avait {0} un matelot qualifié/matelots" .Pluralize (1) => "Un seul matelot capable"
  • "Avait {0} moutons, {1} chèvre (s)". Pluralize (1, 2) => "Avait 1 mouton, 2 chèvres"
///<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;
}
0
Loathing

Un peu tard pour la fête, mais J'ai écrit une bibliothèque appelée MessageFormat.NET qui gère cela.

var str = @"You have {lives, plural, 
                     zero {no lives} 
                      one {one life} 
                    other {# lives}
            } left.";
var result = MessageFormatter.Format(str, new {
    lives = 1337
});

Les espaces dans la chaîne entourant le texte ne sont pas obligatoires, mais uniquement pour des raisons de lisibilité.

C’est formidable lors de la traduction, car les langues ont des règles différentes en matière de pluralisation.

0
Jeff