source: josm/trunk/src/org/openstreetmap/josm/tools/I18n.java@ 13652

Last change on this file since 13652 was 13647, checked in by Don-vip, 6 years ago

see #16204 - Allow to start and close JOSM in WebStart sandbox mode (where every external access is denied). This was very useful to reproduce some very tricky bugs that occured in real life but were almost impossible to diagnose.

  • Property svn:eol-style set to native
File size: 25.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import java.io.BufferedInputStream;
5import java.io.File;
6import java.io.IOException;
7import java.io.InputStream;
8import java.lang.annotation.Retention;
9import java.lang.annotation.RetentionPolicy;
10import java.net.URL;
11import java.nio.charset.StandardCharsets;
12import java.nio.file.InvalidPathException;
13import java.text.MessageFormat;
14import java.util.ArrayList;
15import java.util.Arrays;
16import java.util.Collection;
17import java.util.Comparator;
18import java.util.HashMap;
19import java.util.Locale;
20import java.util.Map;
21import java.util.zip.ZipEntry;
22import java.util.zip.ZipFile;
23
24/**
25 * Internationalisation support.
26 *
27 * @author Immanuel.Scholz
28 */
29public final class I18n {
30
31 /**
32 * This annotates strings which do not permit a clean i18n. This is mostly due to strings
33 * containing two nouns which can occur in singular or plural form.
34 * <br>
35 * No behaviour is associated with this annotation.
36 */
37 @Retention(RetentionPolicy.SOURCE)
38 public @interface QuirkyPluralString {
39 }
40
41 private I18n() {
42 // Hide default constructor for utils classes
43 }
44
45 /**
46 * Enumeration of possible plural modes. It allows us to identify and implement logical conditions of
47 * plural forms defined on <a href="https://help.launchpad.net/Translations/PluralForms">Launchpad</a>.
48 * See <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html">CLDR</a>
49 * for another complete list.
50 * @see #pluralEval
51 */
52 private enum PluralMode {
53 /** Plural = Not 1. This is the default for many languages, including English: 1 day, but 0 days or 2 days. */
54 MODE_NOTONE,
55 /** No plural. Mainly for Asian languages (Indonesian, Chinese, Japanese, ...) */
56 MODE_NONE,
57 /** Plural = Greater than 1. For some latin languages (French, Brazilian Portuguese) */
58 MODE_GREATERONE,
59 /* Special mode for
60 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar">Arabic</a>.*
61 MODE_AR,*/
62 /** Special mode for
63 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#cs">Czech</a>. */
64 MODE_CS,
65 /** Special mode for
66 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#pl">Polish</a>. */
67 MODE_PL,
68 /* Special mode for
69 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ro">Romanian</a>.*
70 MODE_RO,*/
71 /** Special mode for
72 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#lt">Lithuanian</a>. */
73 MODE_LT,
74 /** Special mode for
75 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ru">Russian</a>. */
76 MODE_RU,
77 /** Special mode for
78 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#sk">Slovak</a>. */
79 MODE_SK,
80 /* Special mode for
81 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#sl">Slovenian</a>.*
82 MODE_SL,*/
83 }
84
85 private static volatile PluralMode pluralMode = PluralMode.MODE_NOTONE; /* english default */
86 private static volatile String loadedCode = "en";
87
88
89 private static volatile Map<String, String> strings;
90 private static volatile Map<String, String[]> pstrings;
91 private static Map<String, PluralMode> languages = new HashMap<>();
92 static {
93 //languages.put("ar", PluralMode.MODE_AR);
94 languages.put("ast", PluralMode.MODE_NOTONE);
95 languages.put("bg", PluralMode.MODE_NOTONE);
96 languages.put("be", PluralMode.MODE_RU);
97 languages.put("ca", PluralMode.MODE_NOTONE);
98 languages.put("ca@valencia", PluralMode.MODE_NOTONE);
99 languages.put("cs", PluralMode.MODE_CS);
100 languages.put("da", PluralMode.MODE_NOTONE);
101 languages.put("de", PluralMode.MODE_NOTONE);
102 languages.put("el", PluralMode.MODE_NOTONE);
103 languages.put("en_AU", PluralMode.MODE_NOTONE);
104 languages.put("en_GB", PluralMode.MODE_NOTONE);
105 languages.put("es", PluralMode.MODE_NOTONE);
106 languages.put("et", PluralMode.MODE_NOTONE);
107 //languages.put("eu", PluralMode.MODE_NOTONE);
108 languages.put("fi", PluralMode.MODE_NOTONE);
109 languages.put("fr", PluralMode.MODE_GREATERONE);
110 languages.put("gl", PluralMode.MODE_NOTONE);
111 //languages.put("he", PluralMode.MODE_NOTONE);
112 languages.put("hu", PluralMode.MODE_NOTONE);
113 languages.put("id", PluralMode.MODE_NONE);
114 //languages.put("is", PluralMode.MODE_NOTONE);
115 languages.put("it", PluralMode.MODE_NOTONE);
116 languages.put("ja", PluralMode.MODE_NONE);
117 // fully supported only with Java 8 and later (needs CLDR)
118 languages.put("km", PluralMode.MODE_NONE);
119 languages.put("lt", PluralMode.MODE_LT);
120 languages.put("nb", PluralMode.MODE_NOTONE);
121 languages.put("nl", PluralMode.MODE_NOTONE);
122 languages.put("pl", PluralMode.MODE_PL);
123 languages.put("pt", PluralMode.MODE_NOTONE);
124 languages.put("pt_BR", PluralMode.MODE_GREATERONE);
125 //languages.put("ro", PluralMode.MODE_RO);
126 languages.put("ru", PluralMode.MODE_RU);
127 languages.put("sk", PluralMode.MODE_SK);
128 //languages.put("sl", PluralMode.MODE_SL);
129 languages.put("sv", PluralMode.MODE_NOTONE);
130 //languages.put("tr", PluralMode.MODE_NONE);
131 languages.put("uk", PluralMode.MODE_RU);
132 languages.put("vi", PluralMode.MODE_NONE);
133 languages.put("zh_CN", PluralMode.MODE_NONE);
134 languages.put("zh_TW", PluralMode.MODE_NONE);
135 }
136
137 /**
138 * Translates some text for the current locale.
139 * These strings are collected by a script that runs on the source code files.
140 * After translation, the localizations are distributed with the main program.
141 * <br>
142 * For example, <code>tr("JOSM''s default value is ''{0}''.", val)</code>.
143 * <br>
144 * Use {@link #trn} for distinguishing singular from plural text, i.e.,
145 * do not use {@code tr(size == 1 ? "singular" : "plural")} nor
146 * {@code size == 1 ? tr("singular") : tr("plural")}
147 *
148 * @param text the text to translate.
149 * Must be a string literal. (No constants or local vars.)
150 * Can be broken over multiple lines.
151 * An apostrophe ' must be quoted by another apostrophe.
152 * @param objects the parameters for the string.
153 * Mark occurrences in {@code text} with <code>{0}</code>, <code>{1}</code>, ...
154 * @return the translated string.
155 * @see #trn
156 * @see #trc
157 * @see #trnc
158 */
159 public static String tr(String text, Object... objects) {
160 if (text == null) return null;
161 return MessageFormat.format(gettext(text, null), objects);
162 }
163
164 /**
165 * Translates some text in a context for the current locale.
166 * There can be different translations for the same text within different contexts.
167 *
168 * @param context string that helps translators to find an appropriate
169 * translation for {@code text}.
170 * @param text the text to translate.
171 * @return the translated string.
172 * @see #tr
173 * @see #trn
174 * @see #trnc
175 */
176 public static String trc(String context, String text) {
177 if (context == null)
178 return tr(text);
179 if (text == null)
180 return null;
181 return MessageFormat.format(gettext(text, context), (Object) null);
182 }
183
184 public static String trcLazy(String context, String text) {
185 if (context == null)
186 return tr(text);
187 if (text == null)
188 return null;
189 return MessageFormat.format(gettextLazy(text, context), (Object) null);
190 }
191
192 /**
193 * Marks a string for translation (such that a script can harvest
194 * the translatable strings from the source files).
195 *
196 * For example, <code>
197 * String[] options = new String[] {marktr("up"), marktr("down")};
198 * lbl.setText(tr(options[0]));</code>
199 * @param text the string to be marked for translation.
200 * @return {@code text} unmodified.
201 */
202 public static String marktr(String text) {
203 return text;
204 }
205
206 public static String marktrc(String context, String text) {
207 return text;
208 }
209
210 /**
211 * Translates some text for the current locale and distinguishes between
212 * {@code singularText} and {@code pluralText} depending on {@code n}.
213 * <br>
214 * For instance, {@code trn("There was an error!", "There were errors!", i)} or
215 * <code>trn("Found {0} error in {1}!", "Found {0} errors in {1}!", i, Integer.toString(i), url)</code>.
216 *
217 * @param singularText the singular text to translate.
218 * Must be a string literal. (No constants or local vars.)
219 * Can be broken over multiple lines.
220 * An apostrophe ' must be quoted by another apostrophe.
221 * @param pluralText the plural text to translate.
222 * Must be a string literal. (No constants or local vars.)
223 * Can be broken over multiple lines.
224 * An apostrophe ' must be quoted by another apostrophe.
225 * @param n a number to determine whether {@code singularText} or {@code pluralText} is used.
226 * @param objects the parameters for the string.
227 * Mark occurrences in {@code singularText} and {@code pluralText} with <code>{0}</code>, <code>{1}</code>, ...
228 * @return the translated string.
229 * @see #tr
230 * @see #trc
231 * @see #trnc
232 */
233 public static String trn(String singularText, String pluralText, long n, Object... objects) {
234 return MessageFormat.format(gettextn(singularText, pluralText, null, n), objects);
235 }
236
237 /**
238 * Translates some text in a context for the current locale and distinguishes between
239 * {@code singularText} and {@code pluralText} depending on {@code n}.
240 * There can be different translations for the same text within different contexts.
241 *
242 * @param context string that helps translators to find an appropriate
243 * translation for {@code text}.
244 * @param singularText the singular text to translate.
245 * Must be a string literal. (No constants or local vars.)
246 * Can be broken over multiple lines.
247 * An apostrophe ' must be quoted by another apostrophe.
248 * @param pluralText the plural text to translate.
249 * Must be a string literal. (No constants or local vars.)
250 * Can be broken over multiple lines.
251 * An apostrophe ' must be quoted by another apostrophe.
252 * @param n a number to determine whether {@code singularText} or {@code pluralText} is used.
253 * @param objects the parameters for the string.
254 * Mark occurrences in {@code singularText} and {@code pluralText} with <code>{0}</code>, <code>{1}</code>, ...
255 * @return the translated string.
256 * @see #tr
257 * @see #trc
258 * @see #trn
259 */
260 public static String trnc(String context, String singularText, String pluralText, long n, Object... objects) {
261 return MessageFormat.format(gettextn(singularText, pluralText, context, n), objects);
262 }
263
264 private static String gettext(String text, String ctx, boolean lazy) {
265 int i;
266 if (ctx == null && text.startsWith("_:") && (i = text.indexOf('\n')) >= 0) {
267 ctx = text.substring(2, i-1);
268 text = text.substring(i+1);
269 }
270 if (strings != null) {
271 String trans = strings.get(ctx == null ? text : "_:"+ctx+'\n'+text);
272 if (trans != null)
273 return trans;
274 }
275 if (pstrings != null) {
276 i = pluralEval(1);
277 String[] trans = pstrings.get(ctx == null ? text : "_:"+ctx+'\n'+text);
278 if (trans != null && trans.length > i)
279 return trans[i];
280 }
281 return lazy ? gettext(text, null) : text;
282 }
283
284 private static String gettext(String text, String ctx) {
285 return gettext(text, ctx, false);
286 }
287
288 /* try without context, when context try fails */
289 private static String gettextLazy(String text, String ctx) {
290 return gettext(text, ctx, true);
291 }
292
293 private static String gettextn(String text, String plural, String ctx, long num) {
294 int i;
295 if (ctx == null && text.startsWith("_:") && (i = text.indexOf('\n')) >= 0) {
296 ctx = text.substring(2, i-1);
297 text = text.substring(i+1);
298 }
299 if (pstrings != null) {
300 i = pluralEval(num);
301 String[] trans = pstrings.get(ctx == null ? text : "_:"+ctx+'\n'+text);
302 if (trans != null && trans.length > i)
303 return trans[i];
304 }
305
306 return num == 1 ? text : plural;
307 }
308
309 public static String escape(String msg) {
310 if (msg == null) return null;
311 return msg.replace("\'", "\'\'").replace("{", "\'{\'").replace("}", "\'}\'");
312 }
313
314 private static URL getTranslationFile(String lang) {
315 return I18n.class.getResource("/data/"+lang.replace('@', '-')+".lang");
316 }
317
318 /**
319 * Get a list of all available JOSM Translations.
320 * @return an array of locale objects.
321 */
322 public static Locale[] getAvailableTranslations() {
323 Collection<Locale> v = new ArrayList<>(languages.size());
324 if (getTranslationFile("en") != null) {
325 for (String loc : languages.keySet()) {
326 if (getTranslationFile(loc) != null) {
327 v.add(LanguageInfo.getLocale(loc));
328 }
329 }
330 }
331 v.add(Locale.ENGLISH);
332 Locale[] l = new Locale[v.size()];
333 l = v.toArray(l);
334 Arrays.sort(l, Comparator.comparing(Locale::toString));
335 return l;
336 }
337
338 /**
339 * Determines if a language exists for the given code.
340 * @param code The language code
341 * @return {@code true} if a language exists, {@code false} otherwise
342 */
343 public static boolean hasCode(String code) {
344 return languages.containsKey(code);
345 }
346
347 static void setupJavaLocaleProviders() {
348 // Look up SPI providers first (for JosmDecimalFormatSymbolsProvider).
349 // Enable CLDR locale provider on Java 8 to get additional languages, such as Khmer.
350 // http://docs.oracle.com/javase/8/docs/technotes/guides/intl/enhancements.8.html#cldr
351 // FIXME: This must be updated after we switch to Java 9.
352 // See https://docs.oracle.com/javase/9/docs/api/java/util/spi/LocaleServiceProvider.html
353 try {
354 // Don't call Utils.updateSystemProperty to avoid spurious log at startup
355 System.setProperty("java.locale.providers", "SPI,JRE,CLDR");
356 } catch (SecurityException e) {
357 // Don't call Logging class, it may not be fully initialized yet
358 System.err.println("Unable to set locale providers: " + e.getMessage());
359 }
360 }
361
362 /**
363 * I18n initialization.
364 */
365 public static void init() {
366 setupJavaLocaleProviders();
367
368 /* try initial language settings, may be changed later again */
369 if (!load(LanguageInfo.getJOSMLocaleCode())) {
370 Locale.setDefault(Locale.ENGLISH);
371 }
372 }
373
374 /**
375 * I18n initialization for plugins.
376 * @param source file path/name of the JAR or Zip file containing translation strings
377 * @since 4159
378 */
379 public static void addTexts(File source) {
380 if ("en".equals(loadedCode))
381 return;
382 final ZipEntry enfile = new ZipEntry("data/en.lang");
383 final ZipEntry langfile = new ZipEntry("data/"+loadedCode+".lang");
384 try (
385 ZipFile zipFile = new ZipFile(source, StandardCharsets.UTF_8);
386 InputStream orig = zipFile.getInputStream(enfile);
387 InputStream trans = zipFile.getInputStream(langfile)
388 ) {
389 if (orig != null && trans != null)
390 load(orig, trans, true);
391 } catch (IOException | InvalidPathException e) {
392 Logging.trace(e);
393 }
394 }
395
396 private static boolean load(String l) {
397 if ("en".equals(l) || "en_US".equals(l)) {
398 strings = null;
399 pstrings = null;
400 loadedCode = "en";
401 pluralMode = PluralMode.MODE_NOTONE;
402 return true;
403 }
404 URL en = getTranslationFile("en");
405 if (en == null)
406 return false;
407 URL tr = getTranslationFile(l);
408 if (tr == null || !languages.containsKey(l)) {
409 return false;
410 }
411 try (
412 InputStream enStream = Utils.openStream(en);
413 InputStream trStream = Utils.openStream(tr)
414 ) {
415 if (load(enStream, trStream, false)) {
416 pluralMode = languages.get(l);
417 loadedCode = l;
418 return true;
419 }
420 } catch (IOException e) {
421 // Ignore exception
422 Logging.trace(e);
423 }
424 return false;
425 }
426
427 private static boolean load(InputStream en, InputStream tr, boolean add) {
428 Map<String, String> s;
429 Map<String, String[]> p;
430 if (add) {
431 s = strings;
432 p = pstrings;
433 } else {
434 s = new HashMap<>();
435 p = new HashMap<>();
436 }
437 /* file format:
438 Files are always a group. English file and translated file must provide identical datasets.
439
440 for all single strings:
441 {
442 unsigned short (2 byte) stringlength
443 - length 0 indicates missing translation
444 - length 0xFFFE indicates translation equal to original, but otherwise is equal to length 0
445 string
446 }
447 unsigned short (2 byte) 0xFFFF (marks end of single strings)
448 for all multi strings:
449 {
450 unsigned char (1 byte) stringcount
451 - count 0 indicates missing translations
452 - count 0xFE indicates translations equal to original, but otherwise is equal to length 0
453 for stringcount
454 unsigned short (2 byte) stringlength
455 string
456 }
457 */
458 try {
459 InputStream ens = new BufferedInputStream(en);
460 InputStream trs = new BufferedInputStream(tr);
461 byte[] enlen = new byte[2];
462 byte[] trlen = new byte[2];
463 boolean multimode = false;
464 byte[] str = new byte[4096];
465 for (;;) {
466 if (multimode) {
467 int ennum = ens.read();
468 int trnum = trs.read();
469 if (trnum == 0xFE) /* marks identical string, handle equally to non-translated */
470 trnum = 0;
471 if ((ennum == -1 && trnum != -1) || (ennum != -1 && trnum == -1)) /* files do not match */
472 return false;
473 if (ennum == -1) {
474 break;
475 }
476 String[] enstrings = new String[ennum];
477 for (int i = 0; i < ennum; ++i) {
478 int val = ens.read(enlen);
479 if (val != 2) /* file corrupt */
480 return false;
481 val = (enlen[0] < 0 ? 256+enlen[0] : enlen[0])*256+(enlen[1] < 0 ? 256+enlen[1] : enlen[1]);
482 if (val > str.length) {
483 str = new byte[val];
484 }
485 int rval = ens.read(str, 0, val);
486 if (rval != val) /* file corrupt */
487 return false;
488 enstrings[i] = new String(str, 0, val, StandardCharsets.UTF_8);
489 }
490 String[] trstrings = new String[trnum];
491 for (int i = 0; i < trnum; ++i) {
492 int val = trs.read(trlen);
493 if (val != 2) /* file corrupt */
494 return false;
495 val = (trlen[0] < 0 ? 256+trlen[0] : trlen[0])*256+(trlen[1] < 0 ? 256+trlen[1] : trlen[1]);
496 if (val > str.length) {
497 str = new byte[val];
498 }
499 int rval = trs.read(str, 0, val);
500 if (rval != val) /* file corrupt */
501 return false;
502 trstrings[i] = new String(str, 0, val, StandardCharsets.UTF_8);
503 }
504 if (trnum > 0 && !p.containsKey(enstrings[0])) {
505 p.put(enstrings[0], trstrings);
506 }
507 } else {
508 int enval = ens.read(enlen);
509 int trval = trs.read(trlen);
510 if (enval != trval) /* files do not match */
511 return false;
512 if (enval == -1) {
513 break;
514 }
515 if (enval != 2) /* files corrupt */
516 return false;
517 enval = (enlen[0] < 0 ? 256+enlen[0] : enlen[0])*256+(enlen[1] < 0 ? 256+enlen[1] : enlen[1]);
518 trval = (trlen[0] < 0 ? 256+trlen[0] : trlen[0])*256+(trlen[1] < 0 ? 256+trlen[1] : trlen[1]);
519 if (trval == 0xFFFE) /* marks identical string, handle equally to non-translated */
520 trval = 0;
521 if (enval == 0xFFFF) {
522 multimode = true;
523 if (trval != 0xFFFF) /* files do not match */
524 return false;
525 } else {
526 if (enval > str.length) {
527 str = new byte[enval];
528 }
529 if (trval > str.length) {
530 str = new byte[trval];
531 }
532 int val = ens.read(str, 0, enval);
533 if (val != enval) /* file corrupt */
534 return false;
535 String enstr = new String(str, 0, enval, StandardCharsets.UTF_8);
536 if (trval != 0) {
537 val = trs.read(str, 0, trval);
538 if (val != trval) /* file corrupt */
539 return false;
540 String trstr = new String(str, 0, trval, StandardCharsets.UTF_8);
541 if (!s.containsKey(enstr))
542 s.put(enstr, trstr);
543 }
544 }
545 }
546 }
547 } catch (IOException e) {
548 Logging.trace(e);
549 return false;
550 }
551 if (!s.isEmpty()) {
552 strings = s;
553 pstrings = p;
554 return true;
555 }
556 return false;
557 }
558
559 /**
560 * Sets the default locale (see {@link Locale#setDefault(Locale)} to the local
561 * given by <code>localName</code>.
562 *
563 * Ignored if localeName is null. If the locale with name <code>localName</code>
564 * isn't found the default local is set to <code>en</code> (english).
565 *
566 * @param localeName the locale name. Ignored if null.
567 */
568 public static void set(String localeName) {
569 if (localeName != null) {
570 Locale l = LanguageInfo.getLocale(localeName);
571 if (load(LanguageInfo.getJOSMLocaleCode(l))) {
572 Locale.setDefault(l);
573 } else {
574 if (!"en".equals(l.getLanguage())) {
575 Logging.info(tr("Unable to find translation for the locale {0}. Reverting to {1}.",
576 LanguageInfo.getDisplayName(l), LanguageInfo.getDisplayName(Locale.getDefault())));
577 } else {
578 strings = null;
579 pstrings = null;
580 }
581 }
582 }
583 }
584
585 private static int pluralEval(long n) {
586 switch(pluralMode) {
587 case MODE_NOTONE: /* bg, da, de, el, en, en_GB, es, et, eu, fi, gl, is, it, iw_IL, nb, nl, sv */
588 return (n != 1) ? 1 : 0;
589 case MODE_NONE: /* id, vi, ja, km, tr, zh_CN, zh_TW */
590 return 0;
591 case MODE_GREATERONE: /* fr, pt_BR */
592 return (n > 1) ? 1 : 0;
593 case MODE_CS:
594 return (n == 1) ? 0 : (((n >= 2) && (n <= 4)) ? 1 : 2);
595 //case MODE_AR:
596 // return ((n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((((n % 100) >= 3)
597 // && ((n % 100) <= 10)) ? 3 : ((((n % 100) >= 11) && ((n % 100) <= 99)) ? 4 : 5)))));
598 case MODE_PL:
599 return (n == 1) ? 0 : (((((n % 10) >= 2) && ((n % 10) <= 4))
600 && (((n % 100) < 10) || ((n % 100) >= 20))) ? 1 : 2);
601 //case MODE_RO:
602 // return ((n == 1) ? 0 : ((((n % 100) > 19) || (((n % 100) == 0) && (n != 0))) ? 2 : 1));
603 case MODE_LT:
604 return ((n % 10) == 1) && ((n % 100) != 11) ? 0 : (((n % 10) >= 2)
605 && (((n % 100) < 10) || ((n % 100) >= 20)) ? 1 : 2);
606 case MODE_RU:
607 return (((n % 10) == 1) && ((n % 100) != 11)) ? 0 : (((((n % 10) >= 2)
608 && ((n % 10) <= 4)) && (((n % 100) < 10) || ((n % 100) >= 20))) ? 1 : 2);
609 case MODE_SK:
610 return (n == 1) ? 1 : (((n >= 2) && (n <= 4)) ? 2 : 0);
611 //case MODE_SL:
612 // return (((n % 100) == 1) ? 1 : (((n % 100) == 2) ? 2 : ((((n % 100) == 3)
613 // || ((n % 100) == 4)) ? 3 : 0)));
614 }
615 return 0;
616 }
617}
Note: See TracBrowser for help on using the repository browser.