/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.locale;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import divconq.lang.op.FuncResult;
import divconq.log.Logger;
import divconq.util.StringUtil;
import divconq.xml.XElement;
import divconq.xml.XmlReader;
// TODO code so that we work with Alpha 2, 3 or 4 codes automatically
public class Dictionary {
protected Dictionary parent = null;
protected Map<String,Translation> translations = new HashMap<String,Translation>();
// caching
protected Map<String,TranslationChain> translationscache = new HashMap<String,TranslationChain>();
public void setParent(Dictionary v) {
this.parent = v;
}
public Dictionary() {
}
public Collection<Translation> getTranslations() {
return this.translations.values();
}
public String findToken(ILocaleResource resource, LocaleDefinition locale, String token) {
if (StringUtil.isEmpty(token))
return null;
TranslationChain tc = this.getTranslationChain(locale);
if (tc != null) {
String fnd = tc.findToken(token);
if (fnd != null)
return fnd;
}
while (resource != null) {
tc = this.getTranslationChain(resource.getDefaultLocaleDefinition());
if (tc != null) {
String fnd = tc.findToken(token);
if (fnd != null)
return fnd;
}
resource = resource.getParentLocaleResource();
}
return null;
}
public String tr(ILocaleResource resource, LocaleDefinition locale, String token, Object... params) {
String val = this.findToken(resource, locale, token);
if (StringUtil.isEmpty(val))
return null;
// the expansion of variables is per Attribute Value Templates in XSLT
// http://www.w3.org/TR/xslt#attribute-value-templates
StringBuilder sb = new StringBuilder();
int lpos = 0;
int bpos = val.indexOf("{$");
while (bpos != -1) {
int epos = val.indexOf("}", bpos);
if (epos == -1)
break;
sb.append(val.substring(lpos, bpos));
lpos = epos + 1;
String varname = val.substring(bpos + 2, epos).trim();
// TODO add some formatting features for numbers/datetimes
Long parampos = StringUtil.parseInt(varname);
if ((parampos != null) && (parampos <= params.length)) {
if (params[parampos.intValue() - 1] != null)
sb.append(params[parampos.intValue() -1].toString());
}
else
sb.append(val.substring(bpos, epos + 1));
bpos = val.indexOf("{$", epos);
}
sb.append(val.substring(lpos));
return sb.toString();
}
public String trp(ILocaleResource resource, LocaleDefinition locale, String pluraltoken, String singulartoken, Object... params) {
if ((params.length > 0) && (params[0] instanceof Number) && (((Number)params[0]).intValue() == 1))
return this.tr(resource, locale, singulartoken, params);
return this.tr(resource, locale, pluraltoken, params);
}
public TranslationChain getTranslationChain(LocaleDefinition locale) {
if (locale == null)
return null;
TranslationChain tc = this.translationscache.get(locale.getName());
if (tc == null) {
TranslationChain parent = (this.parent != null) ? this.parent.getTranslationChain(locale) : null;
// we want language check above variant in chain
if (locale.hasVariant()) {
Translation tr = this.translations.get(locale.getLanguage());
if (tr != null) {
TranslationChain lang = new TranslationChain(parent, tr);
parent = lang;
}
}
Translation tr = this.translations.get(locale.getName());
if (tr != null)
tc = new TranslationChain(parent, tr);
else
tc = parent;
this.translationscache.put(locale.getName(), tc);
}
return tc;
}
public void load(Path fl) {
if (fl == null) {
// do not use Tr because dictionary may not be loaded
Logger.error("Unable to apply dictionary file, file null", "Code", "106");
return;
}
if (!Files.exists(fl)) {
// do not use Tr because dictionary may not be loaded
Logger.error("Missing dictionary file, expected: " + fl.normalize(), "Code", "107");
return;
}
FuncResult<XElement> xres3 = XmlReader.loadFile(fl, false);
if (xres3.hasErrors())
return;
this.load(xres3.getResult());
}
protected Translation getOrAddLocale(String name) {
Translation t = this.translations.get(name);
if (t == null) {
t = new Translation(name);
this.translations.put(name, t);
}
return t;
}
public void load(XElement trroot) {
if (trroot == null) {
// do not use Tr because dictionary may not be loaded
Logger.error("Unable to apply dictionary file, missing xml", "Code", "105");
return;
}
for (XElement lel : trroot.selectAll("Locale")) {
String lname = lel.getAttribute("Id");
if (StringUtil.isEmpty(lname))
continue;
Translation t = this.getOrAddLocale(lname);
for (XElement tel : lel.selectAll("Entry")) {
String tname = tel.getAttribute("Token");
if (StringUtil.isEmpty(tname))
continue;
String v = tel.getAttribute("Value");
if (StringUtil.isEmpty(v))
v = tel.getText();
if (StringUtil.isEmpty(v))
continue;
t.put(tname, v);
}
}
}
}