CHANGED: read translations from XML files

This commit is contained in:
Kc 2018-10-04 16:15:46 +02:00
parent d24f1de36a
commit aa7bdaf713
7 changed files with 176 additions and 38 deletions

View File

@ -18,8 +18,8 @@ public class Start {
apiService.start(); apiService.start();
//// testing the API client //// testing the API client
//ApiClient client = ApiClient.getInstance(); ApiClient client = ApiClient.getInstance();
//String test = client.executeCommand("GET blocks/height"); String test = client.executeCommand("GET blocks/height");
//System.out.println(test); System.out.println(test);
} }
} }

View File

@ -27,7 +27,7 @@ public class BlocksResource {
private ApiErrorFactory apiErrorFactory; private ApiErrorFactory apiErrorFactory;
public BlocksResource() { public BlocksResource() {
this(new ApiErrorFactory(new Translator())); this(new ApiErrorFactory(Translator.getInstance()));
} }
public BlocksResource(ApiErrorFactory apiErrorFactory) { public BlocksResource(ApiErrorFactory apiErrorFactory) {

View File

@ -0,0 +1,23 @@
package globalization;
import java.nio.file.Paths;
public class ContextPaths {
public static boolean isValidKey(String value) {
return !value.contains("/");
}
public static String combinePaths(String left, String right) {
return Paths.get("/", left, right).normalize().toString();
}
public static String getParent(String path) {
return combinePaths(path, "..");
}
public static boolean isRoot(String path) {
return path.equals("/");
}
}

View File

@ -134,7 +134,7 @@ public class TranslationXmlStreamReader {
break; break;
case CONTEXT_PATH_ATTRIBUTE_NAME: case CONTEXT_PATH_ATTRIBUTE_NAME:
assureIsValidPathExtension(value); assureIsValidPathExtension(value);
contextPath = combinePaths(contextPath, value); contextPath = ContextPaths.combinePaths(contextPath, value);
break; break;
default: default:
throw new javax.xml.stream.XMLStreamException("Unexpected attribute: " + name); throw new javax.xml.stream.XMLStreamException("Unexpected attribute: " + name);
@ -180,7 +180,7 @@ public class TranslationXmlStreamReader {
switch(name.toString()) { switch(name.toString()) {
case TRANSLATION_KEY_ATTRIBUTE_NAME: case TRANSLATION_KEY_ATTRIBUTE_NAME:
assureIsValidKey(value); assureIsValidKey(value);
path = combinePaths(state.path, value); path = ContextPaths.combinePaths(state.path, value);
break; break;
case TRANSLATION_TEMPLATE_ATTRIBUTE_NAME: case TRANSLATION_TEMPLATE_ATTRIBUTE_NAME:
template = value; template = value;
@ -219,12 +219,8 @@ public class TranslationXmlStreamReader {
} }
private void assureIsValidKey(String value) throws XMLStreamException { private void assureIsValidKey(String value) throws XMLStreamException {
if(value.contains("/")) if(!ContextPaths.isValidKey(value))
throw new javax.xml.stream.XMLStreamException("Key must not contain /"); throw new javax.xml.stream.XMLStreamException("Key is not valid");
}
private String combinePaths(String left, String right) {
return Paths.get(left, right).normalize().toString();
} }
private void assureStartElement(XMLEvent event, String name) throws XMLStreamException { private void assureStartElement(XMLEvent event, String name) throws XMLStreamException {

View File

@ -1,24 +1,32 @@
package globalization; package globalization;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.InputStream;
import java.util.AbstractMap; import java.util.AbstractMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.stream.XMLStreamException;
import org.apache.commons.text.StringSubstitutor; import org.apache.commons.text.StringSubstitutor;
import settings.Settings;
public class Translator { public class Translator {
private Map<String, Object> createMap(Map.Entry<String, Object>[] entries) { Map<Locale, Map<String, String>> translations = new HashMap<Locale, Map<String, String>>();
HashMap<String, Object> map = new HashMap<>();
for (AbstractMap.Entry<String, Object> entry : entries) {
map.put(entry.getKey(), entry.getValue());
}
return map;
}
//XXX: replace singleton pattern by dependency injection? //XXX: replace singleton pattern by dependency injection?
private static Translator instance; private static Translator instance;
private Translator() {
InitializeTranslations();
}
public static Translator getInstance() { public static Translator getInstance() {
if (instance == null) { if (instance == null) {
instance = new Translator(); instance = new Translator();
@ -27,26 +35,112 @@ public class Translator {
return instance; return instance;
} }
public String translate(Locale locale, String templateKey, AbstractMap.Entry<String, Object>... templateValues) { private Settings settings() {
return Settings.getInstance();
}
private void InitializeTranslations() {
String path = this.settings().translationsPath();
File dir = new File(path);
File [] files = dir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".xml");
}
});
Map<Locale, Map<String, String>> translations = new HashMap<>();
TranslationXmlStreamReader translationReader = new TranslationXmlStreamReader();
for (File file : files) {
Iterable<TranslationEntry> entries = null;
try {
InputStream stream = new FileInputStream(file);
entries = translationReader.ReadFrom(stream);
} catch (FileNotFoundException ex) {
Logger.getLogger(Translator.class.getName()).log(Level.SEVERE, String.format("Translation file not found: %s", file), ex);
} catch (XMLStreamException ex) {
Logger.getLogger(Translator.class.getName()).log(Level.SEVERE, String.format("Error in translation file: %s", file), ex);
}
for(TranslationEntry entry : entries) {
Map<String, String> localTranslations = translations.get(entry.locale());
if(localTranslations == null) {
localTranslations = new HashMap<>();
translations.put(entry.locale(), localTranslations);
}
if(localTranslations.containsKey(entry.path())) {
Logger.getLogger(Translator.class.getName()).log(Level.SEVERE, String.format("Duplicate entry for locale '%s' and path '%s' in translation file '%s'. Falling back to default translations.", entry.locale(), entry.path(), file));
return;
}
}
}
// everything is fine, so we store all read translations
this.translations = translations;
}
private Map<String, Object> createMap(Map.Entry<String, Object>[] entries) {
HashMap<String, Object> map = new HashMap<>();
for (AbstractMap.Entry<String, Object> entry : entries) {
map.put(entry.getKey(), entry.getValue());
}
return map;
}
public String translate(Locale locale, String contextPath, String templateKey, AbstractMap.Entry<String, Object>... templateValues) {
Map<String, Object> map = createMap(templateValues); Map<String, Object> map = createMap(templateValues);
return translate(locale, templateKey, map); return translate(locale, contextPath, templateKey, map);
} }
public String translate(Locale locale, String templateKey, Map<String, Object> templateValues) { public String translate(Locale locale, String contextPath, String templateKey, Map<String, Object> templateValues) {
return translate(locale, templateKey, null, templateValues); return translate(locale, contextPath, templateKey, null, templateValues);
} }
public String translate(Locale locale, String templateKey, String defaultTemplate, AbstractMap.Entry<String, Object>... templateValues) { public String translate(Locale locale, String contextPath, String templateKey, String defaultTemplate, AbstractMap.Entry<String, Object>... templateValues) {
Map<String, Object> map = createMap(templateValues); Map<String, Object> map = createMap(templateValues);
return translate(locale, templateKey, defaultTemplate, map); return translate(locale, contextPath, templateKey, defaultTemplate, map);
} }
public String translate(Locale locale, String templateKey, String defaultTemplate, Map<String, Object> templateValues) { public String translate(Locale locale, String contextPath, String templateKey, String defaultTemplate, Map<String, Object> templateValues) {
String template = defaultTemplate; // TODO: get template for the given locale if available // look for requested language
String template = getTemplateFromNearestPath(locale, contextPath, templateKey);
if(template == null) {
// scan default languages
for(String language : this.settings().translationsDefaultLocales()) {
Locale defaultLocale = Locale.forLanguageTag(language);
template = getTemplateFromNearestPath(defaultLocale, contextPath, templateKey);
if(template != null)
break;
}
}
if(template == null)
template = defaultTemplate; // fallback template
StringSubstitutor sub = new StringSubstitutor(templateValues); StringSubstitutor sub = new StringSubstitutor(templateValues);
String result = sub.replace(template); String result = sub.replace(template);
return result; return result;
} }
private String getTemplateFromNearestPath(Locale locale, String contextPath, String templateKey) {
Map<String, String> localTranslations = this.translations.get(locale);
if(localTranslations == null)
return null;
String template = null;
while(true) {
String path = ContextPaths.combinePaths(contextPath, templateKey);
template = localTranslations.get(path);
if(template != null)
break; // found template
if(ContextPaths.isRoot(contextPath))
break; // nothing found
contextPath = ContextPaths.getParent(contextPath);
}
return template;
}
} }

View File

@ -24,11 +24,15 @@ public class Settings {
private int maxBytePerFee = 1024; private int maxBytePerFee = 1024;
private String userpath = ""; private String userpath = "";
//RPC // RPC
private int rpcPort = 9085; private int rpcPort = 9085;
private List<String> rpcAllowed = new ArrayList<String>(Arrays.asList("127.0.0.1", "::1")); // ipv4, ipv6 private List<String> rpcAllowed = new ArrayList<String>(Arrays.asList("127.0.0.1", "::1")); // ipv4, ipv6
private boolean rpcEnabled = true; private boolean rpcEnabled = true;
// Globalization
private String translationsPath = "globalization/";
private String[] translationsDefaultLocales = {"en-GB"};
// Constants // Constants
private static final String SETTINGS_FILENAME = "settings.json"; private static final String SETTINGS_FILENAME = "settings.json";
@ -129,6 +133,17 @@ public class Settings {
{ {
this.rpcEnabled = ((Boolean) json.get("rpcenabled")).booleanValue(); this.rpcEnabled = ((Boolean) json.get("rpcenabled")).booleanValue();
} }
// Globalization
if(json.containsKey("translationspath"))
{
this.translationsPath = ((String) json.get("translationspath"));
}
if(json.containsKey("translationsdefaultlocales"))
{
this.translationsDefaultLocales = ((String[]) json.get("translationsdefaultlocales"));
}
} }
public boolean isTestNet() { public boolean isTestNet() {
@ -163,4 +178,14 @@ public class Settings {
{ {
return this.rpcEnabled; return this.rpcEnabled;
} }
public String translationsPath()
{
return this.translationsPath;
}
public String[] translationsDefaultLocales()
{
return this.translationsDefaultLocales;
}
} }