/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library 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 Lesser General Public License for more * details. */ package com.liferay.lang.builder; import com.liferay.portal.kernel.io.OutputStreamWriter; import com.liferay.portal.kernel.io.unsync.UnsyncBufferedReader; import com.liferay.portal.kernel.io.unsync.UnsyncBufferedWriter; import com.liferay.portal.kernel.io.unsync.UnsyncStringReader; import com.liferay.portal.kernel.language.LanguageConstants; import com.liferay.portal.kernel.language.LanguageValidator; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.util.NaturalOrderStringComparator; import com.liferay.portal.kernel.util.PropertiesUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.Validator; import com.liferay.portal.tools.ArgumentsUtil; import com.liferay.portal.tools.GitException; import com.liferay.portal.tools.GitUtil; import io.github.firemaples.language.Language; import io.github.firemaples.translate.Translate; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeMap; /** * @author Brian Wing Shun Chan * @author Hugo Huijser */ public class LangBuilder { public static final String AUTOMATIC_COPY = com.liferay.portal.tools.LangBuilder.AUTOMATIC_COPY; public static final String AUTOMATIC_TRANSLATION = com.liferay.portal.tools.LangBuilder.AUTOMATIC_TRANSLATION; public static void main(String[] args) throws Exception { Map<String, String> arguments = ArgumentsUtil.parseArguments(args); System.setProperty("line.separator", StringPool.NEW_LINE); String langDirName = GetterUtil.getString( arguments.get(LanguageConstants.KEY_DIR), LangBuilderArgs.LANG_DIR_NAME); String langFileName = GetterUtil.getString( arguments.get("lang.file"), LangBuilderArgs.LANG_FILE_NAME); boolean plugin = GetterUtil.getBoolean( arguments.get("lang.plugin"), LangBuilderArgs.PLUGIN); String portalLanguagePropertiesFileName = arguments.get( "lang.portal.language.properties.file"); boolean translate = GetterUtil.getBoolean( arguments.get("lang.translate"), LangBuilderArgs.TRANSLATE); String translateSubscriptionKey = arguments.get( "lang.translate.subscription.key"); boolean buildCurrentBranch = ArgumentsUtil.getBoolean( arguments, "build.current.branch", false); if (buildCurrentBranch) { String gitWorkingBranchName = ArgumentsUtil.getString( arguments, "git.working.branch.name", "master"); _processCurrentBranch( langFileName, plugin, portalLanguagePropertiesFileName, translate, translateSubscriptionKey, gitWorkingBranchName); return; } try { new LangBuilder( langDirName, langFileName, plugin, portalLanguagePropertiesFileName, translate, translateSubscriptionKey); } catch (Exception e) { ArgumentsUtil.processMainException(arguments, e); } } public LangBuilder( String langDirName, String langFileName, boolean plugin, String portalLanguagePropertiesFileName, boolean translate, String translateSubscriptionKey) throws Exception { _langDirName = langDirName; _langFileName = langFileName; _translate = translate; Translate.setSubscriptionKey(translateSubscriptionKey); _initKeysWithUpdatedValues(); if (plugin) { Class<?> clazz = getClass(); ClassLoader classLoader = clazz.getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream( "content/Language.properties"); if ((inputStream == null) && Validator.isNotNull(portalLanguagePropertiesFileName)) { inputStream = new FileInputStream( portalLanguagePropertiesFileName); } if (inputStream != null) { try { _portalLanguageProperties = PropertiesUtil.load( inputStream, StringPool.UTF8); } finally { inputStream.close(); } } else { _portalLanguageProperties = null; } } else { _portalLanguageProperties = null; } File renameKeysFile = new File(_langDirName + "/rename.properties"); if (renameKeysFile.exists()) { _renameKeys = _readProperties(renameKeysFile); } else { _renameKeys = null; } File propertiesFile = new File( _langDirName + "/" + _langFileName + ".properties"); String content = _orderProperties(propertiesFile); if (Validator.isNull(content)) { return; } // Locales that are not invoked by _createProperties should still be // rewritten to use the right line separator _orderProperties( new File(_langDirName + "/" + _langFileName + "_en_AU.properties")); _orderProperties( new File(_langDirName + "/" + _langFileName + "_en_GB.properties")); _orderProperties( new File(_langDirName + "/" + _langFileName + "_fr_CA.properties")); _copyProperties(propertiesFile, "en"); _createProperties(content, "ar"); // Arabic _createProperties(content, "eu"); // Basque _createProperties(content, "bg"); // Bulgarian _createProperties(content, "ca"); // Catalan _createProperties(content, "zh_CN"); // Chinese (China) _createProperties(content, "zh_TW"); // Chinese (Taiwan) _createProperties(content, "hr"); // Croatian _createProperties(content, "cs"); // Czech _createProperties(content, "da"); // Danish _createProperties(content, "nl"); // Dutch (Netherlands) _createProperties(content, "nl_BE", "nl"); // Dutch (Belgium) _createProperties(content, "et"); // Estonian _createProperties(content, "fi"); // Finnish _createProperties(content, "fr"); // French _createProperties(content, "gl"); // Galician _createProperties(content, "de"); // German _createProperties(content, "el"); // Greek _createProperties(content, "iw"); // Hebrew _createProperties(content, "hi_IN"); // Hindi (India) _createProperties(content, "hu"); // Hungarian _createProperties(content, "in"); // Indonesian _createProperties(content, "it"); // Italian _createProperties(content, "ja"); // Japanese _createProperties(content, "ko"); // Korean _createProperties(content, "lo"); // Lao _createProperties(content, "lt"); // Lithuanian _createProperties(content, "nb"); // Norwegian Bokmål _createProperties(content, "fa"); // Persian _createProperties(content, "pl"); // Polish _createProperties(content, "pt_BR"); // Portuguese (Brazil) _createProperties(content, "pt_PT", "pt_BR"); // Portuguese (Portugal) _createProperties(content, "ro"); // Romanian _createProperties(content, "ru"); // Russian _createProperties(content, "sr_RS"); // Serbian (Cyrillic) _createProperties(content, "sr_RS_latin"); // Serbian (Latin) _createProperties(content, "sk"); // Slovak _createProperties(content, "sl"); // Slovene _createProperties(content, "es"); // Spanish _createProperties(content, "sv"); // Swedish _createProperties(content, "th"); // Thai _createProperties(content, "tr"); // Turkish _createProperties(content, "uk"); // Ukrainian _createProperties(content, "vi"); // Vietnamese } private static String _getSpecialPropertyValue(String key) { if (key.equals(LanguageConstants.KEY_DIR)) { return LanguageConstants.VALUE_LTR; } else if (key.equals(LanguageConstants.KEY_LINE_BEGIN)) { return LanguageConstants.VALUE_LEFT; } else if (key.equals(LanguageConstants.KEY_LINE_END)) { return LanguageConstants.VALUE_RIGHT; } return StringPool.BLANK; } private static void _processCurrentBranch( String langFileName, boolean plugin, String portalLanguagePropertiesFileName, boolean translate, String translateSubscriptionKey, String gitWorkingBranchName) throws Exception { try { String basedir = ".././"; List<String> fileNames = GitUtil.getCurrentBranchFileNames( basedir, gitWorkingBranchName); for (String fileName : fileNames) { int pos = fileName.indexOf( "content/" + langFileName + ".properties"); if (pos == -1) { continue; } String langDirName = basedir + fileName.substring(0, pos + 7); new LangBuilder( langDirName, langFileName, plugin, portalLanguagePropertiesFileName, translate, translateSubscriptionKey); } } catch (GitException ge) { System.out.println(ge.getMessage()); } } private void _copyProperties(File file, String languageId) throws IOException { Path path = Paths.get( _langDirName, _langFileName + "_" + languageId + ".properties"); Files.copy(file.toPath(), path, StandardCopyOption.REPLACE_EXISTING); } private void _createProperties(String content, String languageId) throws IOException { _createProperties(content, languageId, null); } private void _createProperties( String content, String languageId, String parentLanguageId) throws IOException { File propertiesFile = new File( _langDirName + "/" + _langFileName + "_" + languageId + ".properties"); Properties properties = new Properties(); if (propertiesFile.exists()) { properties = _readProperties(propertiesFile); } Properties parentProperties = null; if (parentLanguageId != null) { File parentPropertiesFile = new File( _langDirName + "/" + _langFileName + "_" + parentLanguageId + ".properties"); if (parentPropertiesFile.exists()) { parentProperties = _readProperties(parentPropertiesFile); } } try (UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(new UnsyncStringReader(content)); UnsyncBufferedWriter unsyncBufferedWriter = new UnsyncBufferedWriter( new OutputStreamWriter( new FileOutputStream(propertiesFile), StringPool.UTF8))) { boolean firstLine = true; int state = 0; String line = null; while ((line = unsyncBufferedReader.readLine()) != null) { line = line.trim(); int pos = line.indexOf("="); if (pos != -1) { String key = line.substring(0, pos); String value = line.substring(pos + 1); if (((state == 1) && !key.startsWith("lang.")) || ((state == 2) && !key.startsWith("javax.portlet.")) || ((state == 3) && !key.startsWith("category.")) || ((state == 4) && !key.startsWith("model.resource.")) || ((state == 5) && !key.startsWith("action.")) || ((state == 7) && !key.startsWith("country.")) || ((state == 8) && !key.startsWith("currency.")) || ((state == 9) && !key.startsWith("language.")) || ((state != 9) && key.startsWith("language."))) { throw new RuntimeException( "File " + languageId + " with state " + state + " has key " + key); } String translatedText = properties.getProperty(key); if (_keysWithUpdatedValues.contains(key)) { translatedText = null; } if ((translatedText == null) && (parentProperties != null)) { translatedText = parentProperties.getProperty(key); } if ((translatedText == null) && (_renameKeys != null)) { String renameKey = _renameKeys.getProperty(key); if (renameKey != null) { translatedText = properties.getProperty(key); if ((translatedText == null) && (parentProperties != null)) { translatedText = parentProperties.getProperty( key); } } } if (translatedText != null) { if (translatedText.endsWith(AUTOMATIC_COPY)) { translatedText = ""; } } if ((translatedText == null) || translatedText.equals("")) { if (line.contains("{") || line.contains("<")) { translatedText = value + AUTOMATIC_COPY; } else if (line.contains("[")) { pos = line.indexOf("["); String baseKey = line.substring(0, pos); String translatedBaseKey = properties.getProperty( baseKey); if (Validator.isNotNull(translatedBaseKey)) { translatedText = translatedBaseKey; } else { translatedText = value + AUTOMATIC_COPY; } } else if (LanguageValidator.isSpecialPropertyKey(key)) { translatedText = _getSpecialPropertyValue(key); } else if (languageId.equals("el") && (key.equals("enabled") || key.equals("on") || key.equals("on-date"))) { translatedText = ""; } else if (languageId.equals("es") && key.equals("am")) { translatedText = ""; } else if (languageId.equals("fi") && (key.equals("on") || key.equals("the"))) { translatedText = ""; } else if (languageId.equals("it") && key.equals("am")) { translatedText = ""; } else if (languageId.equals("ja") && (key.equals("any") || key.equals("anytime") || key.equals("down") || key.equals("on") || key.equals("on-date") || key.equals("the"))) { translatedText = ""; } else if (languageId.equals("ko") && key.equals("the")) { translatedText = ""; } else { translatedText = _translate( "en", languageId, key, value, 0); if (Validator.isNull(translatedText)) { translatedText = value + AUTOMATIC_COPY; } else if (!key.startsWith("country.") && !key.startsWith("language.")) { translatedText = translatedText + AUTOMATIC_TRANSLATION; } } } if (Validator.isNotNull(translatedText)) { translatedText = _fixTranslation(translatedText); if (firstLine) { firstLine = false; } else { unsyncBufferedWriter.newLine(); } unsyncBufferedWriter.write(key + "=" + translatedText); unsyncBufferedWriter.flush(); } } else { if (line.startsWith("## Language settings")) { if (state == 1) { throw new RuntimeException(languageId); } state = 1; } else if (line.startsWith( "## Portlet descriptions and titles")) { if (state == 2) { throw new RuntimeException(languageId); } state = 2; } else if (line.startsWith("## Category titles")) { if (state == 3) { throw new RuntimeException(languageId); } state = 3; } else if (line.startsWith("## Model resources")) { if (state == 4) { throw new RuntimeException(languageId); } state = 4; } else if (line.startsWith("## Action names")) { if (state == 5) { throw new RuntimeException(languageId); } state = 5; } else if (line.startsWith("## Messages")) { if (state == 6) { throw new RuntimeException(languageId); } state = 6; } else if (line.startsWith("## Country")) { if (state == 7) { throw new RuntimeException(languageId); } state = 7; } else if (line.startsWith("## Currency")) { if (state == 8) { throw new RuntimeException(languageId); } state = 8; } else if (line.startsWith("## Language")) { if (state == 9) { throw new RuntimeException(languageId); } state = 9; } if (firstLine) { firstLine = false; } else { unsyncBufferedWriter.newLine(); } unsyncBufferedWriter.write(line); unsyncBufferedWriter.flush(); } } } } private String _fixEnglishTranslation(String key, String value) { // http://en.wikibooks.org/wiki/Basic_Book_Design/Capitalizing_Words_in_Titles // http://titlecapitalization.com // http://www.imdb.com if (value.contains(" this ")) { if (value.contains(".") || value.contains("?") || value.contains(":") || key.equals( "the-url-of-the-page-comparing-this-page-content-with-" + "the-previous-version")) { } else { value = StringUtil.replace(value, " this ", " This "); } } else { value = StringUtil.replace(value, " From ", " from "); } return value; } private String _fixTranslation(String value) { value = StringUtil.replace( value.trim(), new String[] { " ", "<b>", "</b>", "<i>", "</i>", " url ", "'", "' ;", """, "" ;", "ReCaptcha", "Captcha" }, new String[] { " ", "<strong>", "</strong>", "<em>", "</em>", " URL ", "\'", "\'", "\"", "\"", "reCAPTCHA", "CAPTCHA" }); return value; } private String _getMicrosoftLanguageId(String languageId) { if (languageId.equals("pt_BR") || languageId.equals("pt_PT")) { return "pt"; } else if (languageId.equals("hi_IN")) { return "hi"; } else if (languageId.equals("in")) { return "id"; } else if (languageId.equals("iw")) { return "he"; } else if (languageId.equals("nb")) { return "no"; } else if (languageId.equals("zh_CN")) { return "zh-CHS"; } else if (languageId.equals("zh_TW")) { return "zh-CHT"; } return languageId; } private void _initKeysWithUpdatedValues() throws Exception { File backupLanguageFile = new File( _langDirName + "/" + _langFileName + "_en.properties"); if (!backupLanguageFile.exists()) { return; } Properties backupLanguageProperties = _readProperties( backupLanguageFile); File languageFile = new File( _langDirName + "/" + _langFileName + ".properties"); Properties languageProperties = _readProperties(languageFile); Set<Map.Entry<Object, Object>> set = languageProperties.entrySet(); for (Map.Entry<Object, Object> entry : set) { String key = (String)entry.getKey(); String value = (String)entry.getValue(); if (!value.equals(backupLanguageProperties.get(key))) { _keysWithUpdatedValues.add(key); } } } private String _orderProperties(File propertiesFile) throws IOException { if (!propertiesFile.exists()) { return null; } String content = _read(propertiesFile); try (UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(new UnsyncStringReader(content)); UnsyncBufferedWriter unsyncBufferedWriter = new UnsyncBufferedWriter(new FileWriter(propertiesFile))) { Map<String, String> messages = new TreeMap<>( new NaturalOrderStringComparator(true, true)); boolean begin = false; boolean firstLine = true; String line = null; while ((line = unsyncBufferedReader.readLine()) != null) { int pos = line.indexOf("="); if (pos != -1) { String key = line.substring(0, pos); String value = line.substring(pos + 1); if (Validator.isNotNull(value)) { value = _fixTranslation(line.substring(pos + 1)); value = _fixEnglishTranslation(key, value); if (_portalLanguageProperties != null) { String portalValue = String.valueOf( _portalLanguageProperties.get(key)); if (value.equals(portalValue)) { System.out.println("Duplicate key " + key); } } messages.put(key, value); } } else { if (begin && line.equals(StringPool.BLANK)) { _sortAndWrite( unsyncBufferedWriter, messages, firstLine); } if (line.equals(StringPool.BLANK)) { begin = !begin; } if (firstLine) { firstLine = false; } else { unsyncBufferedWriter.newLine(); } unsyncBufferedWriter.write(line); } unsyncBufferedWriter.flush(); } if (!messages.isEmpty()) { _sortAndWrite(unsyncBufferedWriter, messages, firstLine); } } return _read(propertiesFile); } private String _read(File file) throws IOException { String s = new String( Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); return StringUtil.replace( s, StringPool.RETURN_NEW_LINE, StringPool.NEW_LINE); } private Properties _readProperties(File file) throws IOException { try (FileInputStream fileInputStream = new FileInputStream(file)) { return PropertiesUtil.load(fileInputStream, StringPool.UTF8); } } private void _sortAndWrite( UnsyncBufferedWriter unsyncBufferedWriter, Map<String, String> messages, boolean firstLine) throws IOException { boolean firstEntry = true; for (Map.Entry<String, String> entry : messages.entrySet()) { if (!firstLine || !firstEntry) { unsyncBufferedWriter.newLine(); } firstEntry = false; unsyncBufferedWriter.write(entry.getKey() + "=" + entry.getValue()); } messages.clear(); } private String _translate( String fromLanguageId, String toLanguageId, String key, String fromText, int limit) { if (!_translate) { return null; } // LPS-61961 if (toLanguageId.equals("da") || toLanguageId.equals("de") || toLanguageId.equals("fi") || toLanguageId.equals("ja") || toLanguageId.equals("nl") || toLanguageId.equals("pt_PT") || toLanguageId.equals("sv")) { return null; } // Limit the number of retries to 3 if (limit == 3) { return null; } Language fromLanguage = Language.fromString( _getMicrosoftLanguageId(fromLanguageId)); Language toLanguage = Language.fromString( _getMicrosoftLanguageId(toLanguageId)); if (toLanguage == null) { return null; } String toText = null; try { StringBundler sb = new StringBundler(8); sb.append("Translating "); sb.append(fromLanguageId); sb.append("_"); sb.append(toLanguageId); sb.append(" "); sb.append(key); sb.append(" "); sb.append(fromText); System.out.println(sb.toString()); toText = Translate.execute(fromText, fromLanguage, toLanguage); } catch (Exception e) { e.printStackTrace(); } // Keep trying if (toText == null) { return _translate( fromLanguageId, toLanguageId, key, fromText, ++limit); } return toText; } private final Set<String> _keysWithUpdatedValues = new HashSet<>(); private final String _langDirName; private final String _langFileName; private final Properties _portalLanguageProperties; private final Properties _renameKeys; private final boolean _translate; }