Package com.mucommander.text

Source Code of com.mucommander.text.Translator

/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.mucommander.text;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mucommander.commons.file.util.ResourceLoader;
import com.mucommander.commons.io.bom.BOMReader;
import com.mucommander.conf.MuConfigurations;
import com.mucommander.conf.MuPreference;


/**
* This class takes care of all text localization issues by loading all text entries from a dictionary file on startup
* and translating them into the current language on demand.
*
* <p>All public methods are static to make it easy to call them throughout the application.</p>
*
* <p>See dictionary file for more information about th dictionary file format.</p>
*
* @author Maxence Bernard
*/
public class Translator {
  private static final Logger LOGGER = LoggerFactory.getLogger(Translator.class);
 
    /** Contains key/value pairs for the current language */
    private static Map<String, String> dictionary;

    /** Contains key/value pairs for the default language, for entries that are not defined in the current language */
    private static Map<String, String> defaultDictionary;
   
    /** List of all available languages in the dictionary file */
    private static List<String> availableLanguages;

    /** Current language */
    private static String language;

    /** Default language */
    private final static String DEFAULT_LANGUAGE = "EN";

    /** Key for available languages */
    private final static String AVAILABLE_LANGUAGES_KEY = "available_languages";

    /**
     * Prevents instance creation.
     */
    private Translator() {}


    /**
     * Determines and sets the current language based on the given list of available languages
     * and the current language set in the preferences if it has been set, else on the system's language.
     * <p>
     * If the language set in the preferences or the system's language is not available, the default language as
     * defined by {@link #DEFAULT_LANGUAGE} will be used.
     * </p>
     *
     * @param availableLanguages list of available languages
     */
    private static void setCurrentLanguage(List<String> availableLanguages) {
        String lang = MuConfigurations.getPreferences().getVariable(MuPreference.LANGUAGE);

        if(lang==null) {
            // language is not set in preferences, use system's language
            // Try to match language with the system's language, only if the system's language
            // has values in dictionary, otherwise use default language (English).
            lang = Locale.getDefault().getLanguage();
            LOGGER.info("Language not set in preferences, trying to match system's language ("+lang+")");
        }
        else {
          LOGGER.info("Using language set in preferences: "+lang);
        }

        // Determines if the list of available languages contains the language (case-insensitive)
        boolean containsLanguage = false;
        int nbAvailableLanguages = availableLanguages.size();
        for(int i=0; i<nbAvailableLanguages; i++) {
            if(availableLanguages.get(i).equalsIgnoreCase(lang)) {
                containsLanguage = true;
                lang = availableLanguages.get(i);   // Use the proper case variation
                break;
            }
        }

        // Determines if language is one of the languages declared as available
        if(containsLanguage) {
            // Language is available
            Translator.language = lang;
            LOGGER.debug("Language "+lang+" is available.");
        }
        else {
            // Language is not available, fall back to default language (English)
            Translator.language = DEFAULT_LANGUAGE;
            LOGGER.debug("Language "+lang+" is not available, falling back to default language "+DEFAULT_LANGUAGE);
        }
   
        // Set preferred language in configuration file
        MuConfigurations.getPreferences().setVariable(MuPreference.LANGUAGE, Translator.language);

        LOGGER.debug("Current language has been set to "+Translator.language);
    }

    /**
     * Loads the default dictionary file.
     *
     * @throws IOException thrown if an IO error occurs.
     */
    public static void loadDictionaryFile() throws IOException {
        loadDictionaryFile(com.mucommander.RuntimeConstants.DICTIONARY_FILE);
    }

