107

很多时候,当生成要显示给用户的消息时,该消息将包含一些我想告知客户的内容。

我举个例子:客户从 1 及以上选择了多个项目,并单击了删除。现在我想给客户一个确认信息,我想提一下他选择的项目数量,以通过选择一堆项目并在他只想删除其中一个时单击删除来最大程度地减少他出错的机会他们。

一种方法是制作这样的通用消息:

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

这里的“问题”noofitemselected是 1 的情况,我们必须写item and it而不是items and them

我的正常解决方案是这样的

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

如果代码中有许多对数字复数的引用,那么这会变得非常长而且非常令人讨厌,并且实际消息变得难以阅读。

所以我的问题很简单。有没有更好的方法来生成这样的消息?

编辑

我看到很多人对我提到的消息应该显示在消息框内的情况非常感兴趣,并且简单地给出了如何完全避免使用消息框的答案,这一切都很好.

但请记住,除消息框外,复数问题也适用于程序中其他地方的文本。例如,显示在网格中选择的行数的网格旁边的标签将在复数方面存在相同的问题。

所以这基本上适用于大多数从程序以某种方式输出的文本,然后解决方案并不像仅仅将程序更改为不再输出文本那么简单:)

4

25 回答 25

94

您可以通过删除项目而无需任何消息并为用户提供非常好的撤消功能来避免所有这些混乱的复数。用户从不阅读任何东西。无论如何,您应该构建一个良好的撤消工具作为程序的一部分。

当您创建一个全面的撤消工具时,您实际上获得了 2 个好处。第一个好处是允许用户改正错误并最小化阅读,从而使用户的生活更轻松。第二个好处是您的应用程序通过允许反转非平凡的工作流程(不仅仅是错误)来反映现实生活。

我曾经编写过一个应用程序,但没有使用单个对话框或确认消息。与使用确认类型的消息相比,这需要一些认真的思考,并且实施起来要困难得多。但根据最终用户的说法,最终结果使用起来相当不错。

于 2010-11-23T09:11:20.940 回答
53

如果有任何机会,无论多么小,这个应用程序都需要翻译成其他语言,那么两者都是错误的。这样做的正确方法是:

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)"。在编写企业代码时,您实际上不必给任何人留下深刻印象。但我建议不要对公开使用的应用程序这样做,因为这给人的印象是程序员很懒惰,从而降低了他们对应用程序质量的看法。相信我,像这样的小事。

于 2010-11-23T08:57:00.003 回答
39

怎么样:

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

这样,您就消除了号码一致的困难,并最终为用户提供更短、更中肯的错误消息作为奖励。我们都知道用户无论如何都不会阅读错误消息。他们越短,他们就越有可能至少看一眼文本。

或者,有了用户不会阅读错误消息的知识,您可以采用不同的方式来解决这个问题。完全跳过确认消息,只提供 Just Works 的撤消功能,无论删除什么。大多数用户已经习惯于在发现不是他们想要的操作时撤消操作,并且可能会发现这种行为比处理另一个烦人的弹出窗口更自然。

于 2010-11-23T08:42:27.143 回答
20

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 捆绑包一起使用,并且您可以为没有复数或单数概念的语言(如日语)提供正确的翻译。

于 2010-11-23T13:53:46.570 回答
12

我不会对消息进行硬编码,而是在单独的资源文件中提供两条消息。像

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)

我认为这种方法更容易本地化和维护。所有 UI 消息都将位于一个位置。

于 2010-11-23T09:00:05.117 回答
11

您可以通过不同的措辞来完全回避这个问题。

string message = "The number of selected items is " + noofitemsselected + ". Are you sure you want to delete everything in this selection?";
于 2010-11-23T08:43:09.997 回答
10

我建议的第一件事是:使用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}}" />
于 2010-11-23T08:53:06.640 回答
7

对于英语,上面有很多答案。对于其他语言来说,这更加困难,因为复数取决于名词的性别和词尾。法语的一些例子:

普通男性:

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 种性别(男性化、女性化和中性)。

如果您的目标不仅仅是处理英语,您需要小心。

于 2010-11-23T12:31:56.023 回答
5

为了能够拥有可以正确本地化的多元化消息,我认为首先在数字和消息之间创建一个间接层是明智的。

例如,使用某种常量来指定要显示的消息。使用一些隐藏实现细节的函数来获取消息。

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 提供良好的支持。

于 2010-11-23T16:30:50.063 回答
5

您必须将以下函数从 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."

是的,此解决方案会忽略非英语的语言。这是否重要取决于您的要求。

于 2010-11-23T21:55:38.783 回答
4

您可以自动生成复数,参见例如。复数生成器

有关复数生成规则,请参见维基百科

string msg = "Do you want to delete " + numItems + GetPlural(" item", numItems) + "?";
于 2010-11-23T08:44:21.263 回答
4

更通用的方法怎么样。第二句避免复数:

要删除的选定项目数:noofitemsselected。
你确定吗?

我发现这样做会将数字放在行尾,这很容易发现。该解决方案可以在任何语言中使用相同的逻辑。

于 2010-11-23T09:20:52.683 回答
4

