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();
//// testing the API client
//ApiClient client = ApiClient.getInstance();
//String test = client.executeCommand("GET blocks/height");
//System.out.println(test);
ApiClient client = ApiClient.getInstance();
String test = client.executeCommand("GET blocks/height");
System.out.println(test);
}
}

View File

@ -27,7 +27,7 @@ public class BlocksResource {
private ApiErrorFactory apiErrorFactory;
public BlocksResource() {
this(new ApiErrorFactory(new Translator()));
this(new ApiErrorFactory(Translator.getInstance()));
}
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;
case CONTEXT_PATH_ATTRIBUTE_NAME:
assureIsValidPathExtension(value);
contextPath = combinePaths(contextPath, value);
contextPath = ContextPaths.combinePaths(contextPath, value);
break;
default:
throw new javax.xml.stream.XMLStreamException("Unexpected attribute: " + name);
@ -180,7 +180,7 @@ public class TranslationXmlStreamReader {
switch(name.toString()) {
case TRANSLATION_KEY_ATTRIBUTE_NAME:
assureIsValidKey(value);
path = combinePaths(state.path, value);
path = ContextPaths.combinePaths(state.path, value);
break;
case TRANSLATION_TEMPLATE_ATTRIBUTE_NAME:
template = value;
@ -219,14 +219,10 @@ public class TranslationXmlStreamReader {
}
private void assureIsValidKey(String value) throws XMLStreamException {
if(value.contains("/"))
throw new javax.xml.stream.XMLStreamException("Key must not contain /");
if(!ContextPaths.isValidKey(value))
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 {
if(!isStartElement(event, name))
throw new javax.xml.stream.XMLStreamException("Unexpected start element: " + event.toString() + ", <" + name + "> expected");

View File

@ -1,13 +1,85 @@
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.HashMap;
import java.util.Locale;
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 settings.Settings;
public class Translator {
Map<Locale, Map<String, String>> translations = new HashMap<Locale, Map<String, String>>();
//XXX: replace singleton pattern by dependency injection?
private static Translator instance;
private Translator() {
InitializeTranslations();
}
public static Translator getInstance() {
if (instance == null) {
instance = new Translator();
}
return instance;
}
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) {
@ -16,37 +88,59 @@ public class Translator {
return map;
}
//XXX: replace singleton pattern by dependency injection?
private static Translator instance;
public String translate(Locale locale, String contextPath, String templateKey, AbstractMap.Entry<String, Object>... templateValues) {
Map<String, Object> map = createMap(templateValues);
return translate(locale, contextPath, templateKey, map);
}
public static Translator getInstance() {
if (instance == null) {
instance = new Translator();
public String translate(Locale locale, String contextPath, String templateKey, Map<String, Object> templateValues) {
return translate(locale, contextPath, templateKey, null, templateValues);
}
public String translate(Locale locale, String contextPath, String templateKey, String defaultTemplate, AbstractMap.Entry<String, Object>... templateValues) {
Map<String, Object> map = createMap(templateValues);
return translate(locale, contextPath, templateKey, defaultTemplate, map);
}
public String translate(Locale locale, String contextPath, String templateKey, String defaultTemplate, Map<String, Object> templateValues) {
// 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;
}
}
return instance;
}
public String translate(Locale locale, String templateKey, AbstractMap.Entry<String, Object>... templateValues) {
Map<String, Object> map = createMap(templateValues);
return translate(locale, templateKey, map);
}
public String translate(Locale locale, String templateKey, Map<String, Object> templateValues) {
return translate(locale, templateKey, null, templateValues);
}
public String translate(Locale locale, String templateKey, String defaultTemplate, AbstractMap.Entry<String, Object>... templateValues) {
Map<String, Object> map = createMap(templateValues);
return translate(locale, templateKey, defaultTemplate, map);
}
public String translate(Locale locale, String templateKey, String defaultTemplate, Map<String, Object> templateValues) {
String template = defaultTemplate; // TODO: get template for the given locale if available
if(template == null)
template = defaultTemplate; // fallback template
StringSubstitutor sub = new StringSubstitutor(templateValues);
String result = sub.replace(template);
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 String userpath = "";
//RPC
// RPC
private int rpcPort = 9085;
private List<String> rpcAllowed = new ArrayList<String>(Arrays.asList("127.0.0.1", "::1")); // ipv4, ipv6
private boolean rpcEnabled = true;
// Globalization
private String translationsPath = "globalization/";
private String[] translationsDefaultLocales = {"en-GB"};
// Constants
private static final String SETTINGS_FILENAME = "settings.json";
@ -129,6 +133,17 @@ public class Settings {
{
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() {
@ -163,4 +178,14 @@ public class Settings {
{
return this.rpcEnabled;
}
public String translationsPath()
{
return this.translationsPath;
}
public String[] translationsDefaultLocales()
{
return this.translationsDefaultLocales;
}
}