package be.nikiroo.jvcard.i18n;
-import java.util.HashMap;
-import java.util.Map;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.lang.reflect.Field;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+import be.nikiroo.jvcard.resources.Bundles;
+import be.nikiroo.jvcard.tui.UiColors;
+
+import com.googlecode.lanterna.input.KeyStroke;
/**
- * This class manages the translation of {@link Trans#StringId}s into
+ * This class manages the translation of {@link Trans.StringId}s into
* user-understandable text.
*
* @author niki
*
*/
public class Trans {
- static private Object lock = new Object();
- static private Trans instance = null;
+ ResourceBundle map;
- private Map<StringId, String> map = null;
+ /**
+ * Create a translation service with the default language.
+ */
+ public Trans() {
+ setLanguage(null);
+ }
/**
- * An enum representing information to be translated to the user.
+ * Create a translation service for the given language. (Will fall back to
+ * the default one i not found.)
*
- * @author niki
+ * @param language
+ * the language to use
+ */
+ public Trans(String language) {
+ setLanguage(language);
+ }
+
+ /**
+ * Translate the given {@link StringId} into user text.
*
+ * @param stringId
+ * the ID to translate
+ *
+ * @return the translated text
*/
- public enum StringId {
- DUMMY, // <-- TODO : remove
- KEY_ACTION_BACK, KEY_ACTION_HELP, // MainWindow
- KEY_ACTION_VIEW_CARD, // FileList
- KEY_ACTION_VIEW_CONTACT, KEY_ACTION_EDIT_CONTACT, KEY_ACTION_SAVE_CARD, KEY_ACTION_DELETE_CONTACT, KEY_ACTION_SWITCH_FORMAT, // ContactList
- NULL; // Special usage
+ public String trans(StringId stringId) {
+ StringId id = stringId;
+ if (!UiColors.getInstance().isUnicode()) {
+ try {
+ id = StringId.valueOf(stringId.name() + "_NOUTF");
+ } catch (IllegalArgumentException iae) {
+ // no special _NOUTF version found
+ }
+ }
- public String trans() {
- return Trans.getInstance().trans(this);
+ if (id == StringId.NULL) {
+ return "";
}
- };
+
+ if (id == StringId.DUMMY) {
+ return "[dummy]";
+ }
+
+ if (map.containsKey(id.name())) {
+ return map.getString(id.name());
+ }
+
+ return id.toString();
+ }
/**
- * Get the (unique) instance of this class.
+ * Translate the given {@link KeyStroke} into a user text {@link String} of
+ * size 3.
*
- * @return the (unique) instance
+ * @param key
+ * the key to translate
+ *
+ * @return the translated text
*/
- static public Trans getInstance() {
- synchronized (lock) {
- if (instance == null)
- instance = new Trans();
+ public String trans(KeyStroke key) {
+ String keyTrans = "";
+
+ switch (key.getKeyType()) {
+ case Enter:
+ if (UiColors.getInstance().isUnicode())
+ keyTrans = " ⤶ ";
+ else
+ keyTrans = trans(StringId.KEY_ENTER);
+ break;
+ case Tab:
+ if (UiColors.getInstance().isUnicode())
+ keyTrans = " ↹ ";
+ else
+ keyTrans = trans(StringId.KEY_TAB);
+
+ break;
+ case Character:
+ keyTrans = " " + key.getCharacter() + " ";
+ break;
+ default:
+ keyTrans = "" + key.getKeyType();
+ int width = 3;
+ if (keyTrans.length() > width) {
+ keyTrans = keyTrans.substring(0, width);
+ } else if (keyTrans.length() < width) {
+ keyTrans = keyTrans
+ + new String(new char[width - keyTrans.length()])
+ .replace('\0', ' ');
+ }
+ break;
}
- return instance;
+ return keyTrans;
}
- public String trans(StringId stringId) {
- if (map.containsKey(stringId)) {
- return map.get(stringId);
+ /**
+ * Initialise the translation mappings for the given language.
+ *
+ * @param language
+ * the language to initialise, in the form "en-GB" or "fr" for
+ * instance
+ */
+ private void setLanguage(String language) {
+ map = Bundles.getBundle("resources", getLocaleFor(language));
+ }
+
+ /**
+ * Create/update the translation .properties files. Will use the most likely
+ * candidate as base if the file does not already exists (for instance,
+ * "en_US" will use "en" as a base).
+ *
+ * @param args
+ * the path where the .properties files are, then the languages
+ * to create/update
+ *
+ * @throws IOException
+ * in case of IO errors
+ */
+ static public void main(String[] args) throws IOException {
+ String path = args[0];
+ for (int i = 1; i < args.length; i++) {
+ Locale locale = getLocaleFor(args[i]);
+ String code = locale.toString();
+ Trans trans = new Trans(code);
+
+ File file = null;
+ if (code.length() > 0) {
+ file = new File(path + "resources_" + code + ".properties");
+ } else {
+ // Default properties file:
+ file = new File(path + "resources.properties");
+ }
+
+ BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
+ new FileOutputStream(file), "UTF-8"));
+
+ String name = locale.getDisplayCountry(locale);
+ if (name.length() == 0)
+ name = locale.getDisplayLanguage(locale);
+ if (name.length() == 0)
+ name = "default";
+
+ if (code.length() > 0) {
+ name = name + " (" + code + ")";
+ }
+
+ writer.append("# " + name + " translation file (UTF-8)\n");
+ writer.append("# \n");
+ writer.append("# Note that any key can be doubled with a _NOUTF suffix\n");
+ writer.append("# to use when the flag --noutf is passed\n");
+ writer.append("# \n");
+ writer.append("# Also, the comments always refer to the key below them.\n");
+ writer.append("# \n");
+ writer.append("\n");
+
+ for (Field field : StringId.class.getDeclaredFields()) {
+ Meta meta = field.getAnnotation(Meta.class);
+ if (meta != null) {
+ StringId id = StringId.valueOf(field.getName());
+ String info = getMetaInfo(meta);
+ if (info != null) {
+ writer.append(info);
+ writer.append("\n");
+ }
+
+ writer.append(id.name());
+ writer.append(" = ");
+ if (!trans.trans(id).equals(id.name()))
+ writer.append(trans.trans(id));
+ writer.append("\n");
+ }
+ }
+
+ writer.close();
}
+ }
- return stringId.toString();
+ /**
+ * Return the {@link Locale} representing the given language.
+ *
+ * @param language
+ * the language to initialise, in the form "en-GB" or "fr" for
+ * instance
+ *
+ * @return the corresponding {@link Locale} or the default {@link Locale} if
+ * it is not known
+ */
+ static private Locale getLocaleFor(String language) {
+ Locale locale;
+
+ if (language == null) {
+ locale = Locale.getDefault();
+ } else {
+ language = language.replaceAll("_", "-");
+ String lang = language;
+ String country = null;
+ if (language.contains("-")) {
+ lang = language.split("-")[0];
+ country = language.split("-")[1];
+ }
+
+ if (country != null)
+ locale = new Locale(lang, country);
+ else
+ locale = new Locale(lang);
+ }
+
+ return locale;
}
- private Trans() {
- map = new HashMap<StringId, String>();
-
- // TODO: get from a file instead?
- map.put(StringId.NULL, "");
- map.put(StringId.DUMMY, "[dummy]");
- map.put(StringId.KEY_ACTION_BACK, "Back");
- map.put(StringId.KEY_ACTION_HELP, "Help");
- map.put(StringId.KEY_ACTION_VIEW_CONTACT, "Open");
- map.put(StringId.KEY_ACTION_VIEW_CARD, "Open");
- map.put(StringId.KEY_ACTION_EDIT_CONTACT, "Edit");
- map.put(StringId.KEY_ACTION_DELETE_CONTACT, "Delete");
- map.put(StringId.KEY_ACTION_SWITCH_FORMAT, "Change view");
+ /**
+ * Return formated, display-able information from the {@link Meta} field
+ * given. Each line will always starts with a "#" character.
+ *
+ * @param meta
+ * the {@link Meta} field
+ *
+ * @return the information to display or NULL if none
+ */
+ static private String getMetaInfo(Meta meta) {
+ String what = meta.what();
+ String where = meta.where();
+ String format = meta.format();
+ String info = meta.info();
+
+ int opt = what.length() + where.length() + format.length();
+ if (opt + info.length() == 0)
+ return null;
+
+ StringBuilder builder = new StringBuilder();
+ builder.append("# ");
+
+ if (opt > 0) {
+ builder.append("(");
+ if (what.length() > 0) {
+ builder.append("WHAT: " + what);
+ if (where.length() + format.length() > 0)
+ builder.append(", ");
+ }
+
+ if (where.length() > 0) {
+ builder.append("WHERE: " + where);
+ if (format.length() > 0)
+ builder.append(", ");
+ }
+
+ if (format.length() > 0) {
+ builder.append("FORMAT: " + format);
+ }
+
+ builder.append(")\n# ");
+ }
+
+ builder.append(info);
+
+ return builder.toString();
}
+
+ /**
+ * The enum representing textual information to be translated to the user as
+ * a key.
+ *
+ * Note that each key that should be translated MUST be annotated with a
+ * {@link Meta} annotation.
+ *
+ * @author niki
+ *
+ */
+ public enum StringId {
+ DUMMY, // <-- TODO : remove
+ NULL, // Special usage, no annotations so it is not visible in
+ // .properties files
+ @Meta(what = "a key to press", where = "action keys", format = "MUST BE 3 chars long", info = "Tab key")
+ KEY_TAB, // keys
+ @Meta(what = "a key to press", where = "action keys", format = "MUST BE 3 chars long", info = "Enter key")
+ KEY_ENTER, //
+ @Meta(what = "", where = "", format = "", info = "")
+ KEY_ACTION_BACK, // MainWindow
+ @Meta(what = "", where = "", format = "", info = "")
+ KEY_ACTION_HELP, //
+ @Meta(what = "", where = "", format = "", info = "")
+ KEY_ACTION_VIEW_CARD, // FileList
+ @Meta(what = "", where = "", format = "", info = "")
+ KEY_ACTION_VIEW_CONTACT, // ContactList
+ @Meta(what = "", where = "", format = "", info = "")
+ KEY_ACTION_EDIT_CONTACT, //
+ @Meta(what = "", where = "", format = "", info = "")
+ KEY_ACTION_SAVE_CARD, //
+ @Meta(what = "", where = "", format = "", info = "")
+ KEY_ACTION_DELETE_CONTACT, //
+ @Meta(what = "", where = "", format = "", info = "")
+ KEY_ACTION_SEARCH, //
+ @Meta(what = "", where = "", format = "", info = "we could use: ' ', ┃, │...")
+ DEAULT_FIELD_SEPARATOR, // MainContentList
+ @Meta(what = "", where = "", format = "", info = "")
+ DEAULT_FIELD_SEPARATOR_NOUTF, //
+ @Meta(what = "", where = "", format = "", info = "")
+ KEY_ACTION_INVERT, // ContactDetails
+ @Meta(what = "", where = "", format = "", info = "")
+ KEY_ACTION_FULLSCREEN, //
+ @Meta(what = "", where = "", format = "", info = "")
+ KEY_ACTION_SWITCH_FORMAT, // multi-usage
+ };
}