// TranslationManager.java // ------------------------------------- // part of YACY // (C) by Michael Peter Christen; mc@yacy.net // first published on http://www.anomic.de // Frankfurt, Germany, 2004 // // This file ist contributed by Burkhard Buelte // last major change: 2016-04-05 // // $LastChangedDate$ // $LastChangedRevision$ // $LastChangedBy$ // // This program 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 2 of the License, or // (at your option) any later version. // // This program 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, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package net.yacy.utils.translation; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import net.yacy.cora.util.ConcurrentLog; import net.yacy.data.Translator; import net.yacy.search.Switchboard; import net.yacy.search.SwitchboardConstants; /** * Utility to create a translation master file from all existing translation * with check file.exists and translation text exists. * Also can join existing translation with master (currently ristrictive, * means only translation text exist in master are included in resultin Map */ public class TranslationManager extends TranslatorXliff { protected Map<String, Map<String, String>> mainTransLists; // current translation entries for one language protected String loadedLng; // language loaded in mainTransLists (2-letter code) public TranslationManager() { super(); } public TranslationManager(final File langfile) { mainTransLists = loadTranslationsLists(langfile); int pos = langfile.getName().indexOf('.'); if (pos >= 0) { loadedLng = langfile.getName().substring(0, pos); } } /** * Add a translation text to the current map map * * @param relFileName relative filename the translation belongs to * @param sourceLngTxt the english source text * @param targetLngTxt the translated text * @return true = if map was modified, otherwise false */ public boolean addTranslation(final String relFileName, final String sourceLngTxt, final String targetLngTxt) { assert mainTransLists != null; return addTranslation (mainTransLists, relFileName, sourceLngTxt, targetLngTxt); } /** * Helper to add a translation text to the map * * @param translation to add the text to * @param relFileName relative filename the translation belongs to * @param sourceLngTxt the english source text * @param targetLngTxt the translated text * @return true = if map was modified, otherwise false */ public boolean addTranslation(Map<String, Map<String, String>> translation, final String relFileName, final String sourceLngTxt, final String targetLngTxt) { boolean modified = false; Map<String, String> transFile; if (translation.containsKey(relFileName)) { transFile = translation.get(relFileName); } else { transFile = new LinkedHashMap<String, String>(); translation.put(relFileName, transFile); modified = true; } String oldLngTxt = transFile.put(sourceLngTxt, targetLngTxt); if (oldLngTxt == null) { modified = targetLngTxt != null; } else if (!oldLngTxt.equals(targetLngTxt)) { modified = true; } return modified; } /** * Get the translation list for a ui/html file * @param filename relative path to htroot * @return translation map or null */ public Map<String, String> getTranslationForFile(String filename) { return mainTransLists.get(filename); } /** * Get a translation target text * @param filename of the translation * @param source english source text * @return translated text or null */ public String getTranslation (String filename, String source) { Map<String, String> tmp = mainTransLists.get(filename); if (tmp != null) return tmp.get(source); else return null; } /** * Translates one file. The relFilepath is the file name as given in the * translation source lists. The source (english) file is expected under * htroot path. The destination file is under DATA/LOCALE and calculated * using the language of loaded data. * * @param relFilepath file name releative to htroot * @return true on success */ public boolean translateFile(String relFilepath) { assert loadedLng != null; assert mainTransLists != null; boolean result = false; if (mainTransLists.containsKey(relFilepath)) { Switchboard sb = Switchboard.getSwitchboard(); if (sb != null) { final String htRootPath = sb.getConfig(SwitchboardConstants.HTROOT_PATH, SwitchboardConstants.HTROOT_PATH_DEFAULT); final File sourceDir = new File(sb.getAppPath(), htRootPath); final File destDir = new File(sb.getDataPath("locale.translated_html", "DATA/LOCALE/htroot"), loadedLng); // get absolute file by adding relative filename final File sourceFile = new File(sourceDir, relFilepath); final File destFile = new File(destDir, relFilepath); result = translateFile(sourceFile, destFile, mainTransLists.get(relFilepath)); // do the translation } } return result; } /** * Create a master translation list by reading all translation files * If a masterOutputFile exists, content is preserved (loaded first) * * @param masterOutpuFile output file (xliff format) * @throws IOException */ public void createMasterTranslationLists(File masterOutputFile) throws IOException { if (masterOutputFile.exists()) // if file exists, conserve existing master content (may be updated by external tool) mainTransLists = loadTranslationsListsFromXliff(masterOutputFile); else mainTransLists = new TreeMap<String, Map<String, String>>(); List<String> lngFiles = Translator.langFiles(new File("locales")); for (String filename : lngFiles) { // load translation list ConcurrentLog.info("TRANSLATOR", "include translation file " + filename); Map<String, Map<String, String>> origTrans = loadTranslationsLists(new File("locales", filename)); for (String transfilename : origTrans.keySet()) { // get translation filename File checkfile = new File("htroot", transfilename); if (checkfile.exists()) { // include in master only if file exists // load content to compare translation text is included StringBuilder content = new StringBuilder(); BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(new FileInputStream(checkfile), StandardCharsets.UTF_8)); String line = null; while ((line = br.readLine()) != null) { content.append(line).append(net.yacy.server.serverCore.CRLF_STRING); } br.close(); } catch (final IOException e) { } finally { if (br != null) { try { br.close(); } catch (final Exception e) { } } } // compare translation list Map<String, String> origList = origTrans.get(transfilename); for (String sourcetxt : origList.keySet()) { if (content.indexOf(sourcetxt) >= 0) { String origVal = origList.get(sourcetxt); // it is possible that intentionally empty translation is given // in this case xliff target is missing (=null) if (origVal != null && !origVal.isEmpty()) { // if translation exists addTranslation(transfilename, sourcetxt, null); // add to master, set target text null } } } } else { ConcurrentLog.fine("TRANSLATOR", "skip file for translation " + transfilename + " (from " + filename + ")"); } } } // save as xliff file w/o language code saveAsXliff(null, masterOutputFile, mainTransLists); } /** * Joins translation master (xliff) and existing translation (lng). * Only texts existing in master are included from the lngfile, * the resulting map includes all keys from master with the matching translation * from lngfile. * * @param xlifmaster master (with en text to be translated) * @param lngfile existing translation * @return resulting map with all entries from master and translation from lngfile * @throws IOException */ public Map<String, Map<String, String>> joinMasterTranslationLists(File xlifmaster, File lngfile) throws IOException { final String filename = lngfile.getName(); mainTransLists = loadTranslationsListsFromXliff(xlifmaster); // load translation list ConcurrentLog.info("TRANSLATOR", "join into master translation file " + filename); Map<String, Map<String, String>> origTrans = loadTranslationsLists(lngfile); for (String transfilename : origTrans.keySet()) { // get translation filename // compare translation list Map<String, String> origList = origTrans.get(transfilename); Map<String, String> masterList = mainTransLists.get(transfilename); for (String sourcetxt : origList.keySet()) { if ((masterList != null) && (masterList.isEmpty() || masterList.containsKey(sourcetxt))) { // only if included in master (as all languages are in there but checked for occuance String origVal = origList.get(sourcetxt); // it is possible that intentionally empty translation is given // in this case xliff target is missing (=null) if (origVal != null && !origVal.isEmpty()) { addTranslation(transfilename, sourcetxt, origVal); } } } } return mainTransLists; } /** * Stores the loaded translations to a .lng file * @param lng * @param f * @return */ public boolean saveXliff(final String lng, File f) { return this.saveAsXliff(lng, f, mainTransLists); } /** * Stores the loaded translations to a .xlf file * @param lng * @param f * @return */ public boolean saveLng(final String lng, File f) { return this.saveAsLngFile(lng, f, mainTransLists); } /** * Total number of loaded translation entries * @return */ public int size() { int i = 0; for (Map<String, String> trans : mainTransLists.values()) { i += trans.size(); } return i; } }