package org.geogebra.common.main; import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.commands.Commands; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Language; import org.geogebra.common.util.lang.Unicode; public abstract class Localization { /** CAS syntax suffix for keys in command bundle */ public final static String syntaxCAS = ".SyntaxCAS"; /** 3D syntax suffix for keys in command bundle */ public final static String syntax3D = ".Syntax3D"; /** syntax suffix for keys in command bundle */ public final static String syntaxStr = ".Syntax"; /** used when a secondary language is being used for tooltips. */ private String[] fontSizeStrings = null; protected Locale currentLocale = Locale.ENGLISH; // Giac works to 13 sig digits (for "double" calculations) private int maxFigures = 15; private int dimension = 2; public Localization(int dimension, int maxFigures) { this.dimension = dimension; this.maxFigures = maxFigures; } /** * @return localized strings describing font sizes (very small, smaall, ...) */ public String[] getFontSizeStrings() { if (fontSizeStrings == null) { fontSizeStrings = new String[] { getPlain("ExtraSmall"), getPlain("VerySmall"), getPlain("Small"), getPlain("Medium"), getPlain("Large"), getPlain("VeryLarge"), getPlain("ExtraLarge") }; } return fontSizeStrings; } private boolean reverseNameDescription = false; /** * For Basque and Hungarian you have to say "A point" instead of "point A" * * @return whether current alnguage needs revverse order of type and name */ final public boolean isReverseNameDescriptionLanguage() { // for Basque and Hungarian return reverseNameDescription; } // For Hebrew and Arabic. Guy Hed, 25.8.2008 public boolean rightToLeftReadingOrder = false; /** * @return whether current language uses RTL orientation */ final public boolean isRightToLeftReadingOrder() { return rightToLeftReadingOrder; } /** decimal point (different in eg Arabic) */ public char unicodeDecimalPoint = '.'; /** comma (different in Arabic) */ public char unicodeComma = ','; // \u060c for Arabic comma /** zero (different in eg Arabic) */ public char unicodeZero = '0'; /** * Text fixer for the Hungarian language * * @param text * the translation text to fix * @return the fixed text * @author Zoltan Kovacs <zoltan@geogebra.org> */ private static String translationFixHu(String inputText) { String text = inputText; // Fixing affixes. // We assume that object names are usual object names like "P", "O_1" // etc. // FIXME: This will not work for longer object names, e.g. "X Triangle", // "mypoint". To solve this problem, we should check the whole word and // its vowels. Probably hunspell for JNA could help (but it can be // too big solution for us), http://dren.dk/hunspell.html. // TODO: The used method is not as fast as it could be, so speedup is // possible. String[] affixesList = { "-ra/-re", "-nak/-nek", "-ba/-be", "-ban/-ben", "-hoz/-hez", "-val/-vel" }; String[] endE2 = { "10", "40", "50", "70", "90" }; // FIXME: Numbers in endings which greater than 999 are not supported // yet. // Special endings for -val/-vel: String[] endO2 = { "00", "20", "30", "60", "80" }; for (String affix : affixesList) { int match; do { match = text.indexOf(affix); // match > 0 can be assumed because an affix will not start the // text if ((match > -1) && (match > 0)) { // Affix found. Get the previous character. String prevChars = translationFixPronouncedPrevChars(text, match, 1); if (Unicode.translationFixHu_endE1 .indexOf(prevChars) > -1) { text = translationFixHuAffixChange(text, match, affix, "e", prevChars); } else if (Unicode.translationFixHu_endO1 .indexOf(prevChars) > -1) { text = translationFixHuAffixChange(text, match, affix, "o", prevChars); } else if (Unicode.translationFixHu_endOE1 .indexOf(prevChars) > -1) { text = translationFixHuAffixChange(text, match, affix, Unicode.translationFixHu_oe, prevChars); } else if (match > 1) { // Append the previous character. // TODO: This could be quicker: to add only the second // char beyond prevChars prevChars = translationFixPronouncedPrevChars(text, match, 2); boolean found2 = false; for (String last2fit : endE2) { if (!found2 && last2fit.equals(prevChars)) { text = translationFixHuAffixChange(text, match, affix, "e", prevChars); found2 = true; } } // Special check for preparing -val/-vel: if (!found2) { for (String last2fit : endO2) { if (!found2 && last2fit.equals(prevChars)) { text = translationFixHuAffixChange(text, match, affix, "o", prevChars); found2 = true; } } } if (!found2) { // Use heuristics: text = translationFixHuAffixChange(text, match, affix, "o", prevChars); } } else { // Use heuristics: text = translationFixHuAffixChange(text, match, affix, "o", prevChars); } } } while (match > -1); } // Fixing definite article. String[] articlesList = { "a(z)", "A(z)" }; // assume they are 3 chars // long for (String article : articlesList) { int match; do { match = text.indexOf(article); if (match > -1) { // Article found. Get the next character. if (match < text.length() - 5) { char checked = Character .toLowerCase(text.charAt(match + 5)); String consonants = "bcdfghjklmnpqrstvwx2346789"; int match2 = consonants.indexOf(checked); String first = text.substring(0, match + 1); String last = text.substring(match + 4); if (match2 > -1) { // removing "(z)" if the next word starts with a // consonant text = first + last; } else { // removing "()" otherwise, using "z" instead (next // word starts with a vowel) text = first + "z" + last; } } } } while (match > -1); } return text; } /** * Gets the previous "pronounced" characters from text before the match * position for the given length. The returned text will be lowercased. * * Example: translationFixPrevChars("ABC_{123}", 8, 4) gives "c123" * * @param text * the text to pronounce * @param match * starting position * @param length * required length for the output * @return lowercased output */ private static String translationFixPronouncedPrevChars(String text, int match, int length) { int pos = match; String rettext = ""; int rettextlen = 0; String thisChar; String ignoredChars = "_{}"; while ((rettextlen < length) && (pos > 0)) { thisChar = text.substring(pos - 1, pos); if (ignoredChars.indexOf(thisChar) == -1) { rettext = thisChar.toLowerCase() + rettext; rettextlen++; } pos--; } return rettext; } /** * Changes a set of possible affixes to the right one * * @param text * the text to be corrected * @param match * starting position of possible change * @param affixes * possible affixes to change * @param affixForm * abbreviation for the change type ("o"/"a"/"e") * @param prevChars * @return the corrected text */ private static String translationFixHuAffixChange(String inputText, int match, String affixes, String affixForm, String prevChars) { String text = inputText; String replace = ""; if ("-ra/-re".equals(affixes)) { if ("a".equals(affixForm) || "o".equals(affixForm)) { replace = "ra"; } else { replace = "re"; } } else if ("-nak/-nek".equals(affixes)) { if ("a".equals(affixForm) || "o".equals(affixForm)) { replace = "nak"; } else { replace = "nek"; } } else if ("-ba/-be".equals(affixes)) { if ("a".equals(affixForm) || "o".equals(affixForm)) { replace = "ba"; } else { replace = "be"; } } else if ("-ban/-ben".equals(affixes)) { if ("a".equals(affixForm) || "o".equals(affixForm)) { replace = "ban"; } else { replace = "ben"; } } else if ("-hoz/-hez".equals(affixes)) { if ("a".equals(affixForm) || "o".equals(affixForm)) { replace = "hoz"; } else if ("e".equals(affixForm)) { replace = "hez"; } else { replace = Unicode.translationFixHu_hoez; } } else if ("-val/-vel".equals(affixes)) { if ("a".equals(affixForm) || "o".equals(affixForm)) { replace = "val"; } else { replace = "vel"; } // Handling some special cases: if (prevChars.length() == 1) { // f-fel, l-lel etc. String sameChars = "flmnrs"; // y-nal, 3-mal etc. String valVelFrom = sameChars + "y356789"; String valVelTo = sameChars + "nmtttcc"; int index = valVelFrom.indexOf(prevChars); if (index > -1) { replace = valVelTo.charAt(index) + replace.substring(1); } else { // x-szel, 1-gyel etc. String valVelFrom2 = "x14"; String[] valVelTo2 = { "sz", "gy", "gy" }; index = valVelFrom2.indexOf(prevChars); if (index > -1) { replace = valVelTo2[index] + replace.substring(1); } } } else if ((prevChars.length() == 2) && prevChars.substring(1).equals("0")) { // (Currently the second part of the conditional is // unnecessary.) // 00-zal, 10-zel, 30-cal etc. // FIXME: A_{00}-val will be replaced to A_{00}-zal currently, // because we silently assume that 00 is preceeded by another // number. String valVelFrom = "013456789"; String valVelTo = "zzcnnnnnn"; int index = valVelFrom.indexOf(prevChars.charAt(0)); if (index > -1) { replace = valVelTo.charAt(index) + replace.substring(1); } else { // 20-szal if (prevChars.charAt(0) == '2') { replace = "sz" + replace.substring(1); } } } } if ("".equals(replace)) { // No replace. return text; } int affixesLength = affixes.length(); // Replace. text = text.substring(0, match) + "-" + replace + text.substring(match + affixesLength); return text; } /** * Gets translation from "command" bundle * * @param key * key * @return translation of given key */ public abstract String getCommand(String key); /** * Gets translation from "plain" bundle * * @param key * key * @return translation of given key */ public final String getPlain(String key) { return getMenu(key); } /** * Returns translation of given key from the "menu" bundle * * @param key * key * @return translation for key */ public abstract String getMenu(String key); /** * Returns translation of given key from the "error" bundle * * @param key * key * @return translation for key */ public abstract String getError(String key); /** * Returns translation of given key from the "symbol" bundle * * @param key * key (either "S.1", "S.2", ... for symbols or "T.1", "T.2" ... * for tooltips) * @return translation for key */ public abstract String getSymbol(int key); /** * @param colorName * localized color name * @return internal color name */ public abstract String reverseGetColor(String colorName); /** * Returns translation of a key in colors bundle * * @param key * key (color name) * @return localized color name */ public abstract String getColor(String key); /** * Translates the key and replaces "%0" by args[0], "%1" by args[1], etc * * @version 2008-09-18 * @author Michael Borcherds, Markus Hohenwarter * @param key * key * @param args * arguments for replacement * @return translated key with replaced %*s */ final public String getPlainArray(String key, String[] args) { String str = getPlain(key); StringBuilder sbPlain = new StringBuilder(); sbPlain.setLength(0); boolean found = false; for (int i = 0; i < str.length(); i++) { char ch = str.charAt(i); if (ch == '%') { // get number after % i++; int pos = str.charAt(i) - '0'; if ((pos >= 0) && (pos < args.length)) { // success sbPlain.append(args[pos]); found = true; } else { // failed sbPlain.append(ch); } } else { sbPlain.append(ch); } } if (!found) { /* * If no parameters were found in key, this key is missing for some * reason (maybe it is not added to the ggbtrans database yet). In * this case all parameters are appended to the displayed string to * help the developers. */ for (String arg : args) { sbPlain.append(" "); sbPlain.append(arg); } } // In some languages we may need some final fixes: return translationFix(sbPlain.toString()); } /** * * * only letters, numbers and _ allowed in label names check for other * characters in the properties, and remove them * * @param key * eg "poly" -> "Name.poly" -> poly -> poly1 as a label * @return "poly" (the suffix is added later) */ final public String getPlainLabel(String key) { String ret = getPlain("Name." + key); for (int i = ret.length() - 1; i >= 0; i--) { if (!StringUtil.isLetterOrDigitOrUnderscore(ret.charAt(i))) { Log.warn("Bad character in key: " + key + "=" + ret); // remove bad character ret = ret.substring(0, i) + ret.substring(i + 1); } } return ret; } /** replace "%0" by arg0 */ final public String getPlain(String key, String... arg0) { return getPlainArray(key, arg0); } /** * * @return 2 letter language name, eg "en" */ public String getLanguage() { return getLanguage(getLocale()); } /** * @param lang * two letter language name * @return whether we are currently using given language */ public boolean languageIs(String lang) { return getLanguage().equals(lang); } /** * In some languages, a properties file cannot completely describe * translations. This method tries to rewrite a text to the correct form. * * @param text * the translation text to fix * @return text the fixed text * @author Zoltan Kovacs <zoltan@geogebra.org> */ public String translationFix(String text) { // Currently no other language is supported than Hungarian. String lang = getLanguage(); if (!("hu".equals(lang))) { return text; } return translationFixHu(text); } private StringBuilder sbOrdinal = new StringBuilder(); /** * given 1, return eg 1st, 1e, 1:e according to the language * * http://en.wikipedia.org/wiki/Ordinal_indicator * * @param n * number * @return corresponding ordinal number */ public String getOrdinalNumber(int n) { String lang = getLanguage(); if ("en".equals(lang)) { return getOrdinalNumberEn(n); } // check here for languages where 1st = 1 if ("pt".equals(lang) || "ar".equals(lang) || "cy".equals(lang) || "fa".equals(lang) || "ja".equals(lang) || "ko".equals(lang) || "lt".equals(lang) || "mr".equals(lang) || "ms".equals(lang) || "nl".equals(lang) || "si".equals(lang) || "th".equals(lang) || "vi".equals(lang) || "zh".equals(lang)) { return n + ""; } if (sbOrdinal == null) { sbOrdinal = new StringBuilder(); } else { sbOrdinal.setLength(0); } // prefixes if ("in".equals(lang)) { sbOrdinal.append("ke-"); } else if ("iw".equals(lang)) { // prefix and postfix for Hebrew sbOrdinal.append("\u200f\u200e"); } sbOrdinal.append(n); if ("cs".equals(lang) || "da".equals(lang) || "et".equals(lang) || "eu".equals(lang) || "hr".equals(lang) || "hu".equals(lang) || "is".equals(lang) || "no".equals(lang) || "sk".equals(lang) || "sr".equals(lang) || "tr".equals(lang)) { sbOrdinal.append('.'); } else if ("de".equals(lang)) { sbOrdinal.append("th"); } else if ("fi".equals(lang)) { sbOrdinal.append(":s"); } else if ("el".equals(lang)) { sbOrdinal.append('\u03b7'); } else if ("ro".equals(lang) || "es".equals(lang) || "it".equals(lang) || "pt".equals(lang)) { sbOrdinal.append(Unicode.FEMININE_ORDINAL_INDICATOR); } else if ("bs".equals(lang) || "sl".equals(lang)) { sbOrdinal.append("-ti"); } else if ("ca".equals(lang)) { switch (n) { // Catalan (masculine) case 0: break; // just "0", not "0e" etc case 1: sbOrdinal.append("r"); break; case 2: sbOrdinal.append("n"); break; case 3: sbOrdinal.append("r"); break; case 4: sbOrdinal.append("t"); break; default: sbOrdinal.append("e"); break; } } else if ("sq".equals(lang)) { sbOrdinal.append("-te"); } else if ("gl".equals(lang)) { sbOrdinal.append("ava"); } else if ("mk".equals(lang)) { sbOrdinal.append("-\u0442\u0438"); } else if ("ka".equals(lang)) { sbOrdinal.append("-\u10d4"); } else if ("iw".equals(lang)) { sbOrdinal.append("\u200e\u200f"); } else if ("ru".equals(lang) || "uk".equals(lang)) { sbOrdinal.append("-\u0433\u043e"); } else if ("fr".equals(lang)) { if (n == 1) { sbOrdinal.append("er"); // could also be "re" for feminine... } else { sbOrdinal.append("e"); // could also be "es" for plural... } } else if ("sv".equals(lang)) { int unitsDigit = n % 10; if ((unitsDigit == 1) || (unitsDigit == 2)) { sbOrdinal.append(":a"); } else { sbOrdinal.append(":e"); } } return sbOrdinal.toString(); } /** * given 1, return eg 1st (English only) * * http://en.wikipedia.org/wiki/Ordinal_indicator * * @param n * number * @return english ordinal number */ public String getOrdinalNumberEn(int n) { /* * http://en.wikipedia.org/wiki/Names_of_numbers_in_English If the tens * digit of a number is 1, then write "th" after the number. For * example: 13th, 19th, 112th, 9,311th. If the tens digit is not equal * to 1, then use the following table: If the units digit is: 0 1 2 3 4 * 5 6 7 8 9 write this after the number th st nd rd th th th th th th */ int tensDigit = (n / 10) % 10; if (tensDigit == 1) { return n + "th"; } int unitsDigit = n % 10; switch (unitsDigit) { case 1: return n + "st"; case 2: return n + "nd"; case 3: return n + "rd"; default: return n + "th"; } } static final public String ROUNDING_MENU_SEPARATOR = "---"; /** * @return rounding menu items */ public String[] getRoundingMenu() { String[] strDecimalSpaces = { getPlain("ADecimalPlaces", "0"), getPlain("ADecimalPlace", "1"), getPlain("ADecimalPlaces", "2"), getPlain("ADecimalPlaces", "3"), getPlain("ADecimalPlaces", "4"), getPlain("ADecimalPlaces", "5"), getPlain("ADecimalPlaces", "10"), getPlain("ADecimalPlaces", "15"), ROUNDING_MENU_SEPARATOR, getPlain("ASignificantFigures", "3"), getPlain("ASignificantFigures", "5"), getPlain("ASignificantFigures", "10"), getPlain("ASignificantFigures", maxFigures + "") }; // zero is singular in eg French if (!isZeroPlural(getLanguage())) { strDecimalSpaces[0] = getPlain("ADecimalPlace", "0"); } return strDecimalSpaces; } /** * in French, zero is singular, eg 0 dcimale rather than 0 decimal places * * @param lang * language code * @return whether 0 is plural */ public boolean isZeroPlural(String lang) { if (lang.startsWith("fr")) { return false; } return true; } private boolean isAutoCompletePossible = true; /** * Returns whether autocomplete should be used at all. Certain languages * make problems with auto complete turned on (e.g. Korean). * * @return whether autocomplete should be used at all, depending on language */ final public boolean isAutoCompletePossible() { return isAutoCompletePossible; } // For Persian and Arabic. private boolean rightToLeftDigits = false; /** * Returns whether current language uses RTL orientation for numbers for * given template. We don't want RTL digits in XML * * @param tpl * string templates * @return whether current language uses RTL orientation for numbers for * given template */ final public boolean isRightToLeftDigits(StringTemplate tpl) { if (!tpl.internationalizeDigits()) { return false; } return rightToLeftDigits; } /** * Use localized digits. */ private boolean useLocalizedDigits = false; /** * @return If localized digits are used for certain languages (Arabic, * Hebrew, etc). */ public boolean isUsingLocalizedDigits() { return useLocalizedDigits; } /** * Updates language flags (RTL, RTL for numbers, reverse word order, * autocomplete possible) * * @param lang * language */ public void updateLanguageFlags(String lang) { rightToLeftReadingOrder = rightToLeftReadingOrder(lang); // force update fontSizeStrings = null; // reverseLanguage = "zh".equals(lang); removed Michael Borcherds // 2008-03-31 reverseNameDescription = "eu".equals(lang) || "hu".equals(lang); // used for eg axes labels // Arabic digits are RTL // Persian aren't http://persian.nmelrc.org/persianword/format.htm rightToLeftDigits = "ar".equals(lang); // Another option: // rightToLeftReadingOrder = // (Character.getDirectionality(getPlain("Algebra").charAt(1)) == // Character.DIRECTIONALITY_RIGHT_TO_LEFT); // turn off auto-complete for Korean isAutoCompletePossible = true;// !"ko".equals(lang); // defaults unicodeDecimalPoint = '.'; unicodeComma = ','; // unicodeThousandsSeparator=','; if (isUsingLocalizedDigits()) { if (lang.startsWith("ar")) { // Arabic unicodeZero = '\u0660'; // Arabic-Indic digit 0 unicodeDecimalPoint = Unicode.ArabicComma; // Arabic-Indic // decimal point unicodeComma = '\u060c'; // Arabic comma // unicodeThousandsSeparator = '\u066c'; // Arabic Thousands // separator } else if (lang.startsWith("fa")) { // Persian unicodeZero = '\u06f0'; // Persian digit 0 (Extended // Arabic-Indic) unicodeDecimalPoint = Unicode.ArabicComma; // Arabic comma unicodeComma = '\u060c'; // Arabic-Indic decimal point // unicodeThousandsSeparator = '\u066c'; // Arabic Thousands // separators } else if (lang.startsWith("ml")) { unicodeZero = '\u0d66'; // Malayalam digit 0 } else if (lang.startsWith("th")) { unicodeZero = '\u0e50'; // Thai digit 0 } else if (lang.startsWith("ta")) { unicodeZero = '\u0be6'; // Tamil digit 0 } else if (lang.startsWith("sd")) { unicodeZero = '\u1bb0'; // Sudanese digit 0 } else if (lang.startsWith("kh")) { unicodeZero = '\u17e0'; // Khmer digit 0 } else if (lang.startsWith("mn")) { unicodeZero = '\u1810'; // Mongolian digit 0 } else if (lang.startsWith("mm")) { unicodeZero = '\u1040'; // Mayanmar digit 0 } else { unicodeZero = '0'; } } else { unicodeZero = '0'; } } /** * @param language * language string * @return whether to use LTR */ public static boolean rightToLeftReadingOrder(String language) { String lang = language.substring(0, 2); // Guy Hed, 25.8.2008 // Guy Hed, 26.4.2009 - added Yiddish and Persian as RTL languages return ("iw".equals(lang) || "ar".equals(lang) || "fa".equals(lang) || "ji".equals(lang) || "he".equals(lang) || "ug".equals(lang)); } /** * @param key * command name * @return command syntax TODO check whether getSyntaxString works here */ public String getCommandSyntax(String key) { String command = getCommand(key); if (dimension == 3) { String key3D = key + Localization.syntax3D; String cmdSyntax3D = getCommand(key3D); if (!cmdSyntax3D.equals(key3D)) { cmdSyntax3D = cmdSyntax3D.replace("[", command + '['); return cmdSyntax3D; } } String syntaxString = Localization.syntaxStr; String syntax = null; if (syntaxString != null) { syntax = getCommand(key + syntaxString); syntax = syntax.replace("[", command + '['); } return syntax; } /** * Use localized labels. */ private boolean useLocalizedLabels = true; /** * @return If localized labels are used for certain languages. */ public boolean isUsingLocalizedLabels() { return useLocalizedLabels; } /** * Use localized labels for certain languages. * * @param useLocalizedLabels * true to make labels of new geos localized */ public void setUseLocalizedLabels(boolean useLocalizedLabels) { this.useLocalizedLabels = useLocalizedLabels; } /** * Use localized digits for certain languages (Arabic, Hebrew, etc). * * Calls {@link #updateLanguageFlags(String)} to apply the change, but just * if the new flag differs from the current. * * @param useLocalizedDigits * whether localized digits should be used * @param app * used for callback (update construction) */ public void setUseLocalizedDigits(boolean useLocalizedDigits, App app) { if (this.useLocalizedDigits == useLocalizedDigits) { return; } this.useLocalizedDigits = useLocalizedDigits; updateLanguageFlags(getLanguage()); app.getKernel().updateConstruction(); app.setUnsaved(); if (app.getEuclidianView1() != null) { app.getEuclidianView1().updateBackground(); } } /** * Returns translation of given key from the "symbol" bundle in tooltip * language * * @param key * key (either "S.1", "S.2", ... for symbols or "T.1", "T.2" ... * for tooltips) * @return translation for key in tooltip language */ public abstract String getSymbolTooltip(int key); /** * @param key * command name * @return CAS syntax */ public String getCommandSyntaxCAS(String key) { String keyCAS = key + syntaxCAS; String command = getCommand(key); String syntax = getCommand(keyCAS); // make sure "PointList.SyntaxCAS" not displayed in dialog if (syntax.equals(keyCAS)) { syntax = getCommand(key + syntaxStr); } syntax = syntax.replace("[", command + '['); return syntax; } /** * * @param key * (internal) command name to check * @return true if this command has a CAS-specific syntax */ public boolean isCASCommand(String key) { String keyCAS = key + syntaxCAS; String syntax = getCommand(keyCAS); if (syntax.equals(keyCAS)) { return false; } return true; } /** * can be over-ridden if required to provide tooltips in another language * * @param string * key * @return translation of key from menu bundle in tooltip language */ public String getMenuTooltip(String key) { return getMenu(key); } /** * @param string * key * @return translation of key from plain bundle in tooltip language */ public final String getPlainTooltip(String string) { return getMenuTooltip(string); } /** * Initialize the command bundle (not needed in Web) */ public abstract void initCommand(); /** * used to force properties to be read from secondary (tooltip) language if * one has been selected */ public void setTooltipFlag() { // overridden in LocalizationJre // nothing to do in web etc } /** * used to stop forcing properties to be read from secondary (tooltip) * language if one has been selected */ public void clearTooltipFlag() { // overridden in LocalizationJre // nothing to do in web etc } /** * @return tooltip language (or null where not supported) */ public String getTooltipLanguageString() { return null; } /** * @return whether language of command bundle changed since we last updated * translation table and directories */ protected abstract boolean isCommandChanged(); /** * @param b * whether language of command bundle changed since we last * updated translation table and directories */ protected abstract void setCommandChanged(boolean b); /** * @return whether command translation bundle is null */ protected abstract boolean isCommandNull(); /** * turns eg Function.sin into "sin" or (in Spanish) "sen" * * guaranteed to remove the "Function." from the start even if a key doesn't * exist (or isn't loaded) * */ public String getFunction(String key) { String ret = getPlain("Function." + key); // make sure we don't get strange function names if the properties // aren't loaded if (ret.startsWith("Function.")) { return ret.replace("Function.", ""); } return ret; } public String getLocaleStr() { return getLocale().toString(); } public Locale getLocale() { return currentLocale; } public int getRightAngleStyle() { return Language.getRightAngleStyle(getLanguage()); } private HashMap<String, String> translateCommandTable; public String getReverseCommand(String command) { if (command == null) { return null; } String key = StringUtil.toLowerCase(command); String ret = translateCommandTable == null ? key : translateCommandTable.get(key); if (ret != null) { return ret; } // if that fails check internal commands for (Commands c : Commands.values()) { if (StringUtil.toLowerCase(c.name()).equals(key)) { return Commands.englishToInternal(c).name(); } } return null; } public void initTranslateCommand() { if (translateCommandTable == null) { translateCommandTable = new HashMap<String, String>(); } translateCommandTable.clear(); } public HashMap<String, String> getTranslateCommandTable() { return translateCommandTable; } public String getKeyboardRow(int row) { return getPlain("Keyboard.row" + row); } abstract protected ArrayList<Locale> getSupportedLocales(); /** * Returns a locale object that has the same country and/or language as * locale. If the language of locale is not supported an English locale is * returned. */ protected Locale getClosestSupportedLocale(Locale locale) { int size = getSupportedLocales().size(); // try to find country and and language String country = getCountry(locale); String language = getLanguage(locale); String variant = getVariant(locale); if (country.length() > 0) { for (int i = 0; i < size; i++) { Locale loc = getSupportedLocales().get(i); if (country.equals(getCountry(loc)) && language.equals(getLanguage(loc)) // needed for no_NO_NY && (!"no".equals(language) || variant.equals(getVariant(loc)))) { // found supported country locale return loc; } } } // try to find only language for (int i = 0; i < size; i++) { Locale loc = getSupportedLocales().get(i); if (language.equals(getLanguage(loc))) { // found supported country locale return loc; } } // we didn't find a matching country or language, // so we take English return Locale.ENGLISH; } public void setLocale(Locale locale) { currentLocale = getClosestSupportedLocale(locale); updateResourceBundles(); } abstract protected void updateResourceBundles(); protected abstract String getLanguage(Locale locale); protected abstract String getCountry(Locale locale); @SuppressWarnings("unused") protected String getVariant(Locale locale) { return ""; } /** * * return East/West as appropriate for eg Hebrew / Arabic * * return String rather than app.borderWest() so we're not dependent on awt * * @return "West" or "East" */ final public String borderWest() { if (!isRightToLeftReadingOrder()) { return "West"; } return "East"; } /** * * return East/West as appropriate for eg Hebrew / Arabic * * return String rather than app.borderEast() so we're not dependent on awt * * @return "East" or "West" */ final public String borderEast() { if (isRightToLeftReadingOrder()) { return "West"; } return "East"; } public String getEnglishCommand(String internalName) { // String internalName = "LineBisector"; Commands toTest = Commands.stringToCommand(internalName); // Log.debug("toTest = " + toTest + " " + toTest.getClass()); for (Commands c : Commands.values()) { Commands cInternal = Commands.englishToInternal(c); // Log.debug(c.name() + " " + cInternal + " " + toTest + " " // + internalName); if (toTest.equals(cInternal) && !c.name().equals(cInternal.toString())) { Log.debug( "English name for " + internalName + " is " + c.name()); return c.name(); } } Log.debug("nothing found, English name must be " + internalName); return internalName; } }