多个用户消息

时间:2010-11-23 08:34:20

标签: c# string localization pluralize

很多时候,当生成要向用户显示的消息时,该消息将包含一些我希望通知客户的某些

我举一个例子:客户已选择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") + "?";

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

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

修改

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

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

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

25 个答案:

答案 0 :(得分:94)

您可以通过删除没有任何消息的项目并为用户提供非常好的撤消功能来避免所有这些混乱的数据。 Users never read anything。无论如何,您should build a good Undo facility作为计划的一部分。

创建全面的撤消功能时,您实际上可以获得2个好处。第一个好处是让用户改变错误并尽量减少阅读,从而使用户的生活更轻松。第二个好处是,您的应用程序通过允许逆转非平凡的工作流程(而不仅仅是错误)来反映现实生活。

我曾经在不使用单个对话框或确认消息的情况下编写了应用程序。它花了一些认真的思考,并且比使用确认类型的消息更难实现。但最终结果是根据其最终用户使用相当不错。

答案 1 :(得分: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)"。在编写企业代码时,你真的不必给任何人留下深刻的印象。但我建议不要为公开使用的应用程序执行此操作,因为这会给人一种程序员懒惰的印象,从而降低他们对应用程序质量的看法。相信我,像这件小事。

答案 2 :(得分:39)

如何:

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

通过这种方式,您可以消除号码协议的困难,最终为用户提供更短,更准确的错误消息作为奖励。我们都知道users don't read error messages anyway。它们越短,它们就越有可能至少一瞥文本

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

答案 3 :(得分: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捆绑包,并且您可以为没有复数或单数词概念的语言(如日语)提供正确的翻译。

答案 4 :(得分: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消息都在一个位置。

答案 5 :(得分:11)

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

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

答案 6 :(得分: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}}" />

答案 7 :(得分: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种性别(maculine,女性和中性)。

如果您的目标不仅仅是处理英语,那么您需要注意。

答案 8 :(得分: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."

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

答案 9 :(得分: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提供良好的支持。

答案 10 :(得分:4)

如何更通用的方式。避免在第二句中复数:

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

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

答案 11 :(得分:4)

您可以自动生成复数,请参阅例如。 plural generator

对于复数生成规则,请参阅wikipedia

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

答案 12 :(得分: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,可以通过重写单个函数来添加。)

答案 13 :(得分: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,“孩子”,“孩子”)等。

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

我觉得你可以为最简单的情况选择最后一个参数:

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 :(得分:3)

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

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

答案 15 :(得分: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?";

答案 16 :(得分:3)

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

自从它在StackOverflow上再次出现以来,我认为宇宙告诉我要有一个好的解决方案。

我很快将一些东西放在一起,但这并不完美,但它可能会被使用或激发一些讨论/发展。

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

singlePropertyName
singlePropertyName_Zero
singlePropertyName_Plural

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

这是代码(目前我已经包含了一些泛型,我知道我可以将第三个参数简单地指定为Type而第二个参数是一个字符串,我认为有一种方法可以将这两个参数组合成更好的东西但是当我有空闲时,我会回到那里。

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

答案 17 :(得分:2)

为什么要呈现用户可以理解的消息?这违背了40年的编程历史。 Nooooo,我们有一件好事,不要用可理解的消息破坏它。


(J / K)

答案 18 :(得分:2)

就像在“魔兽世界”中所做的那样:

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

答案 19 :(得分:2)

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

答案 20 :(得分:2)

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

  1. 重写您的错误消息以避免多元化。对您的用户不太好,但速度更快。

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

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

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

  5. 您使用哪个选项取决于您的产品目标。 - ndp

答案 21 :(得分:2)

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

答案 22 :(得分:0)

我未提及的一种方法是使用替换/选择标记(例如“你即将压缩{0} [?i({0} = 1)”:/ cactus / cacti / ]“。(换句话说,有一个类似格式的表达式,根据参数零,取整数,等于1来指定替换。)我看过在.net之前使用的这些标签;我不是在.net中了解它们的任何标准,我也不知道格式化它们的最佳方法。

答案 23 :(得分:0)

稍微缩短一点
string message = "Are you sure you want to delete " + noofitemsselected + " item" + (noofitemsselected>1 ? "s" : "") + "?";

答案 24 :(得分:0)

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

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

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