Suche brauchbares I18N Lib

steffomio

Mitglied
Gibts da was?
Bei Apache habe ich nichts gefunden oder war grad blind auf beide Augen.

Ich dachte mir, das dieses Problem so populär sein sollte wie Logging, wo es auch ganz legendäre Libs wie Log4j gibt.

Also gibt es sowas auch für InternationalisatioN (I18N)?
 
Das benutze ich bereits, und habe da ein ziemlich hässliches "Gestell" drüber gebaut.
Das erstellt mir zwar eine Datei mit den noch nicht übersetzten Keys und funktioniert auch sonst ganz gut.
Aber mir ist das jedoch alles zu Primitiv. So gibts z.B. keine Fallbacks und so'n bisschen extra Schnickschnack wäre auch nicht schlecht.
Gibts da nicht was besseres?
Java:
package de.steffomio.sockserver.I18N;

import de.steffomio.sockserver.util.PropertiesUtil;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.ResourceBundle;
import org.apache.log4j.Logger;

/**
 *
 * @author Stefan Brinkmann <steffomio@arcor.de>
 */
public enum T {
    // available locales
    de_DE("de", "DE"), // <-- index[0] is default Translation
    en_EN("en", "EN");
    
    private static final Logger logger = Logger.getLogger(T.class);
    // current Locale
    private Locale locale = null;
    
    // Filename prefix of I18N Files
    private static String PREFIX_TRANSLATIONS_FILE = "translation";
    private static String KEY_NOT_FOUND = "::Key not found!";
    
    //current Translation
    private static ResourceBundle TRANSLATION = null;
    
    /**
     * 
     * Use T.activateTranslation(T.anyEntry)<br />
     * to load Resource of translation Data into Instance<br />
     * and set field as active Translation<br /><br />
     * 
     * To switch Translation use:<br />
     * T.activateTranslation(T.anyOtherEntry);<br />
     * T.translateAll();<br />
     * 
     * @param lang
     * @param country 
     */
    T(String lang, String country){
        locale = new Locale(lang, country);
        String Classpath = T.class.getPackage().getName().toString();
        int i = 0;
    }
    
    /**
     * 
     * @param translation 
     */
    public static void activateTranslation(T language){
        language.activateTranslation();
    }
    
    /**
     * loads translation Resource
     */
    public void activateTranslation(){
        TRANSLATION = ResourceBundle.getBundle(T.class.getPackage().getName().toString() + "." + PREFIX_TRANSLATIONS_FILE, locale);
    }
    
    /**
     * 
     * @param
     * @return T activated Language
     */
    public static T setLanguage(String language){
        
        //get all locales from T
        T[] tList = T.values();
        // search for available Translations
        for(int i = 0; i < tList.length; i++){
            if(language.equals(tList[i].toString())){
                tList[i].activateTranslation();
                return tList[i];
            }
        }
        
        // no valid translation found
        tList[0].activateTranslation();
        return tList[0];
    }
    
    /**
     * 
     * @param key
     * @param args
     * @return 
     */
    public static String t (String key, Object... args){
        // catch emty things
        if(key.equals("") || key == null){
            return "";
        }
        
        // return translation
        if(TRANSLATION.containsKey(key)){
            return MessageFormat.format(TRANSLATION.getString(key), args);
        }
        
        // write missed keys to developers best friend File ;)
        missedI18NKeys.put(key, "");
        return key + KEY_NOT_FOUND;
    }
    
    /**
    * for dev only
    * contains not found I18N Keys and write them to missedI18N.properties
    */
    public static void writeMissedKeys(){
        missedI18NKeys.writeOnExit();
    }
    
    private static MissedI18NKeys missedI18NKeys = new MissedI18NKeys();
    
    private static class MissedI18NKeys extends PropertiesUtil{
        
        private String _fileName = "./missed_I18N_keys.properties";
        
        public MissedI18NKeys(){
            //loadExternalFile(_fileName, true, true);
        }
        
        
        /**
         * try to sort and write possibly empty file with missed keys
         */
        public void writeOnExit(){
            try {
                storePropertiesSorted(_fileName, 5, "", true);
            } catch (IOException ex) {
                logger.error("Can't write out missed I18N Keys.", ex);
            }
        }
    }
}
 
Aber mir ist das jedoch alles zu Primitiv. So gibts z.B. keine Fallbacks und so'n bisschen extra Schnickschnack wäre auch nicht schlecht.

