c# - Pluralità nei messaggi degli utenti




string localization pluralize (21)

Molte volte, quando si generano messaggi da mostrare all'utente, il messaggio conterrà un numero di qualcosa di cui voglio informare il cliente.

Darò un esempio: il cliente ha selezionato un numero di articoli da 1 in su e ha fatto clic su delete. Ora voglio dare un messaggio di conferma al cliente, e voglio menzionare il numero di elementi che ha selezionato per minimizzare le possibilità che lui commetta un errore selezionando un gruppo di elementi e facendo clic su Elimina quando vuole solo cancellare uno di loro.

Un modo è rendere il messaggio generico in questo modo:

int noofitemsselected = SomeFunction();
string message = "You have selected " + noofitemsselected + " item(s). Are you sure you want to delete it/them?";

Il "problema" qui è il caso in cui noofitemselected è 1, e dobbiamo scrivere l' articolo e questo invece degli oggetti e loro .

La mia soluzione normale sarà qualcosa di simile

int noofitemsselected = SomeFunction();
string message = "You have selected " + noofitemsselected + " " + (noofitemsselected==1?"item" : "items") + ". Are you sure you want to delete " + (noofitemsselected==1?"it" : "them") + "?";

Questo diventa piuttosto lungo e abbastanza sgradevole molto velocemente se ci sono molti riferimenti al numero di numeri all'interno del codice, e il messaggio reale diventa difficile da leggere.

Quindi le mie domande sono semplicemente. Ci sono modi migliori per generare messaggi come questo?

MODIFICARE

Vedo che un sacco di persone si è appisolato nel caso in cui ho menzionato che il messaggio dovrebbe essere visualizzato all'interno di una finestra di messaggio, e ha semplicemente dato una risposta su come evitare di usare la finestra del messaggio, e questo è tutto ok .

Ma ricorda che il problema della pluralizzazione si applica anche ai testi in altri punti del programma oltre alle finestre di messaggio. Ad esempio, un'etichetta accanto a una griglia che mostra il numero di linee selezionate nella griglia avrà lo stesso problema per quanto riguarda la pluralizzazione.

Quindi questo si applica fondamentalmente alla maggior parte del testo che viene emesso in qualche modo dai programmi, e quindi la soluzione non è così semplice da cambiare semplicemente il programma per non produrre più testo :)


Answers

Vorrei andare con non codificare il messaggio, ma fornire due messaggi in un file di risorse separato. Piace

string DELETE_SINGLE = "You have selected {0} item. Are you sure you want to delete it?";
string DELETE_MULTI = "You have selected {0} items. Are you sure you want to delete them?";

e poi inserirli in String. Formattare come

if(noofitemsselected == 1)
    messageTemplate = MessageResources.DELETE_SINGLE;
else
    messageTemplate = MessageResources.DELETE_MULTI;

string message = String.Format(messageTemplate, noofitemsselected)

Penso che questo approccio sia più facile da localizzare e mantenere. Tutti i messaggi dell'interfaccia utente si troverebbero in un'unica posizione.


Puoi generare automaticamente il plurale, vedi ad es. generatore plurale .

Per le regole di generazione plurale vedi wikipedia

string msg = "Do you want to delete " + numItems + GetPlural(" item", numItems) + "?";

È possibile evitare completamente il problema formando il messaggio in modo diverso.

string message = "The number of selected items is " + noofitemsselected + ". Are you sure you want to delete everything in this selection?";

Per essere in grado di avere messaggi pluralizzati che sarà possibile localizzare correttamente, la mia opinione è che sarebbe saggio prima creare uno strato di riferimento indiretto tra il numero e un messaggio.

Ad esempio, utilizzare una costante di qualche tipo per specificare quale messaggio si desidera visualizzare. Scarica il messaggio utilizzando una funzione che nasconderà i dettagli di implementazione.

get_message(DELETE_WARNING, quantity)

Quindi, crea un dizionario che contenga i possibili messaggi e variazioni e che le variazioni sappiano quando devono essere utilizzati.

DELETE_WARNING = {
   1: 'Are you sure you want to delete %s item',
   >1: 'Are you sure you want to delete %s items'
   >5: 'My language has special plural above five, do you wish to delete it?'
}

Ora potresti semplicemente trovare la chiave che corrisponde alla quantity e interpolare il valore della quantity con quel messaggio.

