From e741845793d462cdb06f27423fa761bb5b8ebd6b Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Fri, 4 Mar 2016 10:50:40 +0100 Subject: [PATCH] Translation system improvement (can now generate .properties files programmatically with @Meta information included) --- src/be/nikiroo/jvcard/i18n/Meta.java | 47 +++++ src/be/nikiroo/jvcard/i18n/Trans.java | 197 ++++++++++++++++-- .../jvcard/resources/resources.properties | 24 ++- .../jvcard/resources/resources_en.properties | 24 ++- .../jvcard/resources/resources_fr.properties | 29 ++- 5 files changed, 272 insertions(+), 49 deletions(-) create mode 100644 src/be/nikiroo/jvcard/i18n/Meta.java diff --git a/src/be/nikiroo/jvcard/i18n/Meta.java b/src/be/nikiroo/jvcard/i18n/Meta.java new file mode 100644 index 0000000..6ebe7be --- /dev/null +++ b/src/be/nikiroo/jvcard/i18n/Meta.java @@ -0,0 +1,47 @@ +package be.nikiroo.jvcard.i18n; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation used to give some information about the translation keys, so the + * translation .properties file can be created programmatically. + * + * @author niki + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Meta { + /** + * What kind of item this key represent (a Key, a Label text, a format to + * use for something else...). + * + * @return what it is + */ + String what(); + + /** + * Where in the application will this key appear (in the action keys, in a + * menu, in a message...). + * + * @return where it is + */ + String where(); + + /** + * What format should/must this key be in. + * + * @return the format it is in + */ + String format(); + + /** + * Free info text to help translate. + * + * @return some info + */ + String info(); +} diff --git a/src/be/nikiroo/jvcard/i18n/Trans.java b/src/be/nikiroo/jvcard/i18n/Trans.java index 210cffc..b41fbde 100644 --- a/src/be/nikiroo/jvcard/i18n/Trans.java +++ b/src/be/nikiroo/jvcard/i18n/Trans.java @@ -1,5 +1,11 @@ package be.nikiroo.jvcard.i18n; +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; @@ -9,7 +15,7 @@ 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 @@ -18,29 +24,11 @@ import com.googlecode.lanterna.input.KeyStroke; public class Trans { ResourceBundle map; - /** - * An enum representing information to be translated to the user. - * - * @author niki - * - */ - public enum StringId { - DUMMY, // <-- TODO : remove - KEY_TAB, KEY_ENTER, // keys - 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_SEARCH, // ContactList - DEAULT_FIELD_SEPARATOR, DEAULT_FIELD_SEPARATOR_NOUTF, // MainContentList - KEY_ACTION_INVERT, KEY_ACTION_FULLSCREEN, // ContactDetails - KEY_ACTION_SWITCH_FORMAT, // multi-usage - NULL; // Special usage - }; - /** * Create a translation service with the default language. */ public Trans() { - init(null); + setLanguage(null); } /** @@ -51,7 +39,7 @@ public class Trans { * the language to use */ public Trans(String language) { - init(language); + setLanguage(language); } /** @@ -138,7 +126,7 @@ public class Trans { * @param lang * the language to initialise */ - private void init(String lang) { + private void setLanguage(String lang) { Locale locale = null; if (lang == null) { @@ -149,4 +137,169 @@ public class Trans { map = Bundles.getBundle("resources", locale); } + + /** + * 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 + */ + public static void main(String[] args) throws IOException { + String path = args[0]; + for (int i = 1; i < args.length; i++) { + Locale locale = Locale.forLanguageTag(args[i].replaceAll("_", "-")); + 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 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 + */ + private static 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 + }; } diff --git a/src/be/nikiroo/jvcard/resources/resources.properties b/src/be/nikiroo/jvcard/resources/resources.properties index 14428ea..c35e079 100644 --- a/src/be/nikiroo/jvcard/resources/resources.properties +++ b/src/be/nikiroo/jvcard/resources/resources.properties @@ -1,20 +1,28 @@ -# English translation file (UTF-8) +# default translation file (UTF-8) +# # Note that any key can be doubled with a _NOUTF suffix # to use when the flag --noutf is passed +# +# Also, the comments always refer to the key below them. +# -# those 2 keys MUST be 3-characters long +# (WHAT: a key to press, WHERE: action keys, FORMAT: MUST BE 3 chars long) +# Tab key KEY_TAB = TAB +# (WHAT: a key to press, WHERE: action keys, FORMAT: MUST BE 3 chars long) +# Enter key KEY_ENTER = ENT -# we could use: " ", ┃, │... -DEAULT_FIELD_SEPARATOR = ┃ -DEAULT_FIELD_SEPARATOR_NOUTF = | KEY_ACTION_BACK = Back KEY_ACTION_HELP = Help -KEY_ACTION_VIEW_CONTACT = Open KEY_ACTION_VIEW_CARD = Open +KEY_ACTION_VIEW_CONTACT = Open KEY_ACTION_EDIT_CONTACT = Edit +KEY_ACTION_SAVE_CARD = Save KEY_ACTION_DELETE_CONTACT = Delete -KEY_ACTION_SWITCH_FORMAT = Change view +KEY_ACTION_SEARCH = Search +# we could use: ' ', ┃, │... +DEAULT_FIELD_SEPARATOR = ┃ +DEAULT_FIELD_SEPARATOR_NOUTF = | KEY_ACTION_INVERT = Invert colours KEY_ACTION_FULLSCREEN = Fullscreen -KEY_ACTION_SEARCH = Search +KEY_ACTION_SWITCH_FORMAT = Change view diff --git a/src/be/nikiroo/jvcard/resources/resources_en.properties b/src/be/nikiroo/jvcard/resources/resources_en.properties index afd62a8..eb08a13 100644 --- a/src/be/nikiroo/jvcard/resources/resources_en.properties +++ b/src/be/nikiroo/jvcard/resources/resources_en.properties @@ -1,20 +1,28 @@ -# default translation file (UTF-8) +# English (en) translation file (UTF-8) +# # Note that any key can be doubled with a _NOUTF suffix # to use when the flag --noutf is passed +# +# Also, the comments always refer to the key below them. +# -# those 2 keys MUST be 3-characters long +# (WHAT: a key to press, WHERE: action keys, FORMAT: MUST BE 3 chars long) +# Tab key KEY_TAB = TAB +# (WHAT: a key to press, WHERE: action keys, FORMAT: MUST BE 3 chars long) +# Enter key KEY_ENTER = ENT -# we could use: " ", ┃, │... -DEAULT_FIELD_SEPARATOR = ┃ -DEAULT_FIELD_SEPARATOR_NOUTF = | KEY_ACTION_BACK = Back KEY_ACTION_HELP = Help -KEY_ACTION_VIEW_CONTACT = Open KEY_ACTION_VIEW_CARD = Open +KEY_ACTION_VIEW_CONTACT = Open KEY_ACTION_EDIT_CONTACT = Edit +KEY_ACTION_SAVE_CARD = Save KEY_ACTION_DELETE_CONTACT = Delete -KEY_ACTION_SWITCH_FORMAT = Change view +KEY_ACTION_SEARCH = Search +# we could use: ' ', ┃, │... +DEAULT_FIELD_SEPARATOR = ┃ +DEAULT_FIELD_SEPARATOR_NOUTF = | KEY_ACTION_INVERT = Invert colours KEY_ACTION_FULLSCREEN = Fullscreen -KEY_ACTION_SEARCH = Search +KEY_ACTION_SWITCH_FORMAT = Change view diff --git a/src/be/nikiroo/jvcard/resources/resources_fr.properties b/src/be/nikiroo/jvcard/resources/resources_fr.properties index 609beaf..9f3841c 100644 --- a/src/be/nikiroo/jvcard/resources/resources_fr.properties +++ b/src/be/nikiroo/jvcard/resources/resources_fr.properties @@ -1,21 +1,28 @@ -# French translation file (UTF-8) -# Note : les clefs peuvent être suffixées de _NOUTF -# pour forcer une autre version quand le flag --noutf -# est passé au programme +# français (fr) translation file (UTF-8) +# +# Note that any key can be doubled with a _NOUTF suffix +# to use when the flag --noutf is passed +# +# Also, the comments always refer to the key below them. +# -# les deux touches qui suivent DOIVENT faire 3 caractères de long +# (WHAT: a key to press, WHERE: action keys, FORMAT: MUST BE 3 chars long) +# Tab key KEY_TAB = TAB +# (WHAT: a key to press, WHERE: action keys, FORMAT: MUST BE 3 chars long) +# Enter key KEY_ENTER = ENT -# autres options: " ", ┃, │... -DEAULT_FIELD_SEPARATOR = ┃ -DEAULT_FIELD_SEPARATOR_NOUTF = | KEY_ACTION_BACK = Retour KEY_ACTION_HELP = Aide -KEY_ACTION_VIEW_CONTACT = Afficher KEY_ACTION_VIEW_CARD = Ouvrir +KEY_ACTION_VIEW_CONTACT = Afficher KEY_ACTION_EDIT_CONTACT = Éditer +KEY_ACTION_SAVE_CARD = Sauver KEY_ACTION_DELETE_CONTACT = Supprimer -KEY_ACTION_SWITCH_FORMAT = Autre affichage +KEY_ACTION_SEARCH = Rechercher +# we could use: ' ', ┃, │... +DEAULT_FIELD_SEPARATOR = ┃ +DEAULT_FIELD_SEPARATOR_NOUTF = | KEY_ACTION_INVERT = Couleurs inversées KEY_ACTION_FULLSCREEN = Plein écran -KEY_ACTION_SEARCH = Rechercher +KEY_ACTION_SWITCH_FORMAT = Autre affichage -- 2.27.0