/*******************************************************************************
* Copyright 2014 Rafael Garcia Moreno.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.bladecoder.engineeditor.model;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Properties;
import com.bladecoder.engine.actions.Action;
import com.bladecoder.engine.actions.DisableActionAction;
import com.bladecoder.engine.assets.EngineAssetManager;
import com.bladecoder.engine.i18n.I18N;
import com.bladecoder.engine.model.BaseActor;
import com.bladecoder.engine.model.CharacterActor;
import com.bladecoder.engine.model.Dialog;
import com.bladecoder.engine.model.DialogOption;
import com.bladecoder.engine.model.InteractiveActor;
import com.bladecoder.engine.model.Scene;
import com.bladecoder.engine.model.SpriteActor;
import com.bladecoder.engine.model.TextRenderer;
import com.bladecoder.engine.model.Verb;
import com.bladecoder.engine.model.World;
import com.bladecoder.engine.util.ActionUtils;
import com.bladecoder.engineeditor.common.EditorLogger;
import com.bladecoder.engineeditor.common.OrderedProperties;
public class I18NHandler {
public static final String WORLD_VERBS_PREFIX = "default";
private String modelPath;
private String worldFilename;
private String chapterFilename;
private Properties i18nWorld;
private Properties i18nChapter;
public I18NHandler(String modelPath) {
this.worldFilename = EngineAssetManager.WORLD_FILENAME_JSON;
this.modelPath = modelPath;
if (!modelPath.endsWith("/"))
this.modelPath = modelPath + "/";
i18nWorld = loadI18N(worldFilename);
}
private String getI18NFilename(String modelFilename) {
return modelPath + modelFilename.substring(0, modelFilename.lastIndexOf('.')) + ".properties";
}
public void load(String chapterId) {
this.chapterFilename = chapterId + ".chapter";
i18nChapter = loadI18N(chapterFilename);
}
private Properties loadI18N(String modelFilename) {
String i18nFilename = getI18NFilename(modelFilename);
// To save in alphabetical order we use the OrderedProperties
Properties i18n = new OrderedProperties();
try {
i18n.load(new InputStreamReader(new FileInputStream(i18nFilename), I18N.ENCODING));
} catch (IOException e) {
EditorLogger.error("ERROR LOADING BUNDLE: " + i18nFilename);
}
return i18n;
}
public String getWorldTranslation(String key) {
if (key == null || key.isEmpty() || key.charAt(0) != I18N.PREFIX || i18nWorld == null)
return key;
return i18nWorld.getProperty(key.substring(1), key);
}
/**
* Returns the translation from chapter property
*
* @param key
* @return
*/
public String getTranslation(String key) {
if (key == null || key.isEmpty() || key.charAt(0) != I18N.PREFIX || i18nChapter == null)
return key;
return i18nChapter.getProperty(key.substring(1), key);
}
public void setTranslation(String key, String value) {
if (key.charAt(0) != I18N.PREFIX) {
if (value == null || value.equals(""))
i18nChapter.remove(key);
else
i18nChapter.setProperty(key, value);
} else {
if (value == null || value.equals(""))
i18nChapter.remove(key.substring(1));
else
i18nChapter.setProperty(key.substring(1), value);
}
}
public void setWorldTranslation(String key, String value) {
if (key.charAt(0) != I18N.PREFIX) {
if (value == null || value.equals(""))
i18nWorld.remove(key);
else
i18nWorld.setProperty(key, value);
} else {
if (value == null || value.equals(""))
i18nWorld.remove(key.substring(1));
else
i18nWorld.setProperty(key.substring(1), value);
}
}
private void save(String filename, Properties p) {
String i18nFilename = getI18NFilename(filename);
deleteUnusedKeys();
try {
FileOutputStream os = new FileOutputStream(i18nFilename);
Writer out = new OutputStreamWriter(os, I18N.ENCODING);
p.store(out, filename);
} catch (IOException e) {
EditorLogger.error("ERROR WRITING BUNDLE: " + i18nFilename);
}
}
public void save() throws FileNotFoundException {
save(worldFilename, i18nWorld);
save(chapterFilename, i18nChapter);
}
public void putTranslationsInElement(Scene scn) {
HashMap<String, Verb> verbs = scn.getVerbManager().getVerbs();
for (Verb v : verbs.values())
putTranslationsInElement(v, false);
for (BaseActor a : scn.getActors().values()) {
putTranslationsInElement(a);
}
}
public void putTranslationsInElement(BaseActor a) {
if (a instanceof InteractiveActor) {
InteractiveActor ia = (InteractiveActor) a;
// 1. DESC attribute
ia.setDesc(getTranslation(ia.getDesc()));
// 2. ACTIONS
HashMap<String, Verb> verbs = ia.getVerbManager().getVerbs();
for (Verb v : verbs.values())
putTranslationsInElement(v, false);
// 3. DIALOGS
if (a instanceof CharacterActor) {
HashMap<String, Dialog> dialogs = ((CharacterActor) a).getDialogs();
if (dialogs != null) {
for (Dialog d : dialogs.values())
putTranslationsInElement(d);
}
}
}
}
public void putTranslationsInElement(Verb v, boolean worldScope) {
ArrayList<Action> actions = v.getActions();
for (Action a : actions) {
putTranslationsInElement(a, worldScope);
}
}
public void putTranslationsInElement(Action a, boolean worldScope) {
String[] names = ActionUtils.getFieldNames(a);
for (String name : names) {
if (name.toLowerCase().endsWith("text")) {
try {
String value = null;
if (worldScope)
value = getWorldTranslation(ActionUtils.getStringValue(a, name));
else
value = getTranslation(ActionUtils.getStringValue(a, name));
ActionUtils.setParam(a, name, value);
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
EditorLogger.error(e.getMessage());
}
}
}
}
public void putTranslationsInElement(Dialog d) {
ArrayList<DialogOption> options = d.getOptions();
for (DialogOption o : options) {
putTranslationsInElement(o);
}
}
public void putTranslationsInElement(DialogOption o) {
o.setText(getTranslation(o.getText()));
o.setResponseText(getTranslation(o.getResponseText()));
}
public void extractStrings(Scene scn) {
HashMap<String, Verb> verbs = scn.getVerbManager().getVerbs();
for (Verb v : verbs.values())
extractStrings(scn.getId(), null, v);
for (BaseActor a : scn.getActors().values()) {
extractStrings(scn.getId(), a);
}
}
public void extractStrings(String sceneid, BaseActor a) {
if (a instanceof InteractiveActor) {
InteractiveActor ia = (InteractiveActor) a;
// 1. DESC attribute
if (ia.getDesc() != null && !ia.getDesc().isEmpty() && ia.getDesc().charAt(0) != I18N.PREFIX) {
String key = genKey(sceneid, a.getId(), "desc");
String value = ia.getDesc();
ia.setDesc(key);
setTranslation(key, value);
}
// 2. ACTIONS
HashMap<String, Verb> verbs = ia.getVerbManager().getVerbs();
for (Verb v : verbs.values())
extractStrings(sceneid, a.getId(), v);
// 3. DIALOGS
if (a instanceof CharacterActor) {
HashMap<String, Dialog> dialogs = ((CharacterActor) a).getDialogs();
if (dialogs != null)
for (Dialog d : dialogs.values())
extractStrings(sceneid, a.getId(), d);
}
// 4. Text for TextRenderer
if (a instanceof SpriteActor && ((SpriteActor) a).getRenderer() instanceof TextRenderer) {
TextRenderer r = (TextRenderer) ((SpriteActor) a).getRenderer();
String v = r.getText();
if (v != null && !v.isEmpty() && v.charAt(0) != I18N.PREFIX) {
String key = genKey(sceneid, a.getId(), "text");
r.setText(key, r.getText());
setTranslation(key, v);
}
}
}
}
public void extractStrings(String sceneid, String actorid, Verb v) {
ArrayList<Action> actions = v.getActions();
for (int i = 0; i < actions.size(); i++) {
Action a = actions.get(i);
extractStrings(sceneid, actorid, v.getHashKey(), i, a);
}
}
public void extractStrings(String sceneid, String actorid, Dialog d) {
ArrayList<DialogOption> options = d.getOptions();
for (int i = 0; i < options.size(); i++) {
DialogOption o = options.get(i);
extractStrings(sceneid, actorid, d.getId(), i, o);
}
}
public void extractStrings(String sceneid, String actorid, String dialogid, int pos, DialogOption o) {
if (o.getText() != null && !o.getText().isEmpty() && o.getText().charAt(0) != I18N.PREFIX) {
String key = genKey(sceneid, actorid, dialogid, pos, "text");
String value = o.getText();
o.setText(key);
setTranslation(key, value);
}
if (o.getResponseText() != null && !o.getResponseText().isEmpty()
&& o.getResponseText().charAt(0) != I18N.PREFIX) {
String key = genKey(sceneid, actorid, dialogid, pos, "responseText");
String value = o.getResponseText();
o.setResponseText(key);
setTranslation(key, value);
}
}
public void extractStrings(String sceneid, String actorid, String parent, int pos, Action a) {
String[] names = ActionUtils.getFieldNames(a);
for (String name : names) {
if (name.toLowerCase().endsWith("text")) {
try {
String value = ActionUtils.getStringValue(a, name);
if (value != null && !value.isEmpty() && value.charAt(0) != I18N.PREFIX) {
String key = genKey(sceneid, actorid, parent, pos, name);
ActionUtils.setParam(a, name, key);
if (sceneid == null)
setWorldTranslation(key, value);
else
setTranslation(key, value);
}
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
EditorLogger.error(e.getMessage());
}
}
}
}
public String genKey(String sceneid, String actorid, String property) {
String key = I18N.PREFIX + sceneid + "." + actorid + "." + property;
return getNotDuplicateKey(key);
}
public String genKey(String sceneid, String actorid, String parent, int pos, String property) {
String key = I18N.PREFIX + (sceneid == null ? WORLD_VERBS_PREFIX : sceneid)
+ (actorid == null ? "" : "." + actorid) + "." + parent + "." + pos + "." + property;
if (sceneid == null)
return getNotDuplicateKeyWorld(key);
return getNotDuplicateKey(key);
}
public String getNotDuplicateKey(String key) {
while (i18nChapter.containsKey(key.charAt(0) == I18N.PREFIX ? key.substring(1) : key))
key += '_';
return key;
}
public String getNotDuplicateKeyWorld(String key) {
while (i18nWorld.containsKey(key.charAt(0) == I18N.PREFIX ? key.substring(1) : key))
key += '_';
return key;
}
private void deleteUnusedKeys() {
ArrayList<String> usedKeys = new ArrayList<String>();
// SCENES
for (Scene s : World.getInstance().getScenes().values())
getUsedKeys(s, usedKeys);
Enumeration<Object> keys = i18nChapter.keys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
// Doesn't remove ui and ink keys
if (!usedKeys.contains(key) && !key.startsWith("ui.") && !key.startsWith("ink.")) {
EditorLogger.debug("Removing translation key: " + key);
i18nChapter.remove(key);
}
}
usedKeys.clear();
// WORLD VERBS
for (Verb v : World.getInstance().getVerbManager().getVerbs().values())
getUsedKeys(v, usedKeys);
keys = i18nWorld.keys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
// Doesn't remove ui keys
if (!usedKeys.contains(key) && !key.startsWith("ui.")) {
EditorLogger.debug("Removing translation key: " + key);
i18nWorld.remove(key);
}
}
}
private void getUsedKeys(Scene s, ArrayList<String> usedKeys) {
for (Verb v : s.getVerbManager().getVerbs().values())
getUsedKeys(v, usedKeys);
for (BaseActor a : s.getActors().values()) {
if (a instanceof InteractiveActor) {
InteractiveActor ia = (InteractiveActor) a;
if (ia.getDesc() != null && !ia.getDesc().isEmpty() && ia.getDesc().charAt(0) == I18N.PREFIX)
usedKeys.add(ia.getDesc().substring(1));
for (Verb v : ia.getVerbManager().getVerbs().values())
getUsedKeys(v, usedKeys);
if (a instanceof CharacterActor) {
CharacterActor ca = (CharacterActor) a;
if (ca.getDialogs() != null) {
for (Dialog d : ca.getDialogs().values()) {
for (DialogOption o : d.getOptions()) {
if (o.getText() != null && !o.getText().isEmpty()
&& o.getText().charAt(0) == I18N.PREFIX)
usedKeys.add(o.getText().substring(1));
if (o.getResponseText() != null && !o.getResponseText().isEmpty()
&& o.getResponseText().charAt(0) == I18N.PREFIX)
usedKeys.add(o.getResponseText().substring(1));
}
}
}
}
if (a instanceof SpriteActor && ((SpriteActor) a).getRenderer() instanceof TextRenderer) {
TextRenderer r = (TextRenderer) ((SpriteActor) a).getRenderer();
String k = r.getText();
if (k != null && !k.isEmpty() && k.charAt(0) == I18N.PREFIX)
usedKeys.add(k.substring(1));
}
}
}
}
private void getUsedKeys(Verb v, ArrayList<String> usedKeys) {
for (Action a : v.getActions()) {
if (a instanceof DisableActionAction)
a = ((DisableActionAction) a).getAction();
String[] fieldNames = ActionUtils.getFieldNames(a);
for (String name : fieldNames) {
if (name.toLowerCase().endsWith("text")) {
String value = null;
try {
value = ActionUtils.getStringValue(a, name);
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
EditorLogger.error(e.getMessage());
}
if (value != null && !value.isEmpty() && value.charAt(0) == I18N.PREFIX) {
usedKeys.add(value.substring(1));
}
}
}
}
}
}