forked from Qortal/qortal
CHANGED: read translations from XML files
This commit is contained in:
parent
d24f1de36a
commit
aa7bdaf713
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
23
src/globalization/ContextPaths.java
Normal file
23
src/globalization/ContextPaths.java
Normal 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("/");
|
||||
}
|
||||
|
||||
}
|
@ -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,12 +219,8 @@ public class TranslationXmlStreamReader {
|
||||
}
|
||||
|
||||
private void assureIsValidKey(String value) throws XMLStreamException {
|
||||
if(value.contains("/"))
|
||||
throw new javax.xml.stream.XMLStreamException("Key must not contain /");
|
||||
}
|
||||
|
||||
private String combinePaths(String left, String right) {
|
||||
return Paths.get(left, right).normalize().toString();
|
||||
if(!ContextPaths.isValidKey(value))
|
||||
throw new javax.xml.stream.XMLStreamException("Key is not valid");
|
||||
}
|
||||
|
||||
private void assureStartElement(XMLEvent event, String name) throws XMLStreamException {
|
||||
|
@ -1,24 +1,32 @@
|
||||
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 {
|
||||
|
||||
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;
|
||||
}
|
||||
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();
|
||||
@ -27,26 +35,112 @@ public class Translator {
|
||||
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);
|
||||
return translate(locale, templateKey, map);
|
||||
return translate(locale, contextPath, 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 contextPath, String templateKey, Map<String, Object> 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);
|
||||
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) {
|
||||
String template = defaultTemplate; // TODO: get template for the given locale if available
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user