com.erudika.para.i18n.LanguageUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.erudika.para.i18n.LanguageUtils.java

Source

/*
 * 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;
    }
}