Java tutorial
/* * Copyright (C) 2014 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.opendatakit.utilities; import android.os.Build; import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.io.Charsets; import org.opendatakit.logging.WebLogger; import java.io.*; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; public final class LocalizationUtils { private static String savedAppName; private static final String TAG = LocalizationUtils.class.getSimpleName(); private static Map<String, Object> commonDefinitions; private static Map<String, Map<String, Object>> tableSpecificDefinitionsMap = new HashMap<>(); private LocalizationUtils() { } public static String genUUID() { return "uuid:" + UUID.randomUUID(); } /** * Used in CommonApplication */ @SuppressWarnings("WeakerAccess") public static synchronized void clearTranslations() { commonDefinitions = null; tableSpecificDefinitionsMap.clear(); } private static synchronized void loadTranslations(String appName, String tableId) throws IOException { if (savedAppName != null && !savedAppName.equals(appName)) { clearTranslations(); } savedAppName = appName; TypeReference<HashMap<String, Object>> ref = new TypeReference<HashMap<String, Object>>() { }; if (commonDefinitions == null) { File commonFile = new File(ODKFileUtils.getCommonDefinitionsFile(appName)); if (commonFile.exists() && commonFile.isFile()) { InputStream stream = null; BufferedReader reader = null; String value; try { stream = new FileInputStream(commonFile); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)); } else { reader = new BufferedReader(new InputStreamReader(stream, Charsets.UTF_8)); } int ch = reader.read(); while (ch != -1 && ch != '{') { ch = reader.read(); } StringBuilder b = new StringBuilder(); b.append((char) ch); ch = reader.read(); while (ch != -1) { b.append((char) ch); ch = reader.read(); } reader.close(); stream.close(); value = b.toString().trim(); if (value.endsWith(";")) { value = value.substring(0, value.length() - 1).trim(); } } finally { if (reader != null) { reader.close(); } else if (stream != null) { stream.close(); } } try { commonDefinitions = ODKFileUtils.mapper.readValue(value, ref); } catch (IOException e) { WebLogger.getLogger(appName).printStackTrace(e); throw new IllegalStateException("Unable to read commonDefinitions.js file"); } } } if (tableId != null) { File tableFile = new File(ODKFileUtils.getTableSpecificDefinitionsFile(appName, tableId)); if (!tableFile.exists()) { tableSpecificDefinitionsMap.remove(tableId); } else { // assume it is current if it exists if (tableSpecificDefinitionsMap.containsKey(tableId)) { return; } InputStream stream = null; BufferedReader reader = null; try { stream = new FileInputStream(tableFile); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)); } else { reader = new BufferedReader(new InputStreamReader(stream, Charsets.UTF_8)); } reader.mark(1); int ch = reader.read(); while (ch != -1 && ch != '{') { reader.mark(1); ch = reader.read(); } reader.reset(); Map<String, Object> tableSpecificTranslations = ODKFileUtils.mapper.readValue(reader, ref); if (tableSpecificTranslations != null) { tableSpecificDefinitionsMap.put(tableId, tableSpecificTranslations); } } finally { if (reader != null) { reader.close(); } else if (stream != null) { stream.close(); } } } } } /** * Used in commonTranslationLocaleScreen * * @param appName the app name * @return a list of locales provided by that app * @throws IOException if the file couldn't be opened */ @SuppressWarnings("unused") public static List<Map<String, Object>> getCommonLocales(String appName) throws IOException { if (commonDefinitions == null) { loadTranslations(appName, null); } if (commonDefinitions != null && commonDefinitions.containsKey("_locales")) { Map<String, Object> localesObject = (Map<String, Object>) commonDefinitions.get("_locales"); if (localesObject != null && localesObject.containsKey("value")) { return (List<Map<String, Object>>) localesObject.get("value"); } } return null; } /** * Used in commonTranslationLocaleScreen * * @param appName the app name * @return a list of locales provided by that app * @throws IOException if the file couldn't be opened */ @SuppressWarnings("unused") public static String getCommonLocaleDefault(String appName) throws IOException { if (commonDefinitions == null) { loadTranslations(appName, null); } if (commonDefinitions != null && commonDefinitions.containsKey("_default_locale")) { Map<String, Object> default_locale = (Map<String, Object>) commonDefinitions.get("_default_locale"); String value = (String) default_locale.get("value"); if (value != null && !value.isEmpty()) { return value; } } return "default"; } private static synchronized Map<String, Object> resolveTranslation(String appName, String tableId, String translationToken) throws IOException { if (appName == null) { throw new IllegalArgumentException("appName cannot be null"); } if (tableId == null) { throw new IllegalArgumentException("tableId cannot be null"); } if (translationToken == null) { throw new IllegalArgumentException("translationToken cannot be null"); } if (savedAppName == null || !savedAppName.equals(appName)) { clearTranslations(); } if (commonDefinitions == null || !tableSpecificDefinitionsMap.containsKey(tableId)) { loadTranslations(appName, tableId); } Map<String, Object> value = null; Map<String, Object> tableSpecificDefinitions = tableSpecificDefinitionsMap.get(tableId); if (tableSpecificDefinitions != null) { Map<String, Object> tokens = (Map<String, Object>) tableSpecificDefinitions.get("_tokens"); value = (Map<String, Object>) tokens.get(translationToken); } if (commonDefinitions != null && value == null) { Map<String, Object> tokens = (Map<String, Object>) commonDefinitions.get("_tokens"); value = (Map<String, Object>) tokens.get(translationToken); } return value; } /** * Retrieve a translation from the localizationMap. The map might be for text, image, * audio, etc. entries. * <p> * If localizationMap is a string, return it as-is, otherwise, it should be a * Map<String,Object> with locale as the key and value as the internationalized string. * <p> * full_locale is assumed to be of the form: language + "_" + country, with language not * containing an underscore. It first tries exact-case match of full_locale then tries for * case-insensitive matching of full_locale and then of just language. And if none of these * are present, returns the default translation or null if none is available. * * @param localizationMap a map of locale IDs to translations * @param full_locale the locale to get the translation for * @return null or the translation string. */ private static String processLocalizationMap(Object localizationMap, String full_locale) { if (localizationMap == null) { throw new IllegalStateException("null localizationMap"); } if (localizationMap instanceof String) { return (String) localizationMap; } Map<String, Object> aMap = (Map<String, Object>) localizationMap; int underscore = full_locale.indexOf('_'); String lang_only_locale = underscore <= 0 ? null : full_locale.substring(0, underscore); String langOnlyMatch = null; String defaultMatch = null; if (aMap.containsKey(full_locale)) { return (String) aMap.get(full_locale); } // otherwise, do a case-independent compare to find a match // and also consider language-only comparisons and, finally // retrieve the default translation. for (Map.Entry<String, Object> entry : aMap.entrySet()) { String key = entry.getKey(); if (key.compareToIgnoreCase(full_locale) == 0) { return (String) entry.getValue(); } if (lang_only_locale != null && key.compareToIgnoreCase(lang_only_locale) == 0) { langOnlyMatch = (String) entry.getValue(); } if (key.compareToIgnoreCase("default") == 0) { defaultMatch = (String) entry.getValue(); } } if (langOnlyMatch != null) { return langOnlyMatch; } return defaultMatch; } /** * displayName is a JSON serialization of either an i18n token or of a Map<String,Object>. * <p> * full_locale is assumed to be of the form: language + "_" + country, with language not * containing an underscore. It first tries exact-case match of full_locale then tries for * case-insensitive matching of full_locale and then of just language. And if none of these * are present, returns the default translation or null if none is available. * * Used all over the place * * @param appName the app name * @param tableId the table id to get the display name for * @param full_locale the locale to use to decode the display name * @param displayName the raw display name json from the properties * @return a display name in the requested locale, if available */ @SuppressWarnings("WeakerAccess") public static String getLocalizedDisplayName(String appName, String tableId, String full_locale, String displayName) { // retrieve the localeMap from the JSON string stored in this field. // this JSON serialization will either be a string that is a translationToken // that points to a translation in the common or table-specific translations // or it will be a localization object with { "text":..., "image":..., ... } fields. Map<String, Object> localizationMap = null; if (displayName.startsWith("\"") && displayName.endsWith("\"")) { String translationToken; try { translationToken = ODKFileUtils.mapper.readValue(displayName, String.class); } catch (IOException e) { WebLogger.getLogger(appName).printStackTrace(e); throw new IllegalStateException("bad displayName: " + displayName); } try { localizationMap = resolveTranslation(appName, tableId, translationToken); } catch (IOException e) { WebLogger.getLogger(appName).printStackTrace(e); throw new IllegalStateException( "unable to retrieve display localization from string " + "token: " + translationToken); } if (localizationMap == null) { //throw new IllegalStateException( //"no translations found for translation token: " + translationToken); WebLogger.getLogger(appName).e(TAG, "No translation tokens found!"); return "Error translating \"" + translationToken + "\""; } } else { TypeReference<Map<String, Object>> ref = new TypeReference<Map<String, Object>>() { }; try { localizationMap = ODKFileUtils.mapper.readValue(displayName, ref); } catch (IOException e) { WebLogger.getLogger(appName).printStackTrace(e); throw new IllegalStateException("bad displayName: " + displayName); } } if (localizationMap == null) { throw new IllegalStateException("bad displayName (no localization map found): " + displayName); } // the localization has "text", "image", etc. keys. // pull out the text entry. Object textEntry = localizationMap.get("text"); if (textEntry == null) { throw new IllegalStateException("no text entry for displayname: " + displayName); } return processLocalizationMap(textEntry, full_locale); } /** * candidateLocalizationMap may either be an i18n token or a Map<String,Object>. * If the former, then it is resolved into a map via the common or table-specific * translations. * <p> * The internationalization for the full_locale is then retrieved from this map. * <p> * full_locale is assumed to be of the form: language + "_" + country, with language not * containing an underscore. It first tries exact-case match of full_locale then tries for * case-insensitive matching of full_locale and then of just language. And if none of these * are present, returns the default translation or null if none is available. * <p> * Used in CommonTranslationsLocaleScreen * * @param appName the app name * @param tableId the table id, used to get a localization map using an i18n token * @param full_locale of the form language + "_" + country * @param candidateLocalizationMap a map from locales to localized strings or an i18n token * @return the localized string */ @SuppressWarnings("unused") public static String getLocalizationFromMap(String appName, String tableId, String full_locale, Object candidateLocalizationMap) { //noinspection UnusedAssignment Map<String, Object> localizationMap = null; if (candidateLocalizationMap == null) { throw new IllegalStateException("null passed as localizationMap or i18nToken"); } if (candidateLocalizationMap instanceof String) { // actually an internationalization token. // Retrieve the entry in the localeMap from this token. // this will be a localization object with { "text":..., "image":..., ... } fields. String translationToken = (String) candidateLocalizationMap; try { localizationMap = resolveTranslation(appName, tableId, translationToken); } catch (IOException e) { WebLogger.getLogger(appName).printStackTrace(e); throw new IllegalStateException( "unable to retrieve display localization from string " + "token: " + translationToken); } if (localizationMap == null) { throw new IllegalStateException("no translations found for translation token: " + translationToken); } } else { // otherwise, it should be localization object with { "text":..., "image":..., ... } fields. localizationMap = (Map<String, Object>) candidateLocalizationMap; } // the localization has "text", "image", etc. keys. // pull out the text entry. Object textEntry = localizationMap.get("text"); if (textEntry == null) { throw new IllegalStateException("no text entry in localization map: " + candidateLocalizationMap); } return processLocalizationMap(textEntry, full_locale); } }