    /**
     * Loads the specified dictionary file, which contains localized text entries.
     *
     * @param filePath path to the dictionary file
     * @throws IOException thrown if an IO error occurs.
     */
    public static void loadDictionaryFile(String filePath) throws IOException {
        availableLanguages = new Vector<String>();
        dictionary         = new Hashtable<String, String>();
        defaultDictionary  = new Hashtable<String, String>();

        BufferedReader br = new BufferedReader(new BOMReader(ResourceLoader.getResourceAsStream(filePath)));
        String line;
        String keyLC;
        String lang;
        String text;
        StringTokenizer st;

        while((line = br.readLine())!=null) {
            if (!line.trim().startsWith("#") && !line.trim().equals("")) {
                st = new StringTokenizer(line);

                try {
                    // Sets delimiter to ':'
                    keyLC = st.nextToken(":").trim().toLowerCase();

                    // Special key that lists available languages, must
                    // be defined before any other entry
                    if(Translator.language==null && keyLC.equals(AVAILABLE_LANGUAGES_KEY)) {
                        // Parse comma separated languages
                        st = new StringTokenizer(st.nextToken(), ",\n");
                        while(st.hasMoreTokens())
                            availableLanguages.add(st.nextToken().trim());

                        LOGGER.debug("Available languages= "+availableLanguages);

                        // Determines current language based on available languages and preferred language (if set) or system's language
                        setCurrentLanguage(availableLanguages);

                        continue;
                    }

                    lang = st.nextToken().trim();

                    // Delimiter is now line break
                    text = st.nextToken("\n");
                    text = text.substring(1, text.length());

                    // Replace "\n" strings in the text by \n characters
                    int pos = 0;

                    while ((pos = text.indexOf("\\n", pos))!=-1)
                        text = text.substring(0, pos)+"\n"+text.substring(pos+2, text.length());

                    // Replace "\\uxxxx" unicode strings by the designated character
                    pos = 0;

                    while ((pos = text.indexOf("\\u", pos))!=-1)
                        text = text.substring(0, pos)+(char)(Integer.parseInt(text.substring(pos+2, pos+6), 16))+text.substring(pos+6, text.length());

                    // Add entry for current language, or for default language if a value for current language wasn't already set
                    if(lang.equalsIgnoreCase(language)) {
                        dictionary.put(keyLC, text);
                        // Remove the default dictionary entry as it will not be used (saves some memory).
                        defaultDictionary.remove(keyLC);
                    }
                    else if(lang.equalsIgnoreCase(DEFAULT_LANGUAGE) && dictionary.get(keyLC)==null) {
                        defaultDictionary.put(keyLC, text);
                    }
                }
                catch(Exception e) {
                    LOGGER.info("error in line " + line + " (" + e + ")");
                    throw new IOException("Syntax error in line " + line);
                }
            }
        }
        br.close();
    }

    /**
     * Returns the current language as a language code ("EN", "FR", "pt_BR", ...).
     *
     * @return lang a language code
     */
    public static String getLanguage() {
        return language;
    }
 
 
    /**
     * Returns an array of available languages, expressed as language codes ("EN", "FR", "pt_BR"...).
     * The returned array is sorted by language codes in case insensitive order.
     *
     * @return an array of language codes.
     */
    public static String[] getAvailableLanguages() {
        String[] languages = availableLanguages.toArray(new String[availableLanguages.size()]);
        Arrays.sort(languages, String.CASE_INSENSITIVE_ORDER);

        return languages;
    }


    /**
     * Returns <code>true</code> if the given entry's key has a value in the current language.
     * If the <code>useDefaultLanguage</code> parameter is <code>true</code>, entries that have no value in the
     * {@link #getLanguage() current language} but one in the {@link #DEFAULT_LANGUAGE} will be considered as having
     * a value (<code>true</code> will be returned).
     *
     * @param key key of the requested dictionary entry (case-insensitive)
     * @param useDefaultLanguage if <code>true</code>, entries that have no value in the {@link #getLanguage() current
     * language} but one in the {@link #DEFAULT_LANGUAGE} will be considered as having a value
     * @return <code>true</code> if the given key has a corresponding value in the current language.
     */
    public static boolean hasValue(String key, boolean useDefaultLanguage) {
        return dictionary.get(key.toLowerCase())!=null
                || (useDefaultLanguage && defaultDictionary.get(key.toLowerCase())!=null);
    }

    /**
     * Returns the localized text String for the given key expressd in the current language, or in the default language
     * if there is no value for the current language. Entry parameters (%1, %2, ...), if any, are replaced by the
     * specified values.
     *
     * @param key key of the requested dictionary entry (case-insensitive)
     * @param paramValues array of parameters which will be used as values for variables.
     * @return the localized text String for the given key expressd in the current language
     */
    public static String get(String key, String... paramValues) {
        // Returns the localized text
        String text = dictionary.get(key.toLowerCase());

        if (text==null) {
            text = defaultDictionary.get(key.toLowerCase());

            if(text==null) {
              LOGGER.debug("No value for "+key+", returning key");
                return key;
            }
            else {
              LOGGER.debug("No value for "+key+" in language "+language+", using "+DEFAULT_LANGUAGE+" value");
                // Don't return yet, parameters need to be replaced
            }
        }

        // Replace %1, %2 ... parameters by their value
        if (paramValues!=null) {
            int pos = -1;
            for(int i=0; i<paramValues.length; i++) {
                while(++pos<text.length()-1 && (pos = text.indexOf("%"+(i+1), pos))!=-1)
                    text = text.substring(0, pos)+paramValues[i]+text.substring(pos+2, text.length());
            }
        }

        // Replace $[key] occurrences by their value
        int pos = 0;
        int pos2;
        String variable;

        while ((pos = text.indexOf("$[", pos))!=-1) {
            pos2 = text.indexOf("]", pos+1);
            variable = text.substring(pos+2, pos2);
            text = text.substring(0, pos)+get(variable, paramValues)+text.substring(pos2+1, text.length());
        }

        return text;
    }


