Java tutorial
/* * Copyright 2013-2015 Erudika. http://erudika.com * * 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. * * For issues and patches go to: https://github.com/erudika */ package com.erudika.para.i18n; import com.erudika.para.persistence.DAO; import com.erudika.para.search.Search; import com.erudika.para.core.Translation; import com.erudika.para.core.Sysprop; import com.erudika.para.utils.Config; import com.erudika.para.utils.Pager; import com.erudika.para.utils.Utils; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.slf4j.Logger; import javax.inject.Inject; import javax.inject.Singleton; import org.apache.commons.lang3.LocaleUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.LoggerFactory; /** * Utility class for language operations. * These can be used to build a crowdsourced translation system. * @author Alex Bogdanovski [alex@erudika.com] * @see Translation */ @Singleton public class LanguageUtils { private static final Logger logger = LoggerFactory.getLogger(LanguageUtils.class); private HashMap<String, Locale> allLocales = new HashMap<String, Locale>(); private HashMap<String, Integer> progressMap = new HashMap<String, Integer>(); private Map<String, String> deflang; private String deflangCode; private String keyPrefix = "language".concat(Config.SEPARATOR); private String progressKey = keyPrefix.concat("progress"); private static final int PLUS = -1; private static final int MINUS = -2; private Search search; private DAO dao; /** * Default constructor. * @param search a core search instance * @param dao a core persistence instance */ @Inject public LanguageUtils(Search search, DAO dao) { this.search = search; this.dao = dao; for (Object loc : LocaleUtils.availableLocaleList()) { Locale locale = new Locale(((Locale) loc).getLanguage()); String locstr = locale.getLanguage(); if (!StringUtils.isBlank(locstr)) { allLocales.put(locstr, locale); progressMap.put(locstr, 0); } } } /** * Returns a map of all translations for a given language. * Defaults to the default language which must be set. * @param appid appid name of the {@link com.erudika.para.core.App} * @param langCode the 2-letter language code * @return the language map */ public Map<String, String> readLanguage(String appid, String langCode) { if (StringUtils.isBlank(langCode) || !allLocales.containsKey(langCode)) { return getDefaultLanguage(); } if (search == null || dao == null) { return getDefaultLanguage(); } Sysprop s = dao.read(appid, keyPrefix.concat(langCode)); TreeMap<String, String> lang = new TreeMap<String, String>(); if (s == null || s.getProperties().isEmpty()) { Map<String, Object> terms = new HashMap<String, Object>(); terms.put("locale", langCode); terms.put("approved", true); List<Translation> tlist = search.findTerms(appid, Utils.type(Translation.class), terms, true); Sysprop saved = new Sysprop(keyPrefix.concat(langCode)); lang.putAll(getDefaultLanguage()); // copy default langmap int approved = 0; for (Translation trans : tlist) { lang.put(trans.getThekey(), trans.getValue()); saved.addProperty(trans.getThekey(), trans.getValue()); approved++; } if (approved > 0) { updateTranslationProgressMap(appid, langCode, approved); } dao.create(appid, saved); } else { Map<String, Object> loaded = s.getProperties(); for (String key : loaded.keySet()) { lang.put(key, loaded.get(key).toString()); } } return lang; } /** * Persists the language map in the data store. Overwrites any existing maps. * @param appid appid name of the {@link com.erudika.para.core.App} * @param langCode the 2-letter language code * @param lang the language map */ public void writeLanguage(String appid, String langCode, Map<String, String> lang) { if (lang == null || lang.isEmpty() || dao == null || StringUtils.isBlank(langCode) || !allLocales.containsKey(langCode)) { return; } // this will overwrite a saved language map! Sysprop s = new Sysprop(keyPrefix.concat(langCode)); Map<String, String> dlang = getDefaultLanguage(); int approved = 0; for (String key : dlang.keySet()) { if (lang.containsKey(key)) { s.addProperty(key, lang.get(key)); if (!dlang.get(key).equals(lang.get(key))) { approved++; } } else { s.addProperty(key, dlang.get(key)); } } if (approved > 0) { updateTranslationProgressMap(appid, langCode, approved); } dao.create(appid, s); } /** * Returns a non-null locale for a given language code. * @param langCode the 2-letter language code * @return a locale. default is English */ public Locale getProperLocale(String langCode) { langCode = StringUtils.substring(langCode, 0, 2); langCode = (StringUtils.isBlank(langCode) || !allLocales.containsKey(langCode)) ? "en" : langCode.trim().toLowerCase(); return allLocales.get(langCode); } /** * Returns the default language map. * @return the default language map or an empty map if the default isn't set. */ public Map<String, String> getDefaultLanguage() { if (deflang == null) { logger.warn("Default language not set."); deflang = new HashMap<String, String>(); getDefaultLanguageCode(); } return deflang; } /** * Sets the default language map. It is the basis language template which is to be translated. * @param deflang the default language map */ public void setDefaultLanguage(Map<String, String> deflang) { this.deflang = deflang; } /** * Returns the default language code * @return the 2-letter language code */ public String getDefaultLanguageCode() { if (deflangCode == null) { deflangCode = "en"; } return deflangCode; } /** * Sets the default language code. * @param langCode the 2-letter language code */ public void setDefaultLanguageCode(String langCode) { this.deflangCode = langCode; } /** * Returns a list of translations for a specific string. * @param appid appid name of the {@link com.erudika.para.core.App} * @param locale a locale * @param key the string key * @param pager the pager object * @return a list of translations */ public List<Translation> readAllTranslationsForKey(String appid, String locale, String key, Pager pager) { return search.findTerms(appid, Utils.type(Translation.class), Collections.singletonMap(Config._PARENTID, key), true, pager); } /** * Returns the set of all approved translations. * @param appid appid name of the {@link com.erudika.para.core.App} * @param langCode the 2-letter language code * @return a set of keys for approved translations */ public Set<String> getApprovedTransKeys(String appid, String langCode) { HashSet<String> approvedTransKeys = new HashSet<String>(); if (StringUtils.isBlank(langCode)) { return approvedTransKeys; } for (Map.Entry<String, String> entry : readLanguage(appid, langCode).entrySet()) { if (!getDefaultLanguage().get(entry.getKey()).equals(entry.getValue())) { approvedTransKeys.add(entry.getKey()); } } return approvedTransKeys; } /** * Returns a map of language codes and the percentage of translated string for that language. * @param appid appid name of the {@link com.erudika.para.core.App} * @return a map indicating translation progress */ public Map<String, Integer> getTranslationProgressMap(String appid) { if (dao == null) { return progressMap; } Sysprop progress = getProgressMap(appid); Map<String, Object> props = progress.getProperties(); for (String key : props.keySet()) { progressMap.put(key, (Integer) props.get(key)); } return progressMap; } /** * Returns a map of all language codes and their locales * @return a map of language codes to locales */ public Map<String, Locale> getAllLocales() { return allLocales; } /** * Approves a translation for a given language. * @param appid appid name of the {@link com.erudika.para.core.App} * @param langCode the 2-letter language code * @param key the translation key * @param value the translated string * @return true if the operation was successful */ public boolean approveTranslation(String appid, String langCode, String key, String value) { if (langCode == null || key == null || value == null || getDefaultLanguageCode().equals(langCode)) { return false; } Sysprop s = dao.read(appid, keyPrefix.concat(langCode)); if (s != null && !value.equals(s.getProperty(key))) { s.addProperty(key, value); dao.update(appid, s); updateTranslationProgressMap(appid, langCode, PLUS); return true; } return false; } /** * Disapproves a translation for a given language. * @param appid appid name of the {@link com.erudika.para.core.App} * @param langCode the 2-letter language code * @param key the translation key * @return true if the operation was successful */ public boolean disapproveTranslation(String appid, String langCode, String key) { if (langCode == null || key == null || getDefaultLanguageCode().equals(langCode)) { return false; } Sysprop s = dao.read(appid, keyPrefix.concat(langCode)); String defStr = getDefaultLanguage().get(key); if (s != null && !defStr.equals(s.getProperty(key))) { s.addProperty(key, defStr); dao.update(appid, s); updateTranslationProgressMap(appid, langCode, MINUS); return true; } return false; } /** * Updates the progress for all languages. * @param appid appid name of the {@link com.erudika.para.core.App} * @param langCode the 2-letter language code * @param value {@link #PLUS}, {@link #MINUS} or the total percent of completion (0-100) */ private void updateTranslationProgressMap(String appid, String langCode, int value) { if (dao == null || getDefaultLanguageCode().equals(langCode)) { return; } double defsize = getDefaultLanguage().size(); double approved = value; Sysprop progress = getProgressMap(appid); if (value == PLUS) { approved = Math.round((Integer) progress.getProperty(langCode) * (defsize / 100) + 1); } else if (value == MINUS) { approved = Math.round((Integer) progress.getProperty(langCode) * (defsize / 100) - 1); } if (approved > defsize) { approved = defsize; } if (defsize == 0) { progress.addProperty(langCode, 0); } else { progress.addProperty(langCode, (int) ((approved / defsize) * 100)); } dao.update(appid, progress); } private Sysprop getProgressMap(String appid) { Sysprop progress = dao.read(appid, progressKey); if (progress == null) { progress = new Sysprop(progressKey); for (String key : progressMap.keySet()) { progress.addProperty(key, progressMap.get(key)); } progress.addProperty(getDefaultLanguageCode(), 100); dao.create(appid, progress); } return progress; } }