| /** |
| * |
| * Copyright (c) 2011, 2016 - Loetz GmbH&Co.KG (69115 Heidelberg, Germany) |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Christophe Loetz (Loetz GmbH&Co.KG) - initial implementation |
| * |
| * |
| * This copyright notice shows up in the generated Java code |
| * |
| */ |
| |
| package org.eclipse.osbp.xtext.i18n |
| |
| //import com.google.api.GoogleAPI |
| //import com.google.api.translate.Language |
| //import com.google.api.translate.Translate |
| import java.util.Comparator |
| import java.util.HashMap |
| import java.util.Properties |
| import java.util.TreeMap |
| import org.apache.commons.lang3.StringEscapeUtils |
| import org.eclipse.emf.ecore.EObject |
| import org.eclipse.emf.ecore.resource.Resource |
| import org.eclipse.osbp.preferences.ProductConfiguration |
| import org.eclipse.osbp.xtext.addons.AdvancedJvmModelGenerator |
| import org.eclipse.osbp.xtext.addons.EObjectHelper |
| import org.eclipse.xtext.RuleCall |
| import org.eclipse.xtext.generator.AbstractFileSystemAccess2 |
| import org.eclipse.xtext.generator.IFileSystemAccess |
| import org.eclipse.xtext.nodemodel.util.NodeModelUtils |
| |
| class StringComparator implements Comparator<String> { |
| override compare(String arg0, String arg1) { |
| if (arg0 < arg1) { |
| return -1 |
| } else if (arg0 > arg1) { |
| return 1 |
| } else { |
| return 0 |
| } |
| } |
| } |
| |
| class I18NModelGenerator extends AdvancedJvmModelGenerator { |
| |
| protected var properties = <String, String>newTreeMap(new StringComparator()) |
| protected var addedTranslatables = <String, String>newHashMap |
| |
| def addTranslatables(String additionals) { |
| // additionals are expected as comma separated list, pairs in list are expected to be separated by "->" |
| addedTranslatables.clear |
| var pairs = additionals.split(",") |
| for (pair : pairs) { |
| if (pair.contains("->")) { |
| var parts = pair.split("->") |
| var iter = parts.iterator |
| addedTranslatables.put(iter.next, iter.next) |
| } else { |
| addedTranslatables.put(pair, null) |
| } |
| } |
| } |
| |
| def getTranslatables(Resource resource, TreeMap<String, String> translatables) { |
| translatables.clear |
| |
| // add static translatables |
| for (additional : addedTranslatables.keySet) { |
| if (additional.contains(":")) { |
| var complex = additional.split(":") |
| translatables.put(I18NKeyGenerator.key(complex.get(1)), I18NKeyGenerator.value(complex.get(1))) |
| } else { |
| translatables.put(I18NKeyGenerator.key(additional), I18NKeyGenerator.value(additional)) |
| } |
| } |
| var EObject eobject = EObjectHelper.getSemanticElement(resource) |
| var node = NodeModelUtils.getNode(eobject) |
| var contentsIterator = node.asTreeIterable.iterator |
| while (contentsIterator.hasNext) { |
| var abstractNode = contentsIterator.next |
| |
| // find all translatables |
| if (abstractNode.grammarElement instanceof RuleCall) { |
| var rule = abstractNode.grammarElement as RuleCall |
| if (rule.rule.name.startsWith(I18nUtil.TRANSLATABLE)) { |
| var found = false |
| while (contentsIterator.hasNext && !found) { |
| abstractNode = contentsIterator.next |
| if (abstractNode.grammarElement instanceof RuleCall) { |
| var text = new StringBuffer(); |
| for (leaf : abstractNode.leafNodes) { |
| text.append(leaf.text.trim) |
| } |
| if (text.toString.contains("extends") || text.toString.contains("mapped superclass")) { |
| found = true; |
| } else if (text.toString.length > 0 && !text.toString.equals("\"\"")) { |
| translatables.put(I18NKeyGenerator.key(text.toString), |
| I18NKeyGenerator.value(text.toString)) |
| } |
| found = true |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| def void generateI18n(IFileSystemAccess fsa, Resource input) { |
| if (ProductConfiguration.willLanguagesAutocreate) { |
| if (fsa instanceof AbstractFileSystemAccess2) { |
| var erfsa = fsa as AbstractFileSystemAccess2 |
| var translations = <String, TreeMap<String, String>>newHashMap |
| |
| var supportedLanguages = ProductConfiguration.getLanguages() |
| properties.clear |
| getTranslatables(input, properties) |
| |
| for (localeId : supportedLanguages.keySet) { |
| val Properties prop = new Properties() |
| try { |
| if (localeId.equals("default")) { |
| prop.load( |
| erfsa.readBinaryFile("I18N.properties", |
| DSLOutputConfigurationProvider.DEFAULT_OUTPUT_I18N)) |
| } else { |
| prop.load( |
| erfsa.readBinaryFile("I18N_" + localeId + ".properties", |
| DSLOutputConfigurationProvider.DEFAULT_OUTPUT_I18N)) |
| } |
| } catch (Exception e) { |
| e.stackTrace |
| } |
| var p = <String, String>newTreeMap(new StringComparator()) |
| translations.put(localeId, p); |
| |
| // remove untranslated entries |
| for (props : prop.stringPropertyNames) { |
| if (prop.getProperty(props).length > 0) { |
| p.put(props, prop.getProperty(props)) |
| } |
| } |
| |
| // add new key to default |
| if (localeId.equals("default")) { |
| for (props : properties.keySet) { |
| translations.get("default").put(props, properties.get(props)) |
| } |
| } |
| } |
| |
| // try to translate it |
| // identify to google's billing service for translations |
| // GoogleAPI.setHttpReferrer(ProductConfiguration.languagesGoogleHttpReferrer) |
| // GoogleAPI.setKey(ProductConfiguration.languagesGoogleApiKey) |
| |
| for (localeId : supportedLanguages.keySet) { |
| if (!localeId.equals("default")) { |
| |
| // get google's language enumeration |
| // var googleLanguage = Language.fromString(supportedLanguages.get(localeId).getLanguage()) |
| |
| // insert new keys |
| for (key : translations.get("default").keySet) { |
| if (!findTranslation(localeId, key, translations)) { |
| |
| // auto translate |
| var value = translations.get("default").get(key) |
| |
| // we split numbers from text for better translation results |
| var payLoads = improveForTranslation(value) |
| |
| // as we use english model definitions, the original can be used as translation |
| if (localeId.equals("en")) { |
| translations.get(localeId).put(key, payLoads.join); |
| } else if (ProductConfiguration.languagesAutotranslate) { // if auto translation is enabled |
| var property = "" |
| |
| // rearrange text and numbers again |
| for (payLoad : payLoads) { |
| if (Character.isLetter(payLoad.charAt(0))) { |
| // use java utf-8 escape sequences |
| property = property + StringEscapeUtils.escapeJava(payLoad |
| // Translate.DEFAULT.execute(payLoad, Language.ENGLISH, googleLanguage) |
| ) |
| } else { |
| property = property + payLoad |
| } |
| } |
| |
| translations.get(localeId).put(key, property) |
| } else if (!ProductConfiguration.languagesAutotranslate) { // if auto translation is disabled, copy english to german as default |
| // TODO (JCD): Only for test purposes. To avoid the auto creation of the german translation values. |
| // if (localeId.equals("de")) { |
| // translations.get(localeId).put(key, payLoads.join); |
| // } |
| } |
| } |
| } |
| } |
| } |
| |
| // write out |
| for (localeId : supportedLanguages.keySet) { |
| var output = new StringBuilder() |
| output.append("#" + localeId + "\n") |
| for (key : translations.get(localeId).keySet) { |
| var value = translations.get(localeId).get(key) |
| // only ISO8859-1 is allowed inside the property values, thus always escape the text!!! |
| value = StringEscapeUtils.escapeJava(value) |
| output.append(key + "=" + value).append("\n") |
| } |
| if (localeId.equals("default")) { |
| erfsa.generateFile("I18N.properties", DSLOutputConfigurationProvider.DEFAULT_OUTPUT_I18N, output) |
| } else { |
| erfsa.generateFile("I18N_" + localeId + ".properties", |
| DSLOutputConfigurationProvider.DEFAULT_OUTPUT_I18N, output) |
| } |
| } |
| } |
| } |
| } |
| |
| def boolean findTranslation(String localeId, String key, HashMap<String, TreeMap<String, String>> translations) { |
| // find translations recursive until base language is reached - |
| // assumption: variants and countries can fall back to base language |
| // de_AT_X |
| var splitter = localeId.split("_") |
| if (splitter.length > 1) { |
| var part = <String>newArrayList(splitter) |
| part.remove(part.size - 1) |
| if (findTranslation(part.join("_"), key, translations)) { |
| return true |
| } |
| } |
| if (translations.containsKey(localeId) && translations.get(localeId).containsKey(key)) { |
| return true |
| } |
| return false |
| } |
| |
| def splitTextAndNumbers(String text) { |
| var array = <String>newArrayList |
| if (text === null || text.length == 0) { |
| return array |
| } |
| var current = "" |
| var len = text.length |
| var i = 1 |
| var lastChar = text.charAt(0) |
| current = current + lastChar |
| while (i < len) { |
| |
| // detect gap |
| if (Character.isLetter(text.charAt(i)) && Character.isDigit(lastChar) || |
| Character.isDigit(text.charAt(i)) && Character.isLetter(lastChar)) { |
| array.add(current) |
| current = "" |
| } |
| lastChar = text.charAt(i) |
| current = current + lastChar |
| i = i + 1 |
| } |
| if (current.length > 0) { |
| array.add(current) |
| } |
| return array |
| } |
| |
| def improveForTranslation(String key) { |
| var payLoad = key.replace("_", " ") |
| |
| //payLoad = payLoad.replaceAll("^m ", "") |
| var newArray = <String>newArrayList |
| var array = splitTextAndNumbers(payLoad) |
| for (element : array) { |
| if (Character.isLetter(element.charAt(0))) { |
| newArray.add(deAbbreviateTranslation(element)) |
| } else { |
| newArray.add(element) |
| } |
| } |
| // capitalize first letter in english |
| if(newArray.size > 0) { |
| newArray.set(0, newArray.get(0).toFirstUpper) |
| } |
| return newArray |
| } |
| |
| def deAbbreviateTranslation(String text) { |
| var payLoad = text |
| |
| // known abbreviations |
| payLoad = payLoad.replaceAll("(^|\\s)abv(\\s|$)", " alcohol by volume ") |
| payLoad = payLoad.replaceAll("(^|\\s)abw(\\s|$)", " alcohol by weight ") |
| payLoad = payLoad.replaceAll("(^|\\s)accnt(\\s|$)", " account ") |
| payLoad = payLoad.replaceAll("(^|\\s)addr(\\s|$)", " address ") |
| payLoad = payLoad.replaceAll("(^|\\s)base id(\\s|$)", " identifier ") |
| payLoad = payLoad.replaceAll("(^|\\s)base uuid(\\s|$)", " identifier ") |
| payLoad = payLoad.replaceAll("(^|\\s)bsin(\\s|$)", " brand single identification number ") |
| payLoad = payLoad.replaceAll("(^|\\s)cal(\\s|$)", " calories ") |
| payLoad = payLoad.replaceAll("(^|\\s)carb(\\s|$)", " carbohydrate ") |
| payLoad = payLoad.replaceAll("(^|\\s)chol(\\s|$)", " cholesterol ") |
| payLoad = payLoad.replaceAll("(^|\\s)cd(\\s|$)", " code ") |
| payLoad = payLoad.replaceAll("(^|\\s)desc(\\s|$)", " description ") |
| payLoad = payLoad.replaceAll("(^|\\s)diet(\\s|$)", " dietary ") |
| payLoad = payLoad.replaceAll("(^|\\s)dv(\\s|$)", " daily volume ") |
| payLoad = payLoad.replaceAll("(^|\\s)exp(\\s|$)", " expiring ") |
| payLoad = payLoad.replaceAll("(^|\\s)hdr(\\s|$)", " header ") |
| payLoad = payLoad.replaceAll("(^|\\s)hier(\\s|$)", " hierarchy ") |
| payLoad = payLoad.replaceAll("(^|\\s)id(\\s|$)", " identifier ") |
| payLoad = payLoad.replaceAll("(^|\\s)img(\\s|$)", " image ") |
| payLoad = payLoad.replaceAll("(^|\\s)lang(\\s|$)", " language ") |
| payLoad = payLoad.replaceAll("(^|\\s)nm(\\s|$)", " name ") |
| payLoad = payLoad.replaceAll("(^|\\s)num(\\s|$)", " number ") |
| payLoad = payLoad.replaceAll("(^|\\s)pkg(\\s|$)", " package ") |
| payLoad = payLoad.replaceAll("(^|\\s)ref(\\s|$)", " reference ") |
| payLoad = payLoad.replaceAll("(^|\\s)sat(\\s|$)", " saturated ") |
| payLoad = payLoad.replaceAll("(^|\\s)serv(\\s|$)", " serving ") |
| payLoad = payLoad.replaceAll("(^|\\s)sod(\\s|$)", " sodium ") |
| payLoad = payLoad.replaceAll("(^|\\s)sku(\\s|$)", " stock-keeping unit ") |
| payLoad = payLoad.replaceAll("(^|\\s)srp(\\s|$)", " suggested retail price ") |
| payLoad = payLoad.replaceAll("(^|\\s)tel(\\s|$)", " telephone ") |
| payLoad = payLoad.replaceAll("(^|\\s)tot(\\s|$)", " total ") |
| payLoad = payLoad.trim |
| return payLoad |
| } |
| |
| override doGenerate(Resource input, IFileSystemAccess fsa) { |
| // create i18n property files |
| fsa.generateI18n(input) |
| super.doGenerate(input, fsa) |
| } |
| } |