Ich nehme den WindowBuilder von Eclipse. Da kannst Du einstellen, wie die Art des ResourceBundles gebildet werden soll. Darüber hinaus, quasi als Schnickschnack gibt's nen schönen PropertyEdtior, in dem Du alle Übersetzungen parallel sehen und erkennen kannst, wo noch Übersetzungen fehlen.

Reicht bei mir aus, zumal ein Fallback doch in ResourceBundle eingebaut ist, oder meinst Du was anderes?
 

Anhänge

  • i18n.png
    i18n.png
    71,2 KB · Aufrufe: 43
Danke, die T class habe ich etwas umgebaut aber noch nicht wirklich getestet, weil ich grad an anderen bereichen baue.

Wenn es dich interessiert, hier die T und die dazu gehörige Util

Java:
public class T {
    
    private static final Logger logger = Logger.getLogger(T.class);
    
    // current locale
    private static Locs currentBundle = Locs.values()[0];
    
    // FallBack if key was not found
    public final static Locs FALL_BACK = Locs.en_EN;
    
    // write not existing keys to file
    private static KeyLogger keyLogger = new KeyLogger();
    
    /**
     * If FallBack don't has the key, the key itself will be displayed, Followed by the KEY_NOT_FOUND.<br/> You can call
     * writeMissedKeys() to write out missed keys to file in workdir
     */
    private static String KEY_NOT_FOUND = "::Key not found!";

    /**
     * handy way for direct access
     * @param key
     * @param args
     * @return 
     */
    public static String t(String key, Object... args) {
        return tr(key, args);
    }

    /**
     * Use this for wrappers in your Base classes
     * @param key
     * @param args
     * @return
     */
    public static String tr(String key, Object[] args) {
        // catch emty things
        if (key.equals("") || key == null) {
            return "";
        }

        // return translation
        if (currentBundle.rBundle.containsKey(key)) {
            return MessageFormat.format(currentBundle.rBundle.getString(key), args);

            // or FallBack
        } else if (FALL_BACK.rBundle.containsKey(key)) {
            return MessageFormat.format(FALL_BACK.rBundle.getString(key), args);
        }

        // or write missed keys to developers best friend File ;)
        keyLogger.put(key, "");
        return key + KEY_NOT_FOUND;
    }

    /**
     * set current used translationSet
     *
     * @param t
     */
    public static void setLanguage(Locs t) {
        currentBundle = t;
    }

    /**
     * for dev only contains not found I18N Keys and write them to missedI18N.properties
     */
    public static void writeMissedKeys() {
        keyLogger.write();
    }
    
    //<editor-fold defaultstate="collapsed" desc="Subclass Locs contains provided Locales">
    /**
     * 
     */
    public enum Locs{
        
        // available locales
        
        en_EN("en", "EN"),
        de_DE("de", "DE");
        
        public final String lang;
        public final String country;
        
        /**
         * The Package where the Recource Bundles are stored
         */
        private String resourcePackagePath = "de.steffomio.sockserver.resource.translation";
        /**
         * contains the ResourceBundle
         */
        public ResourceBundle rBundle;
        
        // current Locale
        private Locale locale;
        
        private Locs(String l, String c){
            lang = l;
            country = c;
            locale = new Locale(lang, country);
            try {
                rBundle = ResourceBundle.getBundle(resourcePackagePath, locale);
            } catch (MissingResourceException ex) {
                logger.fatal(ex.getMessage(), ex);
            }
        }
    }
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="SubClass KeyLogger">
    /**
     *
     */
    private static class KeyLogger extends PropertiesUtil {
        
        /**
         * try to sort and write possibly empty file with missed keys
         */
        public void write() {
            try {
                storePropertiesSorted("./missed_I18N_keys.properties", 5, "", true);
            } catch (IOException ex) {
                logger.error("Can't write out missed I18N Keys.", ex);
            }
        }
    }
    //</editor-fold>
}

und die Util:

Java:
public class PropertiesUtil extends Properties {

    /*
     *
     */
    public PropertiesUtil() {
    }

    /**
     *
     * @param prop
     * @param value search string
     * @return PropertiesUtil.Iteration
     */
    public String[] getByValue(Properties prop, String value) {
        // String array with max. possible entries
        String[] preList = new String[size()];

        // temp search Container 
        Map.Entry next;
        // iterate through Property
        Iterator iter = entrySet().iterator();
        int found = 0;
        while (iter.hasNext()) {
            // map next to Entry
            next = (Map.Entry) iter.next();
            // check and return
            if (next.getValue().equals(value)) {
                preList[found] = (String) next.getKey();
                found++;
            }
        }

        // cut length of array to found Items
        String[] list = new String[found];
        int i = 0;
        while (i < found) {
            list[i] = preList[i];
            i++;
        }
        return list;
    }

