blob: 451fe973acb83da624acc161066e0a1adae49019 [file] [log] [blame]
/**
*
* 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)
}
}