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

Last change on this file was 19395, checked in by stoecker, 3 months ago

see #24125, i18n update, add language fo

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