Множественность в сообщениях пользователей

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

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

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

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

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

Моим обычным решением будет что-то вроде этого

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

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

Итак, мои вопросы просто. Есть ли лучшие способы генерации таких сообщений?

ИЗМЕНИТЬ

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

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

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

Ответ 1

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

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)". Вы не должны никого никого не впечатлять при написании кода предприятия. Но я бы посоветовал не делать этого для публично потребляемого приложения, потому что это создает впечатление, что программист ленив и тем самым снижает свое мнение о качестве приложения. Поверьте мне, мелочи вроде этого.

Ответ 2

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

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

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

Ответ 3

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

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

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

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

Ответ 4

Как насчет того, что 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, и вы можете обеспечить переводы для языков (например, японцев), которые не имеют понятия множественных или сингулярных слов.

Ответ 5

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

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)

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

Ответ 6

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

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

Ответ 7

Первое, что я предлагаю: use 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}}" />

Ответ 8

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

Регулярное мужское:

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, feminine и neuter).

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

Ответ 9

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

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

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.

Ответ 10

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

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

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

Ответ 11

Вы можете создать множественное число автоматически, см. например. множественный генератор.

Для правил множественного генерирования см. wikipedia

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

Ответ 12

Как насчет более общего пути. Избегайте плюрализации во втором предложении:

Number of selected items to be deleted: noofitemsselected.
Are you sure?

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

Ответ 13

Мой общий подход заключается в написании "единственной/множественной функции", например:

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

Затем в теле сообщения я вызываю эту функцию:

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

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

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

Мне кажется, что вы можете сделать последний параметр необязательным для простого случая:

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

Ответ 14

Интернационализация

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

Вы можете использовать функцию GNU Gettext ngettext и предоставить в своем исходном коде два английских сообщения. Gettext предоставит инфраструктуре возможность выбирать из других (потенциально более) сообщений при переводе на другие языки. См. http://www.gnu.org/software/hello/manual/gettext/Plural-forms.html для полного описания поддержки GNU gettext множественного числа.

GNU Gettext находится под LGPL. ngettext имеет имя GettextResourceManager.GetPluralString в порту С# Gettext.

(Если вам не нужна поддержка локализации и вы не хотите сразу использовать Gettext, напишите свою собственную функцию, которая делает это для английского, и передайте ему два полных сообщения, если вам понадобится l10n later, вы можете добавить, переписав одну функцию.)

Ответ 15

Как написать функцию, например

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

.. и использовать его, когда вам нужно?

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

Ответ 16

Для первой проблемы я имею в виду Pluralize, вы можете использовать Inflector.

А для второго вы можете использовать расширение строкового представления с таким именем, как ToPronounString.

Ответ 17

У меня был такой же вопрос, который вчера задал мне член нашей команды.

Так как он появился снова на StackOverflow, я понял, что вселенная говорила мне, что имеет bash при создании достойного решения.

Я быстро собрал что-то вместе, и это ни в коем случае не идеально, но может быть полезно или вызвать некоторые обсуждения/разработки.

Этот код основан на идее, что могут быть 3 сообщения. Один для нулевых элементов, один для одного элемента и один для нескольких элементов, которые следуют следующей структуре:

singlePropertyName
singlePropertyName_Zero
singlePropertyName_Plural

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

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

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

Класс тестовых ресурсов:

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

и мой быстрый исполняемый метод

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

Ответ 18

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

Ответ 19

Вы можете найти более общее сообщение типа "Вы уверены, что хотите удалить выбранный элемент (ы)".

Ответ 20

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

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

  • Используйте более общий язык, но все равно укажите число (ы).

  • Используйте "плюрализацию" и инфлекторную систему ala Rails, поэтому вы можете сказать pluralize(5,'bunch') и получить 5 bunches. Rails имеет хороший шаблон для этого.

  • Для интернационализации вам нужно посмотреть, что предоставляет Java. Это будет поддерживать широкий спектр языков, включая те, которые имеют разные формы прилагательных с 2 или 3 предметами. Решение "s" очень ориентировано на английский язык.

Какой вариант вы используете, зависит от ваших целей продукта. - ndp

Ответ 21

Почему вы хотите представить сообщение, которое пользователи действительно могут понять? Это связано с 40-летней историей программирования. Nooooo, у нас все хорошо, не портите его понятными сообщениями.


(к/к)

Ответ 22

Сделайте это в World of Warcraft:

BILLING_NAG_WARNING = "Your play time expires in %d |4minute:minutes;";

Ответ 23

Он немного короче с

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

Ответ 24

Один из подходов, о которых я не упоминал, - это использование тега подстановки/выбора (например, что-то вроде "Вы собираетесь раздавить {0} [? я ({0} = 1):/cactus/cacti/]" (другими словами, иметь форматируемое выражение, указать подстановку, основанную на том, равен ли аргумент нулю, принятый как целое число, 1). Я видел такие теги, используемые в дни перед .net; знающий любой стандарт для них в .net, и я не знаю, как лучше их форматировать.

Ответ 25

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

Я бы пошел неязычным способом и использовал визуальные очереди для этого. например представьте себе приложение Iphone, которое вы выбираете, вытирая палец. прежде чем удалять их с помощью кнопки "Мастер удаления", она "встряхнет" выбранные элементы и покажет вам поле с вопросительным знаком с кнопками V (ok) или X (отмена)...

Или, в 3D-мире Kinekt/Move/Wii - представьте, что вы выбираете файлы, перемещая руку к кнопке удаления, и вам будет предложено переместить вашу руку над головой, чтобы подтвердить (используя те же визуальные символы, что я упоминал ранее например, вместо того, чтобы просить вас удалить 3 файла, он покажет вам 3 файла с парящим полупрозрачным красным X и скажет вам сделать что-то, чтобы подтвердить.