c# - reglas - singular y plural en ingles
Una forma inteligente de agregar ''s'' para la forma plural en.Net(azúcar sintáctica) (14)
Al observar cómo se escriben normalmente las cadenas, se tienen en cuenta para valores únicos y múltiples simultáneos, por ejemplo,
"Se esperaba {0} archivo (s), pero se encontraron {1} archivo (s)".
El enfoque que tomé fue mantener la misma cadena, pero agregar algunos análisis para determinar si las (s)
deben eliminarse por completo, o para mantener las s
dentro de los soportes redondos. Para palabras irregulares, una barra puede separar la forma singular y plural.
Otros ejemplos:
- "Tenía {0: n2} hijo (s)". Pluralize (1.0) => "Tenía 1.00 hijo"
- "Tenía {0} cereza (es)". Pluralizar (2) => "Tenía 2 cerezas"
- "Tenía {0} becerro / becerros" .Pluralizar (1) => "Tenía 1 becerro"
- "Tenía {0} hijo (s)-en-ley". Pluralizar (2) => "Tenía 2 yernos"
- "Tenía {0} marinero capaz / marineros" .Pluralizar (1) => "Tenía 1 marinero capaz"
- "Tenía {0} ovejas, {1} cabra (s)". Pluralizar (1, 2) => "Tenía 1 oveja, 2 cabras"
///<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;
}
Quiero poder escribir algo como:
Console.WriteLine("You have {0:life/lives} left.", player.Lives);
en lugar de
Console.WriteLine("You have {0} {1} left.", player.Lives, player.Lives == 1 ? "life" : "lives");
de modo que para player.Lives == 1
la salida sería: You have 1 life left.
para el player.Lives != 1
: You have 5 lives left.
o
Console.WriteLine("{0:day[s]} till doomsday.", tillDoomsdayTimeSpan);
Algunos sistemas tienen eso incorporado. ¿Qué tan cerca puedo llegar a esa notación en C #?
EDITAR: Sí, estoy buscando específicamente azúcar sintáctica, y no un método para determinar qué son las formas singular / plural.
Con las nuevas cadenas interpoladas, solo uso algo como esto:
// n is the number of connection attempts
Console.WriteLine($"Needed {n} attempt{(n!=1 ? "s" : "")} to connect...");
Consulte la clase Inflector
que forma parte de Castle ActiveRecord . Está licenciado bajo la licencia Apache.
Tiene un conjunto de reglas de expresión regular que definen cómo se pluralizan las palabras. Sin embargo, la versión que he usado tiene algunos errores en estas reglas, por ejemplo, tiene una regla ''virus'' → ''virii''.
Tengo tres métodos de extensión que envuelven Inflector, el primero de los cuales puede ser justo en su calle:
/// <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);
}
Escribí una biblioteca de código abierto llamada SmartFormat que hace exactamente eso. Está escrito en C # y está en GitHub: http://github.com/scottrippey/SmartFormat
Aunque es compatible con varios idiomas, las "reglas plurales" en inglés son las predeterminadas. Aquí está la sintaxis:
var output = Smart.Format("You have {0} {0:life:lives} left.", player.Lives);
También admite la cantidad "cero" y los marcadores de posición anidados, por lo que podría hacer:
var output = Smart.Format("You have {0:no lives:1 life:{0} lives} left.", player.Lives);
Estoy usando este método de extensión con .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);
}
Para C # 6.0 en adelante, puedes usar cadenas interpoladas para hacer estos trucos.
Ejemplo:
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")}.");
}
Salida:
--- For REGULAR NOUNS ---
I have 1 apple.
I have 5 apples.
--- For IRREGULAR NOUNS ---
He has 1 leaf.
He has 5 leaves.
Puedes jugar en mi .NET Fiddle .
Para más detalles, vaya a la documentación de la cadena interpolada .
Pienso que la forma más fácil de hacerlo es crear una interfaz IPlural
que tenga un método .ToString(int quantity)
que devuelva la forma singular cuando quantity == 1
y la forma plural todas las otras veces.
Puede PluralizationService clase PluralizationService que forma parte del marco .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);
Vale la pena señalar que solo el inglés es compatible por el momento. ¡Advertencia, esto no funciona en el perfil de cliente de Net Framework 4.0!
También puedes escribir un método de extensión:
public static string Pluralize(this string value, int count)
{
if (count == 1)
{
return value;
}
return PluralizationService
.CreateService(new CultureInfo("en-US"))
.Pluralize(value);
}
Y entonces:
Console.WriteLine(
"You have {0} {1} left", player.Lives, "life".Pluralize(player.Lives)
);
Puedes crear un formateador personalizado que hace eso:
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];
}
}
El método Console.WriteLine
no tiene una sobrecarga que tome un formateador personalizado, por lo que tiene que usar String.Format
:
Console.WriteLine(String.Format(
new PluralFormatProvider(),
"You have {0:life;lives} left, {1:apple;apples} and {2:eye;eyes}.",
1, 0, 2)
);
Salida:
You have 1 life left, 0 apples and 2 eyes.
Nota: Esto es lo mínimo para hacer que un formateador funcione, por lo que no maneja ningún otro formato o tipo de datos. Lo ideal sería detectar el formato y el tipo de datos, y pasar el formato a un formateador predeterminado si hay algún otro formato o tipo de datos en la cadena.
Si su aplicación es en inglés, puede usar todas las soluciones que se muestran aquí. Sin embargo, si planea localizar la aplicación, el mensaje plural habilitado debe hacerse de manera adecuada. Esto significa que es posible que necesite varios patrones (de 1 a 6) según el idioma y la regla para elegir qué patrón se utiliza, según el idioma.
Por ejemplo en inglés tendrías dos patrones.
"Tienes {0} vivo a la izquierda" "Tienes {0} vidas a la izquierda"
Entonces tendrías una función de Formato donde pasas estos dos patrones con la variable liveAmount.
Format("You have {0} live left", "You have {0} lives left", liveAmount);
En una aplicación real, no codificaría la cadena de forma rígida, sino que usaría cadenas de recursos.
El formato sabría cuál es el idioma activo y si se utilizara el inglés
if (count == 1)
useSingularPattern
else
usePluralPattern
Para implementar esto usted mismo es un poco complejo pero no tiene que hacerlo. Usted puede un proyecto de código abierto que he hecho
https://github.com/jaska45/I18N
Al usarlo puedes obtener fácilmente la cadena
var str = MultiPattern.Format("one;You have {0} live left;other;You have {0} lives left", liveAmount);
Eso es. La biblioteca sabe qué patrón usar dependiendo del parámetro liveAmount pasado. Las reglas se han extraído de CLDR en el archivo .cs de la biblioteca.
Si desea localizar la aplicación, simplemente coloque la cadena de patrones múltiples en .resx y deje que el traductor la traduzca. Dependiendo del idioma de destino, la cadena de patrones múltiples puede contener 1, 2, 3, 4, 5 o 6 patrones.
Trabajé un poco con PluralizationService
y se me ocurrió. Acabo de hacer el PluralizationService
static
para el rendimiento y combiné todo.
Referencia 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];
}
}
Un poco tarde para la fiesta, pero escribí una biblioteca llamada MessageFormat.NET que maneja esto.
var str = @"You have {lives, plural,
zero {no lives}
one {one life}
other {# lives}
} left.";
var result = MessageFormatter.Format(str, new {
lives = 1337
});
El espacio en blanco en la cadena que rodea el texto no es obligatorio, sino meramente legible.
Esto es genial cuando se traduce, ya que los idiomas tienen diferentes reglas cuando se trata de la pluralización.
Usando la solución @Darin Dimitrov, crearía una extensión para la cadena ....
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());
string message = string.format("You have {0} left.", player.Lives == 1 ? "life" : "lives");
Por supuesto, esto supone que tiene un número finito de valores para pluralizar.