views:

414

answers:

3

I am building a multi-languages grails website and I need to get the ordinal suffix of a number for several languages like English, French, Spanish, German and Italian.

I believe that this issue is quite common for multi-languages website owners. I have found this article that provides a solution but it is English only.

For instance:

/**
 *@value number 
 *@locale Current locale
 *@returns ordinal suffix for the given number
**/
public static String getOrdinalFor(int value, Locale locale) 

will give the following results:

 assert getOrdinalFor(1, Locale.ENGLISH) == "st"
 assert getOrdinalFor(1, Locale.FRENCH) == "er"
 assert getOrdinalFor(2, Locale.ENGLISH) == "nd"
 assert getOrdinalFor(3, Locale.ENGLISH) == "rd"
 assert getOrdinalFor(4, Locale.ENGLISH) == "th"
 assert getOrdinalFor(4, Locale.FRENCH) == "ème"

Do you know a library (in Java or Groovy) that can help this? Or do you know an algorithm that implements it?

+3  A: 

I think in many languages this approach is impossible as they simply have no concept of ordinals that can be composed of a number with some letters as extension. For "1st" i.e. in german you can only either write "1." or "erster"/"erste"/"erstes" depending of genus of what you are numbering.

x4u
or "ersten" depending on sentence structure
Thilo
I agree that it is complex. Maybe impossible for all languages. But for western languages like English, Spanish, French and German, I am sure that it is possible (and not so difficult to implement). What is surprising is that there is no libraries, AFAIK, that solve partly this common situation...
fabien7474
Yes, try to sidestep this requirement if you can. What do you need these suffixes for, anyway? once you get beyond 1st, 2nd, and 3rd, do you really need additional ones?
Mike Sickler
The multi language systems I have worked on simply had all strings translated by humans, if we were to use an ordinal it would be considered a string. So as each string costs to translate things were changed to make sure we only used what was necessary. I was surprised to find a host of international symbols that we could use instead of strings.
Jeff Beck
A: 

I don't know of any library, but the best algorithm may be to get hold of all possible numbers/locales/ordinals in a Map<Integer, Map<Locale, String>>.

private static Map<Integer, Map<Locale, String>> ordinals = new HashMap<Integer, Map<Locale, String>>();
static {
    Map<Locale, String> ordinal1 = new HashMap<Locale, String>();
    ordinal1.put(Locale.ENGLISH, "st");
    ordinal1.put(Locale.FRENCH, "er");
    ordinals.put(1, ordinal1);
    Map<Locale, String> ordinal2 = new HashMap<Locale, String>();
    ordinal2.put(Locale.ENGLISH, "nd");
    ordinals.put(2, ordinal2);
    Map<Locale, String> ordinal3 = new HashMap<Locale, String>();
    ordinal3.put(Locale.ENGLISH, "rd");
    ordinals.put(3, ordinal3);
    Map<Locale, String> ordinal4 = new HashMap<Locale, String>();
    ordinal4.put(Locale.ENGLISH, "th");
    ordinal4.put(Locale.FRENCH, "ème");
    ordinals.put(4, ordinal4);
}

The getOrdinalFor() could then basically look like (obvious runtime error handling aside):

public static String getOrdinalFor(int value, Locale locale) {
    return ordinals.get(value).get(locale);
}

For other numbers which are not covered by the map, you may consider to countdown the number until a match is found in the map (so that requesting the ordinal for e.g. 5 or higher would return the ordinal for 4 (the firstnext match on countdown)):

public static String getOrdinalFor(int value, Locale locale) {
    Map<Locale, String> ordinal = null;
    for (int i = value; i > 0 && (ordinal == null || !ordinal.containsKey(locale)); i--) {
        ordinal = ordinals.get(i);
    }
    return ordinal != null ? ordinal.get(locale) : null; // Or other default? 
}
BalusC
Given your solution, how do you deal with the following number :2900932 ?
fabien7474
It would just return `th`. The OP can of course also only pick the latest digit from the number to get the ordinal for.
BalusC
All possible numbers in a hashmap? That would be a big map!"you may consider to countdown the value until a match is found in the map" -> this will not work correctly. For example: you would have added 23. If you want the correct one for 24, you have to add 24 too, but if you want the correct one for 33, you will have to add it again. Same for 34, 43, 44, ...
Fortega
@BalusC: for 2900932, its 'nd', however, for 2900912, its 'th'
Fortega
Then just change the code accordingly. I've at least given the OP a **basic kickoff** to get started with. It really can't be the meaning that we are all going to develop a **full fledged solution** for others. That's his own problem/requirement, not ours. I at least don't stop you from doing so :)
BalusC
@BalusC What does OP mean? BTW, I agree with you that the idea is to give clues for a basic kickoff.
fabien7474
Original Poster. Who is, well, by coincidence yourself. I didn't see that at first and the first comment was given in a nitpicking style that I didn't expect it coming from the OP.
BalusC
+1  A: 

Unless you can apply a rule across every language, it is common to try to sidestep complex linguistic issues. You have to consider not only the number and its ordinal, but the context in which it appears and even how position within a sentence might affect translation.

Rather than try to write "1st place", a resource like this is much easier to translate:

#easyToTranslate_en.properties
label.position=Position: {0,number,integer}

Depending on what you're trying to do, you may be able to compromise with a solution like this:

#compromise_en.properties
label.position.first=1st place
label.position.second=2nd place
label.position.third=3rd place
label.position.default=Position: {0,number,integer}

This would require the code reading the resource to perform a case test to choose which resource to load.

McDowell