用户消息中的多个
很多时候,当生成消息展示给用户时,消息中会包含一些我想告诉客户的东西 。
我举个例子:客户从1开始select了一些项目,并点击删除。 现在我想给客户一个确认信息,我想提及他select的项目数量,以便通过select一堆项目来最小化他犯错误的机会,并且当他只想删除其中的一个时点击删除他们。
一种方法是使通用消息如下所示:
int noofitemsselected = SomeFunction(); string message = "You have selected " + noofitemsselected + " item(s). Are you sure you want to delete it/them?";
这里的“问题”是noofitemselected
为1的情况,我们必须写项目而不是项目和它们 。
我的正常解决scheme将是这样的
int noofitemsselected = SomeFunction(); string message = "You have selected " + noofitemsselected + " " + (noofitemsselected==1?"item" : "items") + ". Are you sure you want to delete " + (noofitemsselected==1?"it" : "them") + "?";
如果在代码中有多个引用多个数字,并且实际的消息变得难以阅读,这将变得相当长并且相当令人讨厌。
所以我的问题很简单。 有没有更好的方式来产生这样的消息?
编辑
在我提到消息应该显示在一个消息框内的情况下,我看到很多人都挂了,只是简单地回答了如何避免使用消息框,这一切都是好的。
但请记住,除了消息框之外,复杂性问题也适用于程序中其他地方的文本。 例如,显示在网格中select的行数的网格旁边的标签将具有关于复数化的相同问题。
因此,这基本上适用于大多数从程序以某种方式输出的文本,然后解决scheme就不那么简单,只是改变程序不输出文本:)
如果有机会,无论多么小,这个应用程序将需要翻译成其他语言,然后都是错误的。 正确的做法是:
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?" );
这是因为不同的语言处理多个不同。 有些像马来人甚至没有语法复数,所以string通常是相同的。 分离这两个string可以使以后更容易支持其他语言。
否则,如果这个应用程序是由大众消费,应该是用户友好的,那么第二种方法是可取的。 对不起,但我真的不知道做这个更短的方式。
如果这个应用程序的目的是消费只有你的公司内部然后做快捷方式"item(s)"
东西。 在编写企业代码时,你并不需要留下任何印象。 但我build议不要这样做的公共消费的应用程序,因为这给人的印象是程序员是懒惰,从而降低他们对应用程序质量的意见。 相信我,像这件事的小事。
你可以避免所有这些混乱的复数,只是删除没有任何消息的项目,并给予用户一个非常好的撤销设施。 用户从不读任何东西 。 无论如何,你应该build立一个好的撤销设施作为你的程序的一部分。
当你创build一个全面的撤销工具时,你实际上可以得到两个好处。 第一个好处是允许用户改变错误并减less阅读,从而使用户的生活更轻松。 第二个好处是,您的应用程序通过允许逆转非重要的工作stream程(而不仅仅是错误)来反映真实的生活。
我曾经写过一个应用程序,而不使用单个对话框或确认消息。 它采取了一些认真的想法,比使用确认型消息要难得多。 但是根据最终用户的最终结果是相当不错的。
如何只是:
string message = "Are you sure you want to delete " + noofitemsselected + " item(s)?"
这样,您就可以消除数字协议的困难,并且最终为用户提供更短暂的,更为现实的错误消息。 我们都知道用户不会读错误信息 。 他们越短,他们越可能至less看一眼文字。
或者,用这种知识武装用户不读错误消息,你可以用不同的方式来处理。 完全跳过确认消息,只需提供Just Works的撤消function,而不pipe删除了哪些内容。 大多数用户已经习惯于在发现不是他们想要的东西时撤消某个操作,并且很可能会发现这种行为比处理另一个令人讨厌的popup更自然。
怎么样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捆绑使用,并且可以为没有复数或单数概念的语言(如日语)提供翻译。
我会去不硬编码的消息,但提供两个消息在一个单独的资源文件。 喜欢
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)
我认为这种方法更容易本地化和维护。 所有的用户界面信息将在一个单一的位置。
你可以完全避开这个问题,通过不同的方式来expression这个信息。
string message = "The number of selected items is " + noofitemsselected + ". Are you sure you want to delete everything in this selection?";
我build议的第一件事是:使用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应用程序中,我已经看到了一个系统,其中一个资源string将被pipe道分隔以具有两个消息:单数和复数消息(或者零/单/多消息,甚至)。 一个自定义转换器可以用来parsing这个资源并使用相关的(格式化的)string,所以你的Xaml是这样的:
<TextBlock Text="{Binding numOfItems, Converter={StaticResource c:NumericMessageFormatter}, ConverterParameter={StaticResource s:SuitableMessageTemplate}}" />
对于英语,上面有很多答案。 对于其他语言来说,它更难,因为复数取决于名词和词尾的性别。 法语的一些例子:
普通男性:
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.
不规则的阳刚(完成与'')
Vous avez choisi 1 pays. Voulez-vous vraiment le supprimer. Vous avez choisi 2 pays. Voulez-vous vraiment les supprimer?
在大多数拉丁语言中存在同样的问题,在德语或俄语中存在三种性别(黄斑,女性和中性)。
如果你的目标不仅仅是处理英文,你需要小心。
为了能够拥有可以正确本地化的复数消息,我认为首先在数字和消息之间创build一个间接层是明智的。
例如,使用某种常量来指定要显示的消息。 使用一些会隐藏实现细节的函数来获取消息。
get_message(DELETE_WARNING, quantity)
接下来,创build一个包含可能的消息和变体的字典,并且使变体知道何时应该使用它们。
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?' }
现在您可以简单地find与quantity
相对应的关键字,并用该信息插入quantity
的值。
这个过于简单和天真的例子,但我真的没有看到任何其他的理智的方式来做到这一点,并能够提供良好的支持L10N和I18N。
你必须把下面的函数从VBA翻译成C#,但是你的用法会变成:
int noofitemsselected = SomeFunction(); string message = Pluralize("You have selected # item[s]. Are you sure you want to delete [it/them]?", noofitemsselected);
我有一个在MS Access中使用的VBA函数来完成你正在谈论的内容。 我知道我会因为发布VBA而被黑客入侵,但是无论如何, 该algorithm应该从评论中显而易见:
'---------------------------------------------------------------------------------------' ' 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."
是的,这个解决scheme忽略了不是英语的语言。 是否重要取决于您的要求。
你可以自动生成复数,见例如。 复数发生器 。
对于复数生成规则请参阅维基百科
string msg = "Do you want to delete " + numItems + GetPlural(" item", numItems) + "?";
如何更通用的方式。 避免第二句中的复数:
选定要删除的项目数:noofitemsselected。 你确定?
我发现这样做,把这个数字放在线的末端,这真的很容易被发现。 这个解决scheme可以在任何语言中使用相同的逻辑。
我的一般方法是写一个“单/复数函数”,就像这样:
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")+"!";
这并不是一件好事,但至less它(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; }
国际化
我假设你想要国际化的支持,在这种情况下,不同的语言有不同的复数forms(例如一个2的特殊复数forms,或更复杂的语言,如波兰语),你不能依靠一些简单的模式应用到你的string要解决这个问题。
您可以使用GNU Gettext的ngettext
函数,并在您的源代码中提供两个英文消息。 当翻译成其他语言时,Gettext将提供基础设施来从其他(可能更多)消息中进行select。 请参阅http://www.gnu.org/software/hello/manual/gettext/Plural-forms.html获取GNU gettext复数支持的完整描述。
GNU Gettext在LGPL下。 ngettext
在Gettext的C#端口中被命名为GettextResourceManager.GetPluralString
。
(如果你不需要本地化的支持,并且不想立刻使用Gettext,那就编写你自己的函数来做这个英文的,并且把两个完整的消息传给它,这样如果你需要l10n的话,你可以通过重写一个函数来添加)。
如何写function如
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?";
对于第一个问题,我的意思是Pluralize ,你可以使用Inflector 。
而对于第二个,你可以使用一个名字如ToPronounString的string表示扩展。
昨天我有一个同样的问题。
既然它在StackOverflow上再次出现,我想宇宙在告诉我在生产一个体面的解决scheme方面有一个麻烦。
我很快把一些东西放在一起,但它不是完美的,但它可能是有用的或引发一些讨论/发展。
这个代码是基于可以有3个消息的想法。 一个为零项目,一个为一个项目,一个为多个项目,其结构如下:
singlePropertyName
singlePropertyName_Zero
singlePropertyName_Plural
我创build了一个内部类来testing,以模仿资源类。 我还没有使用实际的资源文件进行testing,所以我还没有看到完整的结果。
这里的代码(目前我已经包含了一些generics,我知道我可以指定第三个参数简单地作为一个types,第二个参数是一个string,我想有一种方法可以将这两个参数组合成更好的东西,当我有一个空闲的时刻,我会回来的。
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(); }
testing资源类:
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(); }
从我的angular度来看,你的第一个解决scheme是最适合的。 为什么我这么说,如果你需要应用程序支持多种语言,第二个选项可以是辛苦的。 使用第一种方法,可以很容易地将文本本地化。
你可以去寻找一个更通用的信息,比如“你确定要删除选定的项目”吗?
我要看你想要的消息有多棒。 从最简单到最难:
-
重新编写错误信息以避免复数。 不如你的用户好,但更快。
-
使用更通用的语言,但仍然包括数字。
-
使用“复数”和变形系统ala Rails,所以你可以说
pluralize(5,'bunch')
并获得5 bunches
。 Rails有一个很好的模式。 -
为了国际化,你需要看看Java提供了什么。 这将支持各种各样的语言,包括具有不同forms的2或3个项目的形容词。 “s”解决scheme非常以英语为中心。
您select哪个选项取决于您的产品目标。 – ndp
你为什么要提出一个用户可以真正理解的信息? 这与40年的编程历史相违背。 Nooooo,我们有一件好事情,不要用可理解的信息来破坏它。
(J / K)
像“魔兽世界”中所做的那样:
BILLING_NAG_WARNING = "Your play time expires in %d |4minute:minutes;";
它缩短了一点
string message = "Are you sure you want to delete " + noofitemsselected + " item" + (noofitemsselected>1 ? "s" : "") + "?";
我没有看到的一种方法是使用replace/select标签(例如,“您即将压扁{0} [?i({0} = 1):/ cactus / cacti /]”。 (换句话说,有一个类似于格式的expression式根据参数零(取作整数)是否等于1来指定replace)。在.net之前的日子里我看到过这样的标签;我没有意识到他们在.net中的标准,我也不知道格式化他们的最佳方式。
我想出了一分钟,这里的所有build议要么是做多元化(并且担心多于一级的多元化,性别等等),要么根本不使用它,并提供一个很好的撤销。
我会去非语言的方式,并使用视觉队列。 例如,想象一个Iphone应用程序,通过擦拭手指来select项目。 在使用主删除button删除它们之前,它将“摇动”所选项目,并向您显示带有V(ok)或X(取消)button的问号标题框。
或者,在Kinekt / Move / Wii的3D世界中 – 设想select文件,将手移至删除button,并被告知将手放在头上以确认(使用与前面提到的相同的视觉符号。要求你删除3个文件吗?它会向你展示3个hover半透明红色X的文件,并告诉你做一些确认。