我的一般方法是编写一个“单/复数函数”,如下所示:

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) 将决定放在一个函数中,因此代码稍微杂乱,并且 (b) 足够灵活,可以处理不规则的复数。即说名词(n,“child”,“children”)等很容易。

当然这只适用于英语,但这个概念很容易扩展到具有更复杂结尾的语言。

我突然想到,对于简单的情况,您可以将最后一个参数设为可选:

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;
}
于 2010-11-23T22:22:23.137 回答
4

国际化

我假设您需要国际化支持,在这种情况下,不同的语言具有不同的复数模式(例如,2 的特殊复数形式,或更复杂的语言,如波兰语),并且您不能依赖于将一些简单的模式应用于您的字符串要解决这个问题。

您可以使用 GNU Gettext 的ngettext功能并在您的源代码中提供两条英文消息。Gettext 将提供基础架构,以便在翻译成其他语言时从其他(可能更多)消息中进行选择。有关GNU gettext 的复数支持的完整描述,请参见http://www.gnu.org/software/hello/manual/gettext/Plural-forms.html 。

GNU Gettext 在 LGPL 下。ngettext在 Gettext 的 C# 端口中命名GettextResourceManager.GetPluralString

(如果您不需要本地化支持,并且不想立即使用 Gettext,那么请编写您自己的用于英语的函数,并将两条完整的消息传递给它,这样如果您稍后需要 l10n,您可以通过重写单个函数来添加。)

于 2010-11-24T01:48:16.513 回答
3

像这样写函数怎么样

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?";
于 2010-11-23T08:47:39.447 回答
3

对于第一个问题,我的意思是复数,你可以使用Inflector

其次,您可以使用名称为 ToPronounString 的字符串表示扩展。

于 2010-11-23T08:48:12.397 回答
3

昨天,我们团队的一名成员向我提出了同样的问题。

自从它在 StackOverflow 上再次出现以来,我认为整个世界都在告诉我要为产生一个体面的解决方案而欢欣鼓舞。

我很快把一些东西放在一起,它绝不是完美的,但它可能有用或引发一些讨论/开发。

此代码基于可以有 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();
}
于 2010-11-23T12:30:04.710 回答
2

从我的角度来看,您的第一个解决方案是最合适的。为什么我这么说是,如果您需要应用程序支持多种语言,第二个选项可能会很辛苦。使用第一种方法很容易本地化文本而不需要太多努力。

于 2010-11-23T08:40:58.430 回答
2

您可以使用更通用的消息,例如“您确定要删除所选项目”。

于 2010-11-23T08:41:51.183 回答
2

我取决于你想要的消息有多好。从最简单到最难:

  1. 重写你的错误信息以避免复数。对您的用户来说不是很好,但速度更快。

  2. 使用更通用的语言,但仍包括数字。

  3. 使用“复数”和变形系统ala Rails,所以你可以说pluralize(5,'bunch')和得到5 bunches. Rails 有一个很好的模式。

  4. 对于国际化,您需要查看 Java 提供了什么。这将支持多种语言,包括那些具有 2 或 3 个项目的不同形式的形容词的语言。“s”解决方案非常以英语为中心。

您选择哪个选项取决于您的产品目标。- 国家发展计划

于 2010-11-23T15:44:30.910 回答
2

为什么要呈现用户可以真正理解的信息?这与 40 年的编程历史背道而驰。不,我们有一件好事,不要用可以理解的信息破坏它。


(j/k)

于 2010-11-24T05:26:43.360 回答
2

像在魔兽世界中那样做:

BILLING_NAG_WARNING = "Your play time expires in %d |4minute:minutes;";
于 2010-11-24T08:24:01.787 回答
0

它变得有点短

string message = "Are you sure you want to delete " + noofitemsselected + " item" + (noofitemsselected>1 ? "s" : "") + "?";
于 2010-11-23T08:43:30.663 回答
0

我没有看到提到的一种方法是使用替换/选择标签(例如,“你将要挤压 {0} [?i({0}=1):/cactus/cacti/]”。 (换句话说,有一个类似格式的表达式根据参数零(作为整数)是否等于 1 来指定替换。我已经看到在 .net 之前的日子里使用过这样的标签;我不知道有任何.net 中的标准,我也不知道格式化它们的最佳方法。

于 2010-11-23T18:22:44.247 回答
0

我想开箱即用,这里的所有建议要么进行复数化(并担心超过 1 级的复数化、性别等),要么根本不使用它并提供一个很好的撤消。

我会采用非语言方式并为此使用视觉队列。例如,想象一个 Iphone 应用程序,您通过擦拭手指来选择项目。在使用主删除按钮删除它们之前,它会“摇动”所选项目并向您显示一个带有 V(确定)或 X(取消)按钮的问号框...

或者,在 Kinekt / Move / Wii 的 3D 世界中 - 想象选择文件,将您的手移到删除按钮,然后被告知将您的手移到头顶以确认(使用与我之前提到的相同的视觉符号。例如要求你删除 3 个文件?它会显示 3 个文件,上面有一个悬停的半透明红色 X,并告诉你做一些事情来确认。

于 2010-12-19T06:10:57.513 回答