    /**
     *
     * @param path loads from <b>external</b> resources - not from inside the
     * executed .jar file.<br /><br />
     *
     * local Path is relative to executed .jar File or assigned working Dir <br
     * />like <b>dir/file.properties</b> absolute ath begins with Harddisk ID
     * like C:/dir1/dir2/file.properties <br /> don't use Backslash \ for File
     * seperator don't use trailing Slash for relative Paths
     *
     * @param overwrite overwrite existing Keys or not
     *
     * @param autoCreateFile creates file if not exists. Don't creates
     * Directories
     *
     * @return instance of Properties
     * @throws IOException
     */
    public void loadExternalFile(String path, boolean overwriteKeys, boolean autoCreateFile) throws IOException {
        Properties prop = new Properties();
        File file = new File(path);
        if (autoCreateFile == true && !file.isFile()) {
            file.createNewFile();
        }
        //load File to prop
        FileInputStream fis = new FileInputStream(file);
        prop.load(fis);
        mergeProperties(prop, overwriteKeys);
    }

    /**
     *
     * @param fullPath
     * @return
     * @throws IOException
     */
    /**
     *
     * @param fullPath full qualified slashed (not dotted!) Package Path <br
     * />like /com/me/application/resource/props.properties
     * @param overwriteKeys overwrite existing Keys or not
     */
    public void loadInternalFile(String fullPath, boolean overwriteKeys) throws IOException{
        Properties prop = new Properties();
        InputStream stream = PropertiesUtil.class.getResourceAsStream(fullPath);
        prop.load(stream);
        mergeProperties(prop, overwriteKeys);
        
    }

    /**
     *
     * Stores File outside of executed .jar File.<br /> For working Dir of jar
     * File simply use bare Filename like <br /> myFile.properties<br /> else
     * use full Paths like: <br /> String path =
     * System.getProperty("user.home"); String path =
     * System.getProperty("user.home") + "\\Local Settings\\ApplicationData";
     * String path = System.getProperty("user.dir"); // for working dir full
     * path
     *
     * @param prop
     * @param path
     * @param comment an in File Description (use \r\n for Linebreak)
     * @throws IOException
     */
    public void storeProperties(String path, String comment) throws IOException {
        store(new FileOutputStream(path), comment);
    }

    /**
     * same as storeProperties() but Properties are well sorted by keyName
     * 
     * @param path
     *      any suitable filepath
     * @param sortExtraLineBreak
     *      if property-key changes after String-index(n) an extra linebreak will be inserted for better owerview
     * @param comment
     *      comment on top of File. Maybe Licens Info, usage...
     * @param autoCreate 
     *      automatically create the File if not exists
     */
    public void storePropertiesSorted(String path, int sortExtraLineBreak, String comment, boolean autoCreate) throws IOException {

        // read keys into array
        String[] list = new String[size()];
        Enumeration keys = keys();
        int i = 0;
        while (keys.hasMoreElements()) {
            list[i++] = keys.nextElement().toString();
        }

        // sort keys
        Arrays.sort(list);

        // build file content
        String out = comment;
        String last = "";
        String now = "";
        int checkLength = sortExtraLineBreak;
        for (String s : list) {
            now = s.substring(0, s.length() < checkLength ? s.length() : checkLength);
            if (!now.equals(last)) {
                out += "\r\n";
                last = now;
            }
            out += "\r\n" + s + "=" + this.get(s);
        }

        // write out file
        FileUtil.writeString(path, out, autoCreate);
    }

    /**
     * read from other Properties and write into this Instance
     *
     * @param source the Properties from where will be readed
     * @param target the Properties to where will be written
     * @param overwrite if true, existing keys of target will be overwritten
     * @return
     */
    public void mergeProperties(Properties source, boolean overwrite) {
        Enumeration<Object> keys = source.keys();
        while (keys.hasMoreElements()) {
            String key = (String) keys.nextElement();
            if (overwrite == true) {
                put(key, source.getProperty(key));
            } else if (getProperty(key) == null) {
                put(key, source.getProperty(key));
            }
        }
    }
}

Einfach T.t("any key");
Nicht erstellte Keys werden in einer Datei im Arbeitsverzeichnis gespeichert, die man dann nur noch in die translation_xx_XX.properties kopieren muss -- und natürlich übersetzen.

Wems gefällt, darfs behalten.
 

Zurück
Oben