Множественность пользовательских сообщений - PullRequest
106 голосов
/ 23 ноября 2010

Часто при создании сообщений для показа пользователю сообщение будет содержать число что-то , о котором я хочу сообщить клиенту.

Я приведу пример: клиент выбрал количество товаров от 1 и выше и нажал кнопку удаления. Теперь я хочу отправить клиенту подтверждающее сообщение и упомянуть количество выбранных им предметов, чтобы свести к минимуму вероятность того, что он допустил ошибку, выбрав несколько предметов и нажав «Удалить», когда он хочет удалить только один из им.

Один из способов - сделать общее сообщение следующим образом:

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

«Проблема» здесь - это случай, когда noofitemselected равен 1, и мы должны написать item и it вместо items и их .

Мое нормальное решение будет примерно таким

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

Это становится довольно длинным и довольно неприятным, очень быстрым, если в коде много ссылок на множество чисел, а реальное сообщение становится трудным для чтения.

Так что мои вопросы просты. Есть ли более эффективные способы создания таких сообщений?

EDIT

Я вижу, что многие люди очень зациклились на том, что я упомянул, что сообщение должно отображаться в окне сообщения, и просто дал ответ о том, как вообще не использовать окно сообщения, и что все хорошо.

Но помните, что проблема плюрализации распространяется и на тексты в других местах программы, помимо окон сообщений. Например, метка рядом с сеткой, отображающая количество линий, выбранных в сетке, будет иметь ту же проблему в отношении плюрализации.

Таким образом, это в основном относится к большей части текста, который каким-то образом выводится из программ, и тогда решение не так просто, как просто изменить программу так, чтобы она больше не выводила текст:)

Ответы [ 25 ]

94 голосов
/ 23 ноября 2010

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

Вы фактически получаете 2 преимущества, когда создаете комплексное средство отмены.Первое преимущество облегчает жизнь пользователя, позволяя ему исправлять ошибки и сводить к минимуму чтение.Второе преимущество заключается в том, что ваше приложение отражает реальную жизнь, позволяя изменить нетривиальный рабочий процесс (а не только ошибки).

Однажды я написал приложение, не используя ни одного диалогового окна или подтверждающего сообщения.Это потребовало некоторых серьезных размышлений и было значительно труднее реализовать, чем использование сообщений типа подтверждения.Но конечный результат был довольно приятен для использования в соответствии с его конечными пользователями.

53 голосов
/ 23 ноября 2010

Если есть хоть какой-то шанс, независимо от того, насколько мал, что это приложение нужно будет перевести на другие языки, тогда оба ошибаются. Правильный способ сделать это:

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

Это потому, что разные языки по-разному обрабатывают множественность. Некоторые, такие как малайский, даже не имеют синтаксического множественного числа, поэтому строки обычно бывают идентичными. Разделение двух строк облегчает поддержку других языков в дальнейшем.

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

Если это приложение предназначено только для внутреннего использования вашей компанией, тогда сделайте ярлык "item(s)". Вам не нужно никого удивлять, когда вы пишете предпринимательский код. Но я бы не советовал делать это для общедоступного приложения, потому что это создает впечатление, что программист ленив и, следовательно, снижает свое мнение о качестве приложения. Поверь мне, такие мелочи, как этот вопрос.

39 голосов
/ 23 ноября 2010

Как насчет просто:

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

Таким образом, вы устраняете трудности с соглашением о количестве и получаете еще более короткое, более точное сообщение об ошибке для пользователя в качестве бонуса. Мы все знаем, пользователи не читают сообщения об ошибках в любом случае . Чем они короче, тем больше вероятность, что они по крайней мере посмотрят на текст.

Или, вооружившись этим знанием о том, что пользователи не читают сообщения об ошибках, вы можете подойти к этому по-другому. В целом пропустите подтверждающее сообщение и просто предоставьте функцию отмены, которая просто работает, независимо от того, что было удалено. Большинство пользователей уже привыкли отменять операцию, когда замечают, что это не то, что им нужно, и, вероятно, сочтут это поведение более естественным, чем работа с другим раздражающим всплывающим окном.

20 голосов
/ 23 ноября 2010

А как насчет того, что было в Java на протяжении многих лет: java.text.MessageFormat и ChoiceFormat? См. http://download.oracle.com/javase/1.4.2/docs/api/java/text/MessageFormat.html для получения дополнительной информации.

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.

В вашем случае вы хотите что-то более простое:

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

Преимущество этого подхода заключается в том, что он хорошо работает с пакетами i18n, и вы можете правильно предоставлять переводы для языков (например, японских), которые не имеют понятия множественного числа или отдельных слов.

12 голосов
/ 23 ноября 2010

Я бы пошел не с жестким кодированием сообщения, а с предоставлением двух сообщений в отдельном файле ресурсов.Как

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?";

, а затем передать их в String.Format, как

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

string message = String.Format(messageTemplate, noofitemsselected)

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

11 голосов
/ 23 ноября 2010

Вы можете полностью обойти проблему, сформулировав сообщение по-другому.

string message = "The number of selected items is " + noofitemsselected + ". Are you sure you want to delete everything in this selection?";
10 голосов
/ 23 ноября 2010

Первое, что я бы предложил: используйте string.Format.Это позволяет вам сделать что-то вроде этого:

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

Кроме того, в приложениях WPF я видел системы, в которых строка ресурса будет разделена каналом, чтобы иметь два сообщения: единственное и множественное (или сообщение ноль / один / много, даже).Затем можно использовать пользовательский конвертер для анализа этого ресурса и использования соответствующей (отформатированной) строки, поэтому ваш Xaml выглядит примерно так:

<TextBlock Text="{Binding numOfItems, Converter={StaticResource c:NumericMessageFormatter}, ConverterParameter={StaticResource s:SuitableMessageTemplate}}" />
7 голосов
/ 23 ноября 2010

Для английского, много ответов выше.Для других языков это сложнее, так как множественное число зависит от пола существительного и окончания слова.Некоторые примеры на французском:

Обычный мужской:

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

Обычный женский

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

Нерегулярный мужской (оканчивается на 's')

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

Та же проблема существует в большинстве латинских языков и усугубляется в немецком или русском, где есть 3 пола (maculine, женский и средний).

Вам нужно позаботиться, если ваша цель - справитьсябольше, чем просто английский.

5 голосов
/ 24 ноября 2010

Вам придется перевести приведенную ниже функцию из VBA в C #, но ваше использование изменится на:

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

У меня есть функция VBA, которую я использую в MS Access, чтобы делать именно то, что выговоря о.Я знаю, что меня разнесут на куски за публикацию VBA, но все равно идет.Алгоритм должен быть очевиден из комментариев:

'---------------------------------------------------------------------------------------'
' 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

Использование немного обрезано в комментариях к коду выше, поэтому я повторю его здесь:

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."

Да,Это решение игнорирует языки, которые не являются английскими.Имеет ли это значение, зависит от ваших требований.

5 голосов
/ 23 ноября 2010

Чтобы иметь возможность иметь множественные сообщения, которые можно будет правильно локализовать, я считаю, что было бы целесообразно сначала создать слой косвенности между номером и сообщением.

Например, используйте некоторую константу, чтобы указать, какое сообщение вы хотите отобразить. Получите сообщение, используя некоторую функцию, которая скроет детали реализации.

get_message(DELETE_WARNING, quantity)

Затем создайте словарь, в котором хранятся возможные сообщения и варианты, и сообщите вариантам, когда их следует использовать.

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?'
}

Теперь вы можете просто найти ключ, соответствующий quantity, и интерполировать значение quantity с этим сообщением.

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

...