    /**
     * Based on the number of supplied command line parameters, this method either :
     * <ul>
     * <li>Looks for and reports any missing or unused dictionary entry,
     * using the supplied source folder path to look inside source files
     * for references to dictionary entries.
     * <li>Merges a new language's entries from a dictionary file into a new one.
     * </ul>
     */
    public static void main(String args[]) throws IOException {
        /* 
        // Looks for missing and unused entries
        if(args.length<4) {
        Enumeration languages = dictionaries.keys();
        Vector langsV = new Vector();
        while(languages.hasMoreElements())
        langsV.add(languages.nextElement());
       
        String langs[] = new String[langsV.size()];
        langsV.toArray(langs);
     
        com.mucommander.commons.file.AbstractFile sourceFolder = com.mucommander.commons.file.AbstractFile.getFile(args[0]);
   
        System.out.println("\n##### Looking for missing entries #####");
        checkMissingEntries(sourceFolder, langs);

        System.out.println("\n##### Looking for unused entries #####");
        checkUnusedEntries(sourceFolder, langs);
        }
        // Integrates a new language into the dictionary
        else {
        */
        // Parameters order: originalFile newLanguageFile resultingFile newLanguage
        if(args.length<4) {
            System.out.println("usage: Translator originalFile newLanguageFile mergedFile newLanguage");
            return;
        }

        addLanguageToDictionary(args[0], args[1], args[2], args[3]);
        /*
          }
        */
    }


    /**
     * Checks for missing dictionary entries in the given file or folder and reports them on the standard output.
     * If the given file is a folder, recurse on each file that it contains, if it's a 'regular' file and the
     * extension is '.java', looks for any calls to {@link #Translator.get(String), Translator.get()} and checks
     * that the request entry has a value in each language's dictionary.
     */
    /*
      private static void checkMissingEntries(com.mucommander.commons.file.AbstractFile file, String languages[]) throws IOException {
      if(file.isDirectory()) {
      com.mucommander.commons.file.AbstractFile children[] = file.ls();
      for(int i=0; i<children.length; i++)
      checkMissingEntries(children[i], languages);
      }
      else if(file.getName().endsWith(".java")) {
      BufferedReader br = new BufferedReader(new InputStreamReader(file.getInputStream()));
      String line;
      int pos;
      String entry;
      String value;
      String language;
      while((line=br.readLine())!=null) {
      if(!line.trim().startsWith("//") && (pos=line.indexOf("Translator.get(\""))!=-1) {
      try {
      entry = line.substring(pos+16, line.indexOf("\"", pos+16));
      for(int i=0; i<languages.length; i++) {
      language = languages[i];
      if((String)((Hashtable)dictionaries.get(language)).get(entry)!=null || (!language.equalsIgnoreCase("en") && (value=(String)((Hashtable)dictionaries.get("en")).get(entry))!=null && value.startsWith("$")))
      continue;
      System.out.println("Missing "+language.toUpperCase()+" entry '"+entry+"' in "+file.getAbsolutePath());
      }
      }
      catch(Exception e) {
      }
      }
      }
      br.close();
      }
      }
    */


    /**
     * Checks all enties in all dictionaries, checks that they are used in at least one source file
     * in or under the supplied folder, and reports unused entries on the standard output.
     */
    /*
      private static void checkUnusedEntries(com.mucommander.commons.file.AbstractFile sourceFolder, String languages[]) throws IOException {
      Enumeration entries;
      String entry;
      for(int i=0; i<languages.length; i++) {
      entries = ((Hashtable)dictionaries.get(languages[i])).keys();
      while(entries.hasMoreElements()) {
      entry = (String)entries.nextElement();

      if(!isEntryUsed(entry, sourceFolder))
      System.out.println("Unused "+languages[i].toUpperCase()+" entry "+entry);
      }
      }
      }
    */

