代码之家  ›  专栏  ›  技术社区  ›  Øyvind Bråthen

多个用户消息

  •  106
  • Øyvind Bråthen  · 技术社区  · 15 年前

    很多时候,当生成要向用户显示的消息时,该消息将包含 某物 我想告诉顾客的。

    我将给出一个例子:客户从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") + "?";
    

    如果代码中有很多对数字复数的引用,并且实际的消息很难阅读,那么这会变得很长而且非常糟糕。

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

    编辑

    我看到很多人在我提到信息应该显示在一个信息框中的情况下,已经非常挂掉了,并且简单地给出了一个如何避免使用该信息框的答案,这一切都是好的。

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

    因此,这基本上适用于大多数以某种方式从程序输出的文本,然后解决方案就不再简单到只需将程序更改为不再输出文本:)

    25 回复  |  直到 15 年前
        1
  •  53
  •   slebetman    15 年前

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

    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
  •  94
  •   HTTP 410    15 年前

    只需删除没有任何消息的项,并给用户一个非常好的撤消工具,就可以避免所有这些混乱的多个项。 Users never read anything . 你 should build a good Undo facility 作为你计划的一部分。

    当你创建一个全面的撤销工具时,你实际上得到了两个好处。第一个好处是让用户能够逆转错误并尽量减少阅读,从而使用户的生活更轻松。第二个好处是,你的应用程序通过允许逆转不平凡的工作流(不仅仅是错误)来反映现实生活。

    我曾经写过一个应用程序,没有使用一个对话框或确认消息。这需要一些认真的思考,而且比使用确认类型的消息更难实现。但根据最终用户的说法,最终的结果还是相当不错的。

        3
  •  39
  •   Cody Gray Felix    15 年前

    不如:

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

    这样,您就消除了数字协议的困难,并最终为用户提供了一条更短、更直接的错误消息作为奖励。我们都知道 users don't read error messages anyway . 它们越短,就越有可能至少 一瞥 在课文上。

    或者,利用用户不读取错误消息的知识,您可以采用不同的方法来处理此问题。完全跳过确认消息,只需提供一个撤销功能,不管删除了什么,它都能正常工作。大多数用户已经习惯于在发现某个操作不是他们想要的时候撤销它,并且很可能会发现这种行为比不得不处理另一个烦人的弹出窗口更自然。

        4
  •  20
  •   Berin Loritsch    15 年前

    那么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
  •  12
  •   Jens    15 年前

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

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

    然后将它们输入字符串

    if(noofitemsselected == 1)
        messageTemplate = MessageResources.DELETE_SINGLE;
    else
        messageTemplate = MessageResources.DELETE_MULTI;
    
    string message = String.Format(messageTemplate, noofitemsselected)
    

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

        6
  •  11
  •   gdejohn    15 年前

    你可以用不同的措辞来回避这个问题。

    string message = "The number of selected items is " + noofitemsselected + ". Are you sure you want to delete everything in this selection?";
    
        7
  •  10
  •   Dan Puzey    15 年前

    我建议的第一件事是:使用 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
  •  7
  •   smirkingman    15 年前

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

    普通男性:

    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种性别(黄斑、阴性和中性)。

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

        9
  •  5
  •   Davor Lucic    15 年前

    为了能够有多重的消息,这将是可能的地方化适当,我的意见是,这将是明智的,首先创造一个间接层之间的数字和消息。

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

    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 并插入 带着这个信息。

    这个过于简单和天真的例子,但我真的没有看到任何其他明智的方法来做到这一点,并能够为L10N和I18N提供良好的支持。

        10
  •  5
  •   mwolfe02    15 年前

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

    是的,这个解决方案忽略了非英语的语言。这是否重要取决于你的要求。

        11
  •  4
  •   thumbmunkeys    15 年前

    你可以自动生成复数。 plural generator .

    有关复数生成规则,请参见 wikipedia

    string msg = "Do you want to delete " + numItems + GetPlural(" item", numItems) + "?";
    
        12
  •  4
  •   Danosaure    15 年前

    不如用一种更通用的方法。避免第二句中的复数:

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

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

        13
  •  4
  •   Jay    15 年前

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

    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;
    }
    
        14
  •  4
  •   Ken Bloom    15 年前

    国际化

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

    ngettext 函数并在源代码中提供两条英语消息。Gettext将提供基础设施,以便在转换为其他语言时从其他(可能更多)消息中进行选择。见 http://www.gnu.org/software/hello/manual/gettext/Plural-forms.html 对于GNU gettext的复数支持的完整描述。

    GNU Gettext在LGPL下面。 恩盖特文本 被命名为 GettextResourceManager.GetPluralString 在Gettext的C口。

    (如果您不需要本地化支持,并且不想立即使用Gettext,那么编写自己的函数来为英语执行此操作,并通过 两条完整消息 这样,如果以后需要l10n,可以通过重写单个函数进行添加。)

        15
  •  3
  •   Pavel Morshenyuk    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
  •  3
  •   Yogesh    15 年前

    第一个问题,我是说复数,你可以用 Inflector .

    对于第二种情况,可以使用名称为ToPronounString的字符串表示扩展。

        17
  •  3
  •   Jamie Dixon    15 年前

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

    自从它在StackOverflow上再次出现,我想宇宙告诉我要在产生一个像样的解决方案上大发雷霆。

    我很快就把一些东西组合在一起,但它绝不是完美的,尽管它可能有用,或者引发一些讨论/发展。

    此代码基于可以有3条消息的思想。一个用于零项,一个用于一项,一个用于多个项,遵循以下结构:

    单属性名称
    单属性名称
    单属性名称复数

    为了模仿资源类,我创建了一个内部类来进行测试。我还没有用实际的资源文件测试过,所以我还没有看到完整的结果。

    下面是代码(目前我已经包含了一些泛型,我知道我可以将第三个参数简单地指定为一个类型,而第二个参数是一个字符串,我认为有一种方法可以将这两个参数组合成更好的类型,但我会在有空的时候再来讨论这个问题。

        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
  •  2
  •   Illuminati    15 年前

    在我看来,你的第一个解决方案是最合适的。我这么说的原因是,如果您需要应用程序支持多种语言,那么第二个选项可能会很费劲。第一种方法很容易使文本本地化,而无需太多努力。

        19
  •  2
  •   Peter Alexandr Nikitin    15 年前

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

        20
  •  2
  •   ndp    15 年前

    我要看你想要多好的信息。从最简单到最困难:

    1. 重新编写错误消息以避免多重化。不是对你的用户好,而是更快。

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

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

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

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

        21
  •  2
  •   John Alexiou    15 年前

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


    (千日元)

        22
  •  2
  •   Andreas Bonini    15 年前

    就像在魔兽世界一样:

    BILLING_NAG_WARNING = "Your play time expires in %d |4minute:minutes;";
    
        23
  •  0
  •   Jonas Elfström    15 年前

    它变短了一点

    string message = "Are you sure you want to delete " + noofitemsselected + " item" + (noofitemsselected>1 ? "s" : "") + "?";
    
        24
  •  0
  •   supercat    15 年前

    我没有提到的一种方法是使用替换/选择标记(例如,类似于“You are about to squash{0}[?我({0}=1):/cactus/cacti/]。(换句话说,有一个类似格式的表达式,根据参数0(作为整数)是否等于1来指定替换)。我以前在.net中见过这样的标记;我不知道.net中对它们有什么标准,也不知道格式化它们的最佳方式。

        25
  •  0
  •   Eran Medan    15 年前

    我想开箱即用,这里所有的建议要么做多元化(并担心超过一个层次的多元化,性别等),要么根本不用它,提供一个很好的撤销。

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

    或者,在Kinekt/Move/Wii的3D世界里——想象一下选择文件,将手移到删除按钮,然后被告知将手移到头顶上方进行确认(使用与我之前提到的相同的视觉符号)。不是让你删除3个文件?它将显示3个文件,并显示一个半透明的红色X,告诉你做一些事情来确认。

    推荐文章