Questo esempio semplicistico e ingenuo, ma in realtà non vedo nessun altro modo corretto per farlo ed essere in grado di fornire un buon supporto per L10N e I18N.


Internazionalizzazione

Presumo che tu voglia supporto per l'internazionalizzazione, nel qual caso linguaggi diversi hanno schemi diversi per i plurali (ad esempio una forma plurale speciale per 2 di qualcosa, o linguaggi più complicati come il polacco), e non puoi fare affidamento sull'applicazione di alcuni semplici schemi alla tua stringa per risolverlo.

È possibile utilizzare la funzione ngettext GNU Gettext e fornire due messaggi in inglese nel codice sorgente. Gettext fornirà l'infrastruttura per scegliere da altri (potenzialmente più) messaggi quando tradotti in altre lingue. Vedi http://www.gnu.org/software/hello/manual/gettext/Plural-forms.html per una descrizione completa del supporto plurale di GNU gettext.

GNU Gettext è sotto la LGPL. ngettext è denominato GettextResourceManager.GetPluralString nella porta C # di Gettext.

(Se non hai bisogno del supporto per la localizzazione e non vuoi usare subito Gettext, scrivi la tua funzione che lo fa per l'inglese e passa due messaggi completi , in questo modo se hai bisogno di l10n più tardi, puoi aggiungi riscrivendo una singola funzione.)


Dovrai tradurre la funzione qui sotto da VBA a C #, ma il tuo utilizzo cambierà in:

int noofitemsselected = SomeFunction();
string message = Pluralize("You have selected # item[s]. Are you sure you want to delete [it/them]?", noofitemsselected);

Ho una funzione VBA che uso in MS Access per fare esattamente quello di cui stai parlando. So che verrò fatto a pezzi per aver pubblicato VBA, ma qui va comunque. L'algoritmo dovrebbe essere evidente dai commenti:

'---------------------------------------------------------------------------------------'
' Procedure : Pluralize'
' Purpose   : Formats an English phrase to make verbs agree in number.'
' Usage     : Msg = "There [is/are] # record[s].  [It/They] consist[s/] of # part[y/ies] each."'
'             Pluralize(Msg, 1) --> "There is 1 record.  It consists of 1 party each."'
'             Pluralize(Msg, 6) --> "There are 6 records.  They consist of 6 parties each."'
'---------------------------------------------------------------------------------------'
''
Function Pluralize(Text As String, Num As Variant, Optional NumToken As String = "#")
Const OpeningBracket = "\["
Const ClosingBracket = "\]"
Const DividingSlash = "/"
Const CharGroup = "([^\]]*)"  'Group of 0 or more characters not equal to closing bracket'
Dim IsPlural As Boolean, Msg As String, Pattern As String

    On Error GoTo Err_Pluralize

    If IsNumeric(Num) Then
        IsPlural = (Num <> 1)
    End If

    Msg = Text

    'Replace the number token with the actual number'
    Msg = Replace(Msg, NumToken, Num)

    'Replace [y/ies] style references'
    Pattern = OpeningBracket & CharGroup & DividingSlash & CharGroup & ClosingBracket
    Msg = RegExReplace(Pattern, Msg, "$" & IIf(IsPlural, 2, 1))

    'Replace [s] style references'
    Pattern = OpeningBracket & CharGroup & ClosingBracket
    Msg = RegExReplace(Pattern, Msg, IIf(IsPlural, "$1", ""))

    'Return the modified message'    
    Pluralize = Msg
End Function

Function RegExReplace(SearchPattern As String, _
                      TextToSearch As String, _
                      ReplacePattern As String) As String
Dim RE As Object

    Set RE = CreateObject("vbscript.regexp")
    With RE
        .MultiLine = False
        .Global = True
        .IgnoreCase = False
        .Pattern = SearchPattern
    End With

    RegExReplace = RE.Replace(TextToSearch, ReplacePattern)
End Function

L'uso è stato tagliato un po 'nei commenti del codice sopra, quindi lo ripeterò qui:

Msg = "There [is/are] # record[s]. [It/They] consist[s/] of # part[y/ies] each."

Pluralize(Msg, 1) --> "There is 1 record.  It consists of 1 party each."
Pluralize(Msg, 6) --> "There are 6 records.  They consist of 6 parties each."

Sì, questa soluzione ignora le lingue che non sono inglesi. Se ciò che conta dipende dalle tue esigenze.


Potresti scegliere un messaggio più generico come "Sei sicuro di voler eliminare gli elementi selezionati".


Per il primo problema, intendo Pluralize, puoi usare Inflector .

E per il secondo, puoi usare un'estensione per la rappresentazione delle stringhe con un nome come ToPronounString.


Vorrei pensare fuori dagli schemi per un minuto, tutti i suggerimenti qui sono o pluralizzano (e preoccupati di più di 1 livello di pluralizzazione, genere, ecc.) O non lo usano affatto e forniscono un bel annullamento.

Andrei in modo non linguale e userei code visive per questo. Ad esempio, immagina un'app Iphone per selezionare gli elementi pulendo il dito. prima di eliminarli usando il pulsante di cancellazione principale, "scuoterà" gli elementi selezionati e mostrerà una casella con un punto interrogativo con i pulsanti V (ok) o X (cancella) ...

Oppure, nel mondo 3D di Kinekt / Move / Wii - immagina di selezionare i file, sposta la tua mano sul pulsante di cancellazione e ti viene detto di muovere la mano sopra la testa per confermare (usando gli stessi simboli visivi che ho citato prima. di chiederti di cancellare 3 file? ti mostrerà 3 file con una X rossa passante per metà trasparente e ti dirò di fare qualcosa per confermare.


Puoi evitare tutto questo disordinato pluralismo semplicemente eliminando gli elementi senza alcun messaggio e dando all'utente una funzionalità di annullamento davvero buona. Gli utenti non leggono mai nulla . Dovresti comunque costruire una buona funzione di annullamento come parte del tuo programma.

In realtà ottieni 2 vantaggi quando crei una funzione di annullamento completa. Il primo vantaggio semplifica la vita dell'utente consentendogli di invertire gli errori e ridurre al minimo la lettura. Il secondo vantaggio è che la tua app riflette la vita reale consentendo l'inversione del flusso di lavoro non banale (non solo errori).

Una volta ho scritto un'app senza usare una singola finestra di dialogo o un messaggio di conferma. Ci sono voluti alcuni pensieri seri ed è stato molto più difficile da implementare rispetto all'utilizzo di messaggi di tipo conferma. Ma il risultato finale è stato piuttosto piacevole da usare in base ai suoi utenti finali.


Diventa un po 'più breve con

string message = "Are you sure you want to delete " + noofitemsselected + " item" + (noofitemsselected>1 ? "s" : "") + "?";

Che dire di ciò che Java ha avuto da anni: java.text.MessageFormat e ChoiceFormat? Vedi http://download.oracle.com/javase/1.4.2/docs/api/java/text/MessageFormat.html per ulteriori informazioni.

MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
form.applyPattern(
   "There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}.");

Object[] testArgs = {new Long(12373), "MyDisk"};

System.out.println(form.format(testArgs));

// output, with different testArgs
output: The disk "MyDisk" are no files.
output: The disk "MyDisk" is one file.
output: The disk "MyDisk" are 1,273 files.

Nel tuo caso vuoi qualcosa di più semplice:

MessageFormat form = new MessageFormat("Are you sure you want to delete {0,choice,1#one item,1<{0,number.integer} files}?");

Il vantaggio di questo approccio è che funziona bene con i bundle i18n e puoi fornire traduzioni appropriate per le lingue (come il giapponese) che non hanno alcun concetto di parole al plurale o singolari.


La prima cosa che suggerirei è: usare string.Format . Questo ti permette di fare qualcosa del genere:

int numOfItems = GetNumOfItems();
string msgTemplate;
msgTemplate = numOfItems == 1 ? "You selected only {0} item." : "Wow, you selected {0} items!";
string msg = string.Format(msgTemplate, numOfItems);

Inoltre, nelle app WPF, ho visto sistemi in cui una stringa di risorse sarebbe delimitata da pipe per avere due messaggi: un messaggio singolare e plurale (o un messaggio zero / singolo / molti, pari). Quindi è possibile utilizzare un convertitore personalizzato per analizzare questa risorsa e utilizzare la stringa rilevante (formattata), quindi il tuo Xaml è qualcosa del genere:

<TextBlock Text="{Binding numOfItems, Converter={StaticResource c:NumericMessageFormatter}, ConverterParameter={StaticResource s:SuitableMessageTemplate}}" />

Il mio approccio generale è scrivere una "funzione singolo / plurale", come questa:

public static string noun(int n, string single, string plural)
{
  if (n==1)
    return single;
  else
    return plural;
}

Quindi nel corpo del messaggio chiamo questa funzione:

string message="Congratulations! You have won "+n+" "+noun(n, "foobar", "foobars")+"!";

Questo non è molto meglio, ma almeno, (a) mette la decisione in una funzione e quindi disimpegna un po 'il codice, e (b) è abbastanza flessibile da gestire i plurali irregolari. cioè è abbastanza facile dire sostantivo (n, "bambino", "figli") e simili.

Naturalmente questo funziona solo per l'inglese, ma il concetto è facilmente estensibile alle lingue con terminazioni più complesse.

Mi viene in mente che potresti rendere l'ultimo parametro opzionale per il caso facile:

public static string noun(int n, string single, string plural=null)
{
  if (n==1)
    return single;
  else if (plural==null)
    return single+"s";
  else
    return plural;
}

Dal mio punto di vista, la tua prima soluzione è la più adatta. Perché dico che, nel caso tu abbia bisogno dell'applicazione per supportare più lingue, la seconda opzione può essere scrupolosa. Con il primo approccio è facile localizzare il testo senza troppi sforzi.


Un approccio che non ho visto menzionato sarebbe l'uso di un tag di sostituzione / selezione (ad es. Qualcosa come "Stai per schiacciare {0} [? I ({0} = 1): / cactus / cactus /]". (in altre parole, avere un'espressione di formato specificare la sostituzione in base all'argomento se l'argomento zero, preso come numero intero, è uguale a 1). Ho visto tali tag utilizzati nei giorni precedenti .net; Non sono a conoscenza di alcun standard per loro in .net, né conosco il modo migliore per formattarli.


Ho avuto questa stessa identica domanda che mi è stata posta ieri da un membro del nostro team.

Da quando è apparso di nuovo qui su , ho pensato che l'universo mi stesse dicendo di avere una botta nella produzione di una soluzione decente.

Ho rapidamente messo insieme qualcosa e non è affatto perfetto, tuttavia potrebbe essere utile o suscitare discussioni / sviluppi.

Questo codice si basa sull'idea che possono esserci 3 messaggi. Uno per zero elementi, uno per un articolo e uno per più di un elemento che seguono la seguente struttura:

singlePropertyName
singlePropertyName_Zero
singlePropertyName_Plural

Ho creato una classe interna da testare per simulare la classe di risorse. Non ho ancora provato questo utilizzando un file di risorse effettivo, quindi devo ancora vedere il risultato completo.

Ecco il codice (al momento ho incluso alcuni generici dove so che avrei potuto specificare il terzo param semplicemente come Type e anche il secondo param è una stringa, penso che ci sia un modo per combinare questi due parametri in qualcosa di meglio ma io Tornerò a quello quando avrò un momento libero.

    public static string GetMessage<T>(int count, string resourceSingularName, T resourceType) where T : Type
{
    var resourcePluralName = resourceSingularName + "_Plural";
    var resourceZeroName = resourceSingularName + "_Zero";
    string resource = string.Empty;
    if(count == 0)
    {
        resource = resourceZeroName;
    }
    else{
        resource = (count <= 1)? resourceSingularName : resourcePluralName;
    }
    var x = resourceType.GetProperty(resource).GetValue(Activator.CreateInstance(resourceType),null);

    return x.ToString();
}

Prova la classe di risorse:

internal class TestMessenger
{
    public string Tester{get{
    return "Hello World of one";}}
    public string Tester_Zero{get{
    return "Hello no world";}}
    public string Tester_Plural{get{
    return "Hello Worlds";}}
}

e il mio metodo di esecuzione rapida

void Main()
{
    var message = GetMessage(56, "Tester",typeof(TestMessenger));
    message.Dump();
}

Per l'inglese, molte risposte sopra. Per altri linguaggi è più difficile, poiché i plurali dipendono dal genere del nome e dalla parola che termina. Alcuni esempi in francese:

Maschile regolare:

Vous avez choisi 1 compte. Voulez-vous vraiment le supprimer.
Vous avez choisi 2 comptes. Voulez-vous vraiment les supprimer.

Regolare femminile

Vous avez choisi 1 table. Voulez-vous vraiment la supprimer.
Vous avez choisi 2 tables. Voulez-vous vraiment les supprimer.

Maschile irregolare (finisce con "s")

Vous avez choisi 1 pays. Voulez-vous vraiment le supprimer.
Vous avez choisi 2 pays. Voulez-vous vraiment les supprimer?

Lo stesso problema esiste nella maggior parte delle lingue latine e peggiora in tedesco o russo, dove ci sono 3 generi (macrogeno, femminile e neutro).

Avrai bisogno di fare attenzione se il tuo obiettivo è di gestire più che solo l'inglese.


Perché vorresti presentare un messaggio che gli utenti possano effettivamente capire? Va contro i 40 anni di storia della programmazione. Nooooo, abbiamo una buona cosa da fare, non rovinare tutto con messaggi comprensibili .

(J / k)


Che ne dici di scrivere funzioni come

string GetOutputMessage(int count, string oneItemMsg, string multiItemMsg)
{
 return string.Format("{0} {1}", count, count > 1 ? multiItemMsg : oneItemMsg);
}

.. e usalo quando ti serve?

string message = "You have selected " + GetOutputMessage(noofitemsselected,"item","items") + ". Are you sure you want to delete it/them?";

Un ciclo di messaggi è una piccola parte di codice che esiste in qualsiasi programma nativo di Windows. Somiglia all'incirca a questo:

MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{ 
   TranslateMessage(&msg); 
   DispatchMessage(&msg); 
} 

L'API Win32 GetMessage () recupera un messaggio da Windows. Il tuo programma spende in genere il 99,9% del tempo lì, in attesa che Windows dica che è successo qualcosa di interessante. TranslateMessage () è una funzione di supporto che traduce i messaggi della tastiera. DispatchMessage () assicura che la procedura della finestra venga chiamata con il messaggio.

Ogni programma .NET abilitato alla GUI ha un ciclo di messaggi, viene avviato da Application.Run ().

La rilevanza di un loop di messaggi in Office è correlata a COM. I programmi di Office sono programmi abilitati al COM, ecco come funzionano le classi Microsoft.Office.Interop. COM si occupa di eseguire il threading per conto di una coclasse COM, garantisce che le chiamate effettuate su un'interfaccia COM vengano sempre eseguite dal thread corretto. La maggior parte delle classi COM ha una chiave di registro nel registro che dichiara il proprio ThreadingModel, di gran lunga le più comuni (incluso Office) utilizzano "Apartment". Ciò significa che l'unico modo sicuro per chiamare un metodo di interfaccia è effettuare la chiamata dallo stesso thread che ha creato l'oggetto classe. O per dirla in altro modo: di gran lunga la maggior parte delle classi COM non sono thread-safe.

Ogni thread abilitato COM appartiene a un appartamento COM. Esistono due tipi: Single Threaded Apartments (STA) e Multi Thread Apartment (MTA). Una classe COM threadizzata dell'appartamento deve essere creata su un thread STA. Puoi vederlo nei programmi .NET, il punto di ingresso del thread UI di un programma Windows Form o WPF ha l'attributo [STAThread]. Il modello apartment per altri thread è impostato dal metodo Thread.SetApartmentState ().

Gran parte degli impianti idraulici di Windows non funzioneranno correttamente se il thread dell'interfaccia utente non è STA. In particolare Drag + Drop, gli appunti, le finestre di dialogo di Windows come OpenFileDialog, controlli come WebBrowser, applicazioni di interfaccia utente come gli screen reader. E molti server COM, come Office.

Un requisito indispensabile per un thread STA è che non dovrebbe mai bloccare e deve pompare un loop di messaggi. Il ciclo dei messaggi è importante perché è ciò che viene utilizzato da COM per eseguire il marshalling di una chiamata al metodo di interfaccia da un thread a un altro. Sebbene .NET faciliti le chiamate di marshaling (ad esempio Control.BeginInvoke o Dispatcher.BeginInvoke), è in realtà una cosa molto complessa da fare. Il thread che esegue la chiamata deve essere in uno stato ben noto. Non si può semplicemente interrompere arbitrariamente un thread e forzarlo per effettuare una chiamata al metodo, ciò causerebbe problemi di re-entrancy orribili. Un thread dovrebbe essere "inattivo", non occupato nell'eseguire qualsiasi codice che muta lo stato del programma.

Forse puoi vedere dove porta questo: sì, quando un programma sta eseguendo il ciclo dei messaggi, è inattivo. Il marshalling effettivo avviene tramite una finestra nascosta creata da COM, utilizza PostMessage per fare in modo che la procedura della finestra di tale finestra esegua il codice. Nel thread STA. Il ciclo di messaggi garantisce che questo codice venga eseguito.







c# string localization pluralize