One difficulty with such messages (setting aside the localization problems which I won't treat here) is to express a quantity.
Way too often, I see messages like:
The script processed 1 lines.
Obviously, this isn't correct English.
So the lazy programmer corrects the message:
The script processed 1 line(s).
This is uglier!
So he puts a test, but since it is repetitive, it puts it in a function:
The script processed 1 line.
The script processed 10 lines.
That's better. Then he uses his function elsewhere:
10 appendixs were created.
Ouch!
At my job, I found a Java function trying to address this problem, but only treated the nouns finishing by 'y': days but tries, guys but spies, etc.
I saw some limitations, and after a quick search on the Internet, I saw many more exceptions!
So I rewrote the function and improved a nearby function addressing the problem of ordinal numbers (1st, 10th...).
I thought it would be nice to provide these functions to AutoHotkey users, so here there are:
/* EnglishMessages.ahk Functions to format properly messages in English. // by Philippe Lhoste <PhiLho(a)GMX.net> http://Phi.Lho.free.fr // File/Project history: 1.00.000 -- 2006/03/23 (PL) -- Creation. */ /* // Get the numerical ordinal number string: // 1 => "1st" // 2 => "2nd" // 3 => "3rd" // 4 => "4th" // ... // http://home.comcast.net/~igpl/NWA.html */ GetNumericalOrdinalNumber@English(_number) { local ret, suffix ret := _number If (_number > 0) { If (Mod(_number, 100) > 3 && Mod(_number, 100) < 20) suffix := "th" Else If (Mod(_number, 10) = 1) suffix := "st" Else If (Mod(_number, 10) = 2) suffix := "nd" Else If (Mod(_number, 10) = 3) suffix := "rd" Else suffix := "th" ret = %ret%%suffix% } Return ret } /* // Computes the plural form of a noun. // http://www2.gsu.edu/~wwwesl/egw/pluralsn.htm // I skipped some rare cases... // Also there are some exceptions (zoo/zoos, corpus/corpora) // and some really irregular names: // mouse -> mice, child -> children, die -> dice, etc. // taken in account by an optional parameter. */ GetPluralName@English(_count, _name, _pluralName="") { local len, lastChar, previousChar _count := _count . " " If (_count = 1 || _count < 0) { ; Singular ; In English, we write: 0 foos, 1 foo, 2 foos, etc. Return _count . _name } if (_pluralName != "") { Return _count . _pluralName } len := StrLen(_name) StringRight lastChar, _name, 1 previousChar = ? If (len > 1) { StringMid previousChar, _name, len - 1, 1 } If (lastChar = "y") { If (previousChar != "a" && previousChar != "e" && previousChar != "o" && previousChar != "u") { ; Not a voyel ; baby/babies, spy/spies, try/tries StringLeft _name, _name, len - 1 Return _count . _name . "ies" } ; days, preys, boys, guys: fall through } Else If ((previousChar = "e" || previousChar = "i") && lastChar = "x") { ; appendix/appendices, index/indices StringLeft _name, _name, len - 2 Return _count . _name . "ices" } Else If (previousChar = "i" && lastChar = "s") { ; analysis/analyses, basis/bases, parenthesis/parentheses StringLeft _name, _name, len - 2 Return _count . _name . "es" } Else If (previousChar = "f" && lastChar = "e") { ; knife/knives, wife/wives StringLeft _name, _name, len - 2 Return _count . _name . "ves" } Else If (previousChar = "o" && lastChar = "n" || previousChar = "u" && lastChar = "m") { ; criterion/criteria, phenomenon/phenomena ; datum/data, curriculum/curricula StringLeft _name, _name, len - 2 Return _count . _name . "a" } Else If (previousChar = "u" && lastChar = "s") { ; focus/foci, stimulus/stimuli ; but also ; corpus/corpora, genus/genera, left here... StringLeft _name, _name, len - 2 Return _count . _name . "i" } Else If (lastChar = "f") { ; shelf/shelves, wolf/wolves, half/halves StringLeft _name, _name, len - 1 Return _count . _name . "ves" } Else If (lastChar = "a") { ; formula/formulae, antenna/antennae Return _count . _name . "e" } Else If (lastChar = "s" || lastChar = "z" || lastChar = "x" || lastChar = "o" || (previousChar = "s" || previousChar = "c") && lastChar = "h") { ; glass/glasses, buzz/buzzes, box/boxes, bush/bushes, switch/switches ; potato/potatoes, echo/echoes, hero/heroes ; Exception: studio/studios, piano/pianos, ; kangaroo/kangaroos, zoo/zoos, ; to be treated with the optional parameter Return _count . _name . "es" } ; Last resort, the most common one... Return _count . _name . "s" } ; Some test script, run only if the file is ran standalone If (A_ScriptName = "EnglishMessages.ahk") { appName = English Messages testNames = ( Join| baby|spy|try|day|prey|boy|guy glass|buzz|box|bush|switch|beach potato|echo|hero appendix|index|shelf|wolf|knife|wife|half analysis|basis|parenthesis formula|antenna|criterion|phenomenon|datum|curriculum focus|stimulus ) res = Loop Parse, testNames, | { res := res . GetPluralName@English(1, A_LoopField) . " / " res := res . GetPluralName@English(2, A_LoopField) . "`n" } MsgBox %res% res = Loop 35 { res := res . GetNumericalOrdinalNumber@English(A_Index) . ", " } res := res . "`n" Loop 35 { res := res . GetNumericalOrdinalNumber@English(99 + A_Index) . ", " } MsgBox %res% Exit }I don't know if somebody will ever use them, but at least it was fun and instructive to write them!
I didn't dare to write them for French...
Another challenge I can address in both languages is how to write a number in letters.