    /**
     * Checks if the given entry is used in the supplied file or folder.
     */
    /*
      private static boolean isEntryUsed(String entry, com.mucommander.commons.file.AbstractFile file) throws IOException {
      if(file.isDirectory()) {
      com.mucommander.commons.file.AbstractFile children[] = file.ls();
      for(int i=0; i<children.length; i++)
      if(isEntryUsed(entry, children[i]))
      return true;
      return false;
      }
      else if(file.getName().endsWith(".java")) {
      BufferedReader br = new BufferedReader(new InputStreamReader(file.getInputStream()));
      String line;
      int pos;
      while((line=br.readLine())!=null) {
      if(!line.trim().startsWith("//") && (pos=line.indexOf("\""+entry+"\""))!=-1) {
      br.close();
      return true;
      }
      }
      br.close();
      return false;
      }
   
      return false;
      }
    */

    /**
     * Merges a dictionary file with another one, adding entries of the specified new language.
     * <p>This method is used to merge dictionary files sent by contributors.
     *
     * @param originalFile current version of the dictionary file
     * @param newLanguageFile dictionary file containing new language entries
     * @param resultingFile merged dictionary file
     * @param newLanguage new language
     * @throws IOException if an I/O error occurred
     */
    private static void addLanguageToDictionary(String originalFile, String newLanguageFile, String resultingFile, String newLanguage) throws IOException {
        // Initialize streams
        BufferedReader originalFileReader = new BufferedReader(new BOMReader(new FileInputStream(originalFile)));
        BufferedReader newLanguageFileReader = new BufferedReader(new BOMReader(new FileInputStream(newLanguageFile)));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(resultingFile), "UTF-8"));

        // Parse new language's entries
        String line;
        int lineNum = 0;
        String key;
        String lang;
        String text;
        StringTokenizer st;
        Map<String, String> newLanguageEntries = new Hashtable<String, String>();
        while ((line = newLanguageFileReader.readLine())!=null) {
            try {
                if (!line.trim().startsWith("#") && !line.trim().equals("")) {
                    st = new StringTokenizer(line);

                    // Sets delimiter to ':'
                    key = st.nextToken(":");
                    lang = st.nextToken();

                    if(lang.equalsIgnoreCase(newLanguage)) {
                        // Delimiter is now line break
                        text = st.nextToken("\n");
                        text = text.substring(1, text.length());

                        newLanguageEntries.put(key, text);
                    }
                }
                lineNum++;
            }
            catch(Exception e) {
              LOGGER.warn("caught "+e+" at line "+lineNum);
                return;
            }
        }

        // Insert new language entries in resulting file
        boolean keyProcessedForNewLanguage = false;
        String currentKey = null;
        while ((line = originalFileReader.readLine())!=null) {
            boolean emptyLine = line.trim().startsWith("#") || line.trim().equals("");
            if (!keyProcessedForNewLanguage && (emptyLine || (currentKey!=null && !line.startsWith(currentKey+":")))) {
                if(currentKey!=null) {
                    String newLanguageValue = newLanguageEntries.get(currentKey);
                    if(newLanguageValue!=null) {
                        // Insert new language's entry in resulting file
                      LOGGER.info("New language entry for key="+currentKey+" value="+newLanguageValue);
                        pw.println(currentKey+":"+newLanguage+":"+newLanguageValue);
                    }

                    keyProcessedForNewLanguage = true;
                }
            }

            if(!emptyLine) {
                // Parse entry
                st = new StringTokenizer(line);

                // Set delimiter to ':'
                key = st.nextToken(":");
                lang = st.nextToken();

                if(!key.equals(currentKey)) {
                    currentKey = key;
                    keyProcessedForNewLanguage = false;
                }

                if(lang.equalsIgnoreCase(newLanguage)) {
                    // Delimiter is now line break
                    String existingNewLanguageValue = st.nextToken("\n");
                    existingNewLanguageValue = existingNewLanguageValue.substring(1, existingNewLanguageValue.length());
                    String newLanguageValue = newLanguageEntries.get(currentKey);

                    if(newLanguageValue!=null) {
                        if(!existingNewLanguageValue.equals(newLanguageValue))
                          LOGGER.warn("Warning: found an updated value for key="+currentKey+", using new value="+newLanguageValue+" existing value="+existingNewLanguageValue);

                        pw.println(currentKey+":"+newLanguage+":"+newLanguageValue);
                    }
                    else {
                      LOGGER.warn("Existing dictionary has a value for key="+currentKey+" that is missing in the new dictionary file, using existing value= "+existingNewLanguageValue);
                        pw.println(currentKey+":"+newLanguage+":"+existingNewLanguageValue);
                    }

                    keyProcessedForNewLanguage = true;
                }
                else {
                    pw.println(line);
                }
            }
            else {
                pw.println(line);
            }
        }

        newLanguageFileReader.close();
        originalFileReader.close();
        pw.close();
    }
}
TOP

Related Classes of com.mucommander.text.Translator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.