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

Last change on this file was 19479, checked in by stoecker, 2 months ago

i18n update, add lo, lv, nn

  • Property svn:eol-style set to native
File size: 32.6 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.HashMap;
15import java.util.Locale;
16import java.util.Map;
17import java.util.regex.Matcher;
18import java.util.regex.Pattern;
19import java.util.stream.Stream;
20import java.util.zip.ZipEntry;
21import java.util.zip.ZipFile;
22
23import org.openstreetmap.josm.data.osm.TagMap;
24
25/**
26 * Internationalisation support.
27 *
28 * @author Immanuel.Scholz
29 */
30public final class I18n {
31
32 private static final String CORE_TRANS_DIRECTORY = "/data/";
33 private static final String PLUGIN_TRANS_DIRECTORY = "data/";
34
35 /**
36 * This annotates strings which do not permit a clean i18n. This is mostly due to strings
37 * containing two nouns which can occur in singular or plural form.
38 * <br>
39 * No behaviour is associated with this annotation.
40 */
41 @Retention(RetentionPolicy.SOURCE)
42 public @interface QuirkyPluralString {
43 }
44
45 private I18n() {
46 // Hide default constructor for utils classes
47 }
48
49 /**
50 * Enumeration of possible plural modes. It allows us to identify and implement logical conditions of
51 * plural forms defined on <a href="https://help.launchpad.net/Translations/PluralForms">Launchpad</a>.
52 * See <a href="https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html">CLDR</a>
53 * for another complete list.
54 * @see #pluralEval
55 */
56 private enum PluralMode {
57 /** Plural = Not 1. This is the default for many languages, including English: 1 day, but 0 days or 2 days. */
58 MODE_NOTONE,
59 /** No plural. Mainly for Asian languages (Indonesian, Chinese, Japanese, ...) */
60 MODE_NONE,
61 /** Plural = Greater than 1. For some latin languages (French, Brazilian Portuguese) */
62 MODE_GREATERONE,
63 /* Special mode for
64 * <a href="https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar">Arabic</a>.*/
65 MODE_AR,
66 /** Special mode for
67 * <a href="https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#cs">Czech</a>. */
68 MODE_CS,
69 /** Special mode for
70 * <a href="https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#pl">Polish</a>. */
71 MODE_PL,
72 /* Special mode for
73 * <a href="https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ro">Romanian</a>.*
74 MODE_RO,*/
75 /** Special mode for
76 * <a href="https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#lt">Lithuanian</a>. */
77 MODE_LT,
78 /** Special mode for
79 * <a href="https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#lv">Latvian</a>. */
80 MODE_LV,
81 /** Special mode for
82 * <a href="https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ru">Russian</a>. */
83 MODE_RU,
84 /** Special mode for
85 * <a href="https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#sk">Slovak</a>. */
86 MODE_CY,
87 /** Special mode for
88 * <a href="https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#cy">Welsh</a>. */
89 MODE_SK,
90 /* Special mode for
91 * <a href="https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#sl">Slovenian</a>.*
92 MODE_SL,*/
93 }
94
95 private static volatile PluralMode pluralMode = PluralMode.MODE_NOTONE; /* english default */
96 private static volatile String loadedCode = "en";
97
98 /** Map (english/locale) of singular strings **/
99 private static volatile Map<String, String> strings;
100 /** Map (english/locale) of plural strings **/
101 private static volatile Map<String, String[]> pstrings;
102 private static final Locale originalLocale = Locale.getDefault();
103 private static final Map<String, PluralMode> languages = new HashMap<>();
104 // NOTE: check also WikiLanguage handling in LanguageInfo.java when adding new languages
105 static {
106 languages.put("ar", PluralMode.MODE_AR);
107 languages.put("ast", PluralMode.MODE_NOTONE);
108 languages.put("bg", PluralMode.MODE_NOTONE);
109 languages.put("be", PluralMode.MODE_RU);
110 languages.put("ca", PluralMode.MODE_NOTONE);
111 languages.put("ca@valencia", PluralMode.MODE_NOTONE);
112 languages.put("cs", PluralMode.MODE_CS);
113 languages.put("cy", PluralMode.MODE_CY);
114 languages.put("da", PluralMode.MODE_NOTONE);
115 languages.put("de", PluralMode.MODE_NOTONE);
116 languages.put("el", PluralMode.MODE_NOTONE);
117 languages.put("en_AU", PluralMode.MODE_NOTONE);
118 languages.put("en_CA", PluralMode.MODE_NOTONE);
119 languages.put("en_GB", PluralMode.MODE_NOTONE);
120 languages.put("eo", PluralMode.MODE_NOTONE);
121 languages.put("es", PluralMode.MODE_NOTONE);
122 languages.put("et", PluralMode.MODE_NOTONE);
123 //languages.put("eu", PluralMode.MODE_NOTONE);
124 languages.put("fa", PluralMode.MODE_NONE);
125 languages.put("fi", PluralMode.MODE_NOTONE);
126 languages.put("fo", PluralMode.MODE_NOTONE);
127 languages.put("fr", PluralMode.MODE_GREATERONE);
128 languages.put("gl", PluralMode.MODE_NOTONE);
129 //languages.put("he", PluralMode.MODE_NOTONE);
130 languages.put("hu", PluralMode.MODE_NOTONE);
131 languages.put("id", PluralMode.MODE_NONE);
132 languages.put("is", PluralMode.MODE_NOTONE);
133 languages.put("it", PluralMode.MODE_NOTONE);
134 languages.put("ja", PluralMode.MODE_NONE);
135 languages.put("ko", PluralMode.MODE_NONE);
136 languages.put("km", PluralMode.MODE_NONE);
137 languages.put("lt", PluralMode.MODE_LT);
138 languages.put("lo", PluralMode.MODE_NONE);
139 languages.put("lv", PluralMode.MODE_LV);
140 languages.put("mr", PluralMode.MODE_NOTONE);
141 languages.put("nb", PluralMode.MODE_NOTONE);
142 languages.put("nl", PluralMode.MODE_NOTONE);
143 languages.put("nn", PluralMode.MODE_NOTONE);
144 languages.put("pl", PluralMode.MODE_PL);
145 languages.put("pt", PluralMode.MODE_NOTONE);
146 languages.put("pt_BR", PluralMode.MODE_GREATERONE);
147 //languages.put("ro", PluralMode.MODE_RO);
148 languages.put("ru", PluralMode.MODE_RU);
149 languages.put("sk", PluralMode.MODE_SK);
150 //languages.put("sl", PluralMode.MODE_SL);
151 languages.put("sr@latin", PluralMode.MODE_RU);
152 languages.put("sv", PluralMode.MODE_NOTONE);
153 languages.put("tr", PluralMode.MODE_NONE);
154 languages.put("uk", PluralMode.MODE_RU);
155 //languages.put("vi", PluralMode.MODE_NONE);
156 languages.put("zh_CN", PluralMode.MODE_NONE);
157 languages.put("zh_TW", PluralMode.MODE_NONE);
158 }
159
160 private static final String HIRAGANA = "hira";
161 private static final String KATAKANA = "kana";
162 private static final String LATIN = "latn";
163 private static final String PINYIN = "pinyin";
164 private static final String LATINPINYIN = "latn-pinyin";
165 private static final String ROMAJI = "rm";
166 private static final String HANI = "hani";
167 private static final String HANT = "hant";
168 private static final String HANS = "hans";
169 private static final String BOPOMOFO = "bopo";
170
171 // Matches ISO-639 two and three letters language codes + scripts
172 private static final Pattern LANGUAGE_NAMES = Pattern.compile(
173 "name:(\\p{Lower}{2,3})(?:[-_](?i:(" + String.join("|", HIRAGANA, KATAKANA,
174 LATIN, PINYIN, LATINPINYIN, ROMAJI, HANI, HANS, HANT, BOPOMOFO) + ")))?");
175
176 private static String format(String text, Object... objects) {
177 if (objects.length == 0 && !text.contains("'")) {
178 return text;
179 }
180 try {
181 return MessageFormat.format(text, objects);
182 } catch (InvalidPathException e) {
183 System.err.println("!!! Unable to format '" + text + "': " + e.getMessage());
184 e.printStackTrace();
185 return null;
186 }
187 }
188
189 /**
190 * Translates some text for the current locale.
191 * These strings are collected by a script that runs on the source code files.
192 * After translation, the localizations are distributed with the main program.
193 * <br>
194 * For example, <code>tr("JOSM''s default value is ''{0}''.", val)</code>.
195 * <br>
196 * Use {@link #trn} for distinguishing singular from plural text, i.e.,
197 * do not use {@code tr(size == 1 ? "singular" : "plural")} nor
198 * {@code size == 1 ? tr("singular") : tr("plural")}
199 *
200 * @param text the text to translate.
201 * Must be a string literal. (No constants or local vars.)
202 * Can be broken over multiple lines.
203 * An apostrophe ' must be quoted by another apostrophe.
204 * @param objects the parameters for the string.
205 * Mark occurrences in {@code text} with <code>{0}</code>, <code>{1}</code>, ...
206 * @return the translated string.
207 * @see #trn
208 * @see #trc
209 * @see #trnc
210 */
211 public static String tr(String text, Object... objects) {
212 if (text == null) return null;
213 return format(gettext(text, null), objects);
214 }
215
216 /**
217 * Translates some text in a context for the current locale.
218 * There can be different translations for the same text within different contexts.
219 *
220 * @param context string that helps translators to find an appropriate
221 * translation for {@code text}.
222 * @param text the text to translate.
223 * @return the translated string.
224 * @see #tr
225 * @see #trn
226 * @see #trnc
227 */
228 public static String trc(String context, String text) {
229 if (context == null)
230 return tr(text);
231 if (text == null)
232 return null;
233 return format(gettext(text, context), (Object) null);
234 }
235
236 public static String trcLazy(String context, String text) {
237 if (context == null)
238 return tr(text);
239 if (text == null)
240 return null;
241 return format(gettextLazy(text, context), (Object) null);
242 }
243
244 /**
245 * Marks a string for translation (such that a script can harvest
246 * the translatable strings from the source files).
247 *
248 * For example, <code>
249 * String[] options = new String[] {marktr("up"), marktr("down")};
250 * lbl.setText(tr(options[0]));</code>
251 * @param text the string to be marked for translation.
252 * @return {@code text} unmodified.
253 */
254 public static String marktr(String text) {
255 return text;
256 }
257
258 public static String marktrc(String context, String text) {
259 return text;
260 }
261
262 /**
263 * Translates some text for the current locale and distinguishes between
264 * {@code singularText} and {@code pluralText} depending on {@code n}.
265 * <br>
266 * For instance, {@code trn("There was an error!", "There were errors!", i)} or
267 * <code>trn("Found {0} error in {1}!", "Found {0} errors in {1}!", i, Integer.toString(i), url)</code>.
268 *
269 * @param singularText the singular text to translate.
270 * Must be a string literal. (No constants or local vars.)
271 * Can be broken over multiple lines.
272 * An apostrophe ' must be quoted by another apostrophe.
273 * @param pluralText the plural text to translate.
274 * Must be a string literal. (No constants or local vars.)
275 * Can be broken over multiple lines.
276 * An apostrophe ' must be quoted by another apostrophe.
277 * @param n a number to determine whether {@code singularText} or {@code pluralText} is used.
278 * @param objects the parameters for the string.
279 * Mark occurrences in {@code singularText} and {@code pluralText} with <code>{0}</code>, <code>{1}</code>, ...
280 * @return the translated string.
281 * @see #tr
282 * @see #trc
283 * @see #trnc
284 */
285 public static String trn(String singularText, String pluralText, long n, Object... objects) {
286 return format(gettextn(singularText, pluralText, null, n), objects);
287 }
288
289 /**
290 * Translates some text in a context for the current locale and distinguishes between
291 * {@code singularText} and {@code pluralText} depending on {@code n}.
292 * There can be different translations for the same text within different contexts.
293 *
294 * @param context string that helps translators to find an appropriate
295 * translation for {@code text}.
296 * @param singularText the singular text to translate.
297 * Must be a string literal. (No constants or local vars.)
298 * Can be broken over multiple lines.
299 * An apostrophe ' must be quoted by another apostrophe.
300 * @param pluralText the plural text to translate.
301 * Must be a string literal. (No constants or local vars.)
302 * Can be broken over multiple lines.
303 * An apostrophe ' must be quoted by another apostrophe.
304 * @param n a number to determine whether {@code singularText} or {@code pluralText} is used.
305 * @param objects the parameters for the string.
306 * Mark occurrences in {@code singularText} and {@code pluralText} with <code>{0}</code>, <code>{1}</code>, ...
307 * @return the translated string.
308 * @see #tr
309 * @see #trc
310 * @see #trn
311 */
312 public static String trnc(String context, String singularText, String pluralText, long n, Object... objects) {
313 return format(gettextn(singularText, pluralText, context, n), objects);
314 }
315
316 private static String gettext(String text, String ctx, boolean lazy) {
317 int i;
318 if (ctx == null && text.startsWith("_:") && (i = text.indexOf('\n')) >= 0) {
319 ctx = text.substring(2, i-1);
320 text = text.substring(i+1);
321 }
322 if (strings != null) {
323 String trans = strings.get(ctx == null ? text : "_:"+ctx+'\n'+text);
324 if (trans != null)
325 return trans;
326 }
327 if (pstrings != null) {
328 i = pluralEval(1);
329 String[] trans = pstrings.get(ctx == null ? text : "_:"+ctx+'\n'+text);
330 if (trans != null && trans.length > i)
331 return trans[i];
332 }
333 return lazy ? gettext(text, null) : text;
334 }
335
336 private static String gettext(String text, String ctx) {
337 return gettext(text, ctx, false);
338 }
339
340 /* try without context, when context try fails */
341 private static String gettextLazy(String text, String ctx) {
342 return gettext(text, ctx, true);
343 }
344
345 private static String gettextn(String text, String plural, String ctx, long num) {
346 int i;
347 if (ctx == null && text.startsWith("_:") && (i = text.indexOf('\n')) >= 0) {
348 ctx = text.substring(2, i-1);
349 text = text.substring(i+1);
350 }
351 if (pstrings != null) {
352 i = pluralEval(num);
353 String[] trans = pstrings.get(ctx == null ? text : "_:"+ctx+'\n'+text);
354 if (trans != null && trans.length > i)
355 return trans[i];
356 }
357
358 return num == 1 ? text : plural;
359 }
360
361 /**
362 * Escapes the special i18n characters <code>'{}</code> with quotes.
363 * @param msg unescaped string
364 * @return escaped string
365 * @since 4477
366 */
367 public static String escape(String msg) {
368 if (msg == null) return null;
369 return msg.replace("\'", "\'\'").replace("{", "\'{\'").replace("}", "\'}\'");
370 }
371
372 private static URL getTranslationFile(String lang) {
373 return I18n.class.getResource(CORE_TRANS_DIRECTORY + lang.replace('@', '-') + ".lang");
374 }
375
376 /**
377 * Get a list of all available JOSM Translations.
378 * @return an array of locale objects.
379 */
380 public static Stream<Locale> getAvailableTranslations() {
381 Stream<String> languages = Stream.concat(
382 getTranslationFile("en") != null ? I18n.languages.keySet().stream() : Stream.empty(),
383 Stream.of("en"));
384 return languages.filter(loc -> getTranslationFile(loc) != null).map(LanguageInfo::getLocale);
385 }
386
387 /**
388 * Determines if a language exists for the given code.
389 * @param code The language code
390 * @return {@code true} if a language exists, {@code false} otherwise
391 */
392 public static boolean hasCode(String code) {
393 return languages.containsKey(code);
394 }
395
396 static String setupJavaLocaleProviders() {
397 // Look up SPI providers first (for JosmDecimalFormatSymbolsProvider).
398 try {
399 try {
400 // First check we're able to open a stream to our own SPI file
401 // Java will fail on Windows if the jar file is in a folder with a space character!
402 I18n.class.getResourceAsStream("/META-INF/services/java.text.spi.DecimalFormatSymbolsProvider").close();
403 // Don't call Utils.updateSystemProperty to avoid spurious log at startup
404 return System.setProperty("java.locale.providers", "SPI,CLDR");
405 } catch (RuntimeException | IOException e) {
406 // Don't call Logging class, it may not be fully initialized yet
407 System.err.println("Unable to set SPI locale provider: " + e.getMessage());
408 }
409 } catch (SecurityException e) {
410 // Don't call Logging class, it may not be fully initialized yet
411 System.err.println("Unable to set locale providers: " + e.getMessage());
412 }
413 try {
414 return System.setProperty("java.locale.providers", "CLDR");
415 } catch (SecurityException e) {
416 // Don't call Logging class, it may not be fully initialized yet
417 System.err.println("Unable to set locale providers: " + e.getMessage());
418 return null;
419 }
420 }
421
422 /**
423 * I18n initialization.
424 */
425 public static void init() {
426 setupJavaLocaleProviders();
427
428 /* try initial language settings, may be changed later again */
429 if (!load(LanguageInfo.getJOSMLocaleCode())) {
430 Locale.setDefault(new Locale("en", Locale.getDefault().getCountry()));
431 }
432 }
433
434 /**
435 * I18n initialization for plugins.
436 * @param source file path/name of the JAR or Zip file containing translation strings
437 * @since 4159
438 */
439 public static void addTexts(File source) {
440 if ("en".equals(loadedCode))
441 return;
442 final ZipEntry enfile = new ZipEntry(PLUGIN_TRANS_DIRECTORY + "en.lang");
443 final ZipEntry langfile = new ZipEntry(PLUGIN_TRANS_DIRECTORY + loadedCode.replace('@', '-') + ".lang");
444 try (
445 ZipFile zipFile = new ZipFile(source, StandardCharsets.UTF_8);
446 InputStream orig = zipFile.getInputStream(enfile);
447 InputStream trans = zipFile.getInputStream(langfile)
448 ) {
449 if (orig != null && trans != null)
450 load(orig, trans, true);
451 } catch (IOException | InvalidPathException e) {
452 Logging.trace(e);
453 }
454 }
455
456 private static boolean load(String l) {
457 if ("en".equals(l) || "en_US".equals(l)) {
458 strings = null;
459 pstrings = null;
460 loadedCode = "en";
461 pluralMode = PluralMode.MODE_NOTONE;
462 return true;
463 }
464 URL en = getTranslationFile("en");
465 if (en == null)
466 return false;
467 URL tr = getTranslationFile(l);
468 if (tr == null || !languages.containsKey(l)) {
469 return false;
470 }
471 try (
472 InputStream enStream = Utils.openStream(en);
473 InputStream trStream = Utils.openStream(tr)
474 ) {
475 if (load(enStream, trStream, false)) {
476 pluralMode = languages.get(l);
477 loadedCode = l;
478 return true;
479 }
480 } catch (IOException e) {
481 // Ignore exception
482 Logging.trace(e);
483 }
484 return false;
485 }
486
487 private static boolean load(InputStream en, InputStream tr, boolean add) {
488 Map<String, String> s;
489 Map<String, String[]> p;
490 if (add) {
491 s = strings;
492 p = pstrings;
493 } else {
494 s = new HashMap<>();
495 p = new HashMap<>();
496 }
497 /* file format:
498 Files are always a group. English file and translated file must provide identical datasets.
499
500 for all single strings:
501 {
502 unsigned short (2 byte) stringlength
503 - length 0 indicates missing translation
504 - length 0xFFFE indicates translation equal to original, but otherwise is equal to length 0
505 string
506 }
507 unsigned short (2 byte) 0xFFFF (marks end of single strings)
508 for all multi strings:
509 {
510 unsigned char (1 byte) stringcount
511 - count 0 indicates missing translations
512 - count 0xFE indicates translations equal to original, but otherwise is equal to length 0
513 for stringcount
514 unsigned short (2 byte) stringlength
515 string
516 }
517 */
518 try {
519 InputStream ens = new BufferedInputStream(en);
520 InputStream trs = new BufferedInputStream(tr);
521 byte[] enlen = new byte[2];
522 byte[] trlen = new byte[2];
523 boolean multimode = false;
524 byte[] str = new byte[4096];
525 for (;;) {
526 if (multimode) {
527 int ennum = ens.read();
528 int trnum = trs.read();
529 if (trnum == 0xFE) /* marks identical string, handle equally to non-translated */
530 trnum = 0;
531 if ((ennum == -1 && trnum != -1) || (ennum != -1 && trnum == -1)) /* files do not match */
532 return false;
533 if (ennum == -1) {
534 break;
535 }
536 String[] enstrings = new String[ennum];
537 for (int i = 0; i < ennum; ++i) {
538 int val = ens.read(enlen);
539 if (val != 2) /* file corrupt */
540 return false;
541 val = (enlen[0] < 0 ? 256+enlen[0] : enlen[0])*256+(enlen[1] < 0 ? 256+enlen[1] : enlen[1]);
542 if (val > str.length) {
543 str = new byte[val];
544 }
545 int rval = ens.read(str, 0, val);
546 if (rval != val) /* file corrupt */
547 return false;
548 enstrings[i] = new String(str, 0, val, StandardCharsets.UTF_8);
549 }
550 String[] trstrings = new String[trnum];
551 for (int i = 0; i < trnum; ++i) {
552 int val = trs.read(trlen);
553 if (val != 2) /* file corrupt */
554 return false;
555 val = (trlen[0] < 0 ? 256+trlen[0] : trlen[0])*256+(trlen[1] < 0 ? 256+trlen[1] : trlen[1]);
556 if (val > str.length) {
557 str = new byte[val];
558 }
559 int rval = trs.read(str, 0, val);
560 if (rval != val) /* file corrupt */
561 return false;
562 trstrings[i] = new String(str, 0, val, StandardCharsets.UTF_8);
563 }
564 if (trnum > 0 && !p.containsKey(enstrings[0])) {
565 p.put(enstrings[0], trstrings);
566 }
567 } else {
568 int enval = ens.read(enlen);
569 int trval = trs.read(trlen);
570 if (enval != trval) /* files do not match */
571 return false;
572 if (enval == -1) {
573 break;
574 }
575 if (enval != 2) /* files corrupt */
576 return false;
577 enval = (enlen[0] < 0 ? 256+enlen[0] : enlen[0])*256+(enlen[1] < 0 ? 256+enlen[1] : enlen[1]);
578 trval = (trlen[0] < 0 ? 256+trlen[0] : trlen[0])*256+(trlen[1] < 0 ? 256+trlen[1] : trlen[1]);
579 if (trval == 0xFFFE) /* marks identical string, handle equally to non-translated */
580 trval = 0;
581 if (enval == 0xFFFF) {
582 multimode = true;
583 if (trval != 0xFFFF) /* files do not match */
584 return false;
585 } else {
586 if (enval > str.length) {
587 str = new byte[enval];
588 }
589 if (trval > str.length) {
590 str = new byte[trval];
591 }
592 int val = ens.read(str, 0, enval);
593 if (val != enval) /* file corrupt */
594 return false;
595 String enstr = new String(str, 0, enval, StandardCharsets.UTF_8);
596 if (trval != 0) {
597 val = trs.read(str, 0, trval);
598 if (val != trval) /* file corrupt */
599 return false;
600 String trstr = new String(str, 0, trval, StandardCharsets.UTF_8);
601 if (!s.containsKey(enstr))
602 s.put(enstr, trstr);
603 }
604 }
605 }
606 }
607 } catch (IOException e) {
608 Logging.trace(e);
609 return false;
610 }
611 if (!s.isEmpty()) {
612 strings = s;
613 pstrings = p;
614 return true;
615 }
616 return false;
617 }
618
619 /**
620 * Sets the default locale (see {@link Locale#setDefault(Locale)} to the local
621 * given by <code>localName</code>.
622 *
623 * Ignored if localeName is null. If the locale with name <code>localName</code>
624 * isn't found the default local is set to <code>en</code> (english).
625 *
626 * @param localeName the locale name. Ignored if null.
627 */
628 public static void set(String localeName) {
629 if (localeName != null) {
630 Locale l = LanguageInfo.getLocale(localeName, true);
631 if (load(LanguageInfo.getJOSMLocaleCode(l))) {
632 Locale.setDefault(l);
633 } else {
634 if (!"en".equals(l.getLanguage())) {
635 Logging.info(tr("Unable to find translation for the locale {0}. Reverting to {1}.",
636 LanguageInfo.getDisplayName(l), LanguageInfo.getDisplayName(Locale.getDefault())));
637 } else {
638 strings = null;
639 pstrings = null;
640 }
641 }
642 }
643 }
644
645 /**
646 * Updates the default locale: overrides the numbering system, if defined in internal boundaries.xml for the current language/country.
647 * @since 16109
648 */
649 public static void initializeNumberingFormat() {
650 Locale l = Locale.getDefault();
651 TagMap tags = Territories.getCustomTags(l.getCountry());
652 if (tags != null) {
653 String numberingSystem = tags.get("ldml:nu:" + l.getLanguage());
654 if (numberingSystem != null && !numberingSystem.equals(l.getExtension(Locale.UNICODE_LOCALE_EXTENSION))) {
655 Locale.setDefault(new Locale.Builder()
656 .setLanguage(l.getLanguage())
657 .setRegion(l.getCountry())
658 .setVariant(l.getVariant())
659 .setExtension(Locale.UNICODE_LOCALE_EXTENSION, numberingSystem)
660 .build());
661 }
662 }
663 }
664
665 private static int pluralEval(long n) {
666 switch (pluralMode) {
667 case MODE_NOTONE: /* bg, da, de, el, en, en_AU, en_CA, en_GB, eo, es, et, eu, fi, fo, gl, is, it, iw_IL, mr, nb, nl, sv */
668 return (n != 1) ? 1 : 0;
669 case MODE_NONE: /* id, vi, ja, km, tr, zh_CN, zh_TW */
670 return 0;
671 case MODE_GREATERONE: /* fr, pt_BR */
672 return (n > 1) ? 1 : 0;
673 case MODE_CS:
674 return (n == 1) ? 0 : (((n >= 2) && (n <= 4)) ? 1 : 2);
675 case MODE_AR:
676 return ((n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((((n % 100) >= 3)
677 && ((n % 100) <= 10)) ? 3 : ((((n % 100) >= 11) && ((n % 100) <= 99)) ? 4 : 5)))));
678 case MODE_PL:
679 return (n == 1) ? 0 : (((((n % 10) >= 2) && ((n % 10) <= 4))
680 && (((n % 100) < 10) || ((n % 100) >= 20))) ? 1 : 2);
681 //case MODE_RO:
682 // return ((n == 1) ? 0 : ((((n % 100) > 19) || (((n % 100) == 0) && (n != 0))) ? 2 : 1));
683 case MODE_LT:
684 return ((n % 10) == 1) && ((n % 100) != 11) ? 0 : (((n % 10) >= 2)
685 && (((n % 100) < 10) || ((n % 100) >= 20)) ? 1 : 2);
686 case MODE_LV:
687 return ((n % 10) == 1) && ((n % 100) != 11) ? 0 : (n != 0) ? 1 : 2;
688 case MODE_RU:
689 return (((n % 10) == 1) && ((n % 100) != 11)) ? 0 : (((((n % 10) >= 2)
690 && ((n % 10) <= 4)) && (((n % 100) < 10) || ((n % 100) >= 20))) ? 1 : 2);
691 case MODE_SK:
692 return (n == 1) ? 1 : (((n >= 2) && (n <= 4)) ? 2 : 0);
693 case MODE_CY:
694 return (n == 1) ? 0 : ((n == 2) ? 1 : (n != 8 && n != 11) ? 2 : 3);
695 //case MODE_SL:
696 // return (((n % 100) == 1) ? 1 : (((n % 100) == 2) ? 2 : ((((n % 100) == 3)
697 // || ((n % 100) == 4)) ? 3 : 0)));
698 }
699 return 0;
700 }
701
702 /**
703 * Returns the map of singular translations.
704 * @return the map of singular translations.
705 * @since 13761
706 */
707 public static Map<String, String> getSingularTranslations() {
708 return new HashMap<>(strings);
709 }
710
711 /**
712 * Returns the map of plural translations.
713 * @return the map of plural translations.
714 * @since 13761
715 */
716 public static Map<String, String[]> getPluralTranslations() {
717 return new HashMap<>(pstrings);
718 }
719
720 /**
721 * Returns the original default locale found when the JVM started.
722 * Used to guess real language/country of current user disregarding language chosen in JOSM preferences.
723 * @return the original default locale found when the JVM started
724 * @since 14013
725 */
726 public static Locale getOriginalLocale() {
727 return originalLocale;
728 }
729
730 /**
731 * Returns the localized name of the given script. Only scripts used in the OSM database are known.
732 * @param script Writing system
733 * @return the localized name of the given script, or null
734 * @since 15501
735 */
736 public static String getLocalizedScript(String script) {
737 if (script != null) {
738 switch (script.toLowerCase(Locale.ENGLISH)) {
739 case HIRAGANA:
740 return /* I18n: a Japanese syllabary */ tr("Hiragana");
741 case KATAKANA:
742 return /* I18n: a Japanese syllabary */ tr("Katakana");
743 case LATIN:
744 return /* I18n: usage of latin letters/script for usually non-latin languages */ tr("Latin");
745 case PINYIN: case LATINPINYIN:
746 return /* I18n: official romanization system for Standard Chinese */ tr("Pinyin");
747 case HANI:
748 return /* I18n: Han characters for Vietnamese or Korean language */ tr("Hani");
749 case HANS:
750 return /* I18n: Simplified Chinese */ tr("Simplified");
751 case HANT:
752 return /* I18n: Traditional Chinese */ tr("Traditional");
753 case BOPOMOFO:
754 return /* I18n: Mandarin Phonetic Symbols/Zhuyin */ tr("Bopomofo");
755 case ROMAJI:
756 return /* I18n: a Japanese syllabary (latin script) */ tr("Rōmaji");
757 default:
758 Logging.warn("Unsupported script: {0}", script);
759 }
760 }
761 return null;
762 }
763
764 /**
765 * Returns the localized name of the given language and optional script.
766 * @param language Language
767 * @return the pair of localized name + known state of the given language, or null
768 * @since 15501
769 */
770 public static Pair<String, Boolean> getLocalizedLanguageName(String language) {
771 Matcher m = LANGUAGE_NAMES.matcher(language);
772 if (m.matches()) {
773 String code = m.group(1);
774 String label = new Locale(code).getDisplayLanguage();
775 boolean knownNameKey = !code.equals(label);
776 String script = getLocalizedScript(m.group(2));
777 if (script != null) {
778 label += " (" + script + ")";
779 }
780 return new Pair<>(label, knownNameKey);
781 }
782 return null;
783 }
784}
Note: See TracBrowser for help on using the repository browser.