Fix FN when empty (with a configurable option) + some i18n
[jvcard.git] / src / be / nikiroo / jvcard / resources / Trans.java
CommitLineData
7da41ecd 1package be.nikiroo.jvcard.resources;
a3b510ab 2
e7418457
NR
3import java.io.BufferedWriter;
4import java.io.File;
5import java.io.FileOutputStream;
6import java.io.IOException;
7import java.io.OutputStreamWriter;
8import java.lang.reflect.Field;
668268fc
NR
9import java.util.Locale;
10import java.util.ResourceBundle;
296a0b75 11
a3b510ab 12/**
e7418457 13 * This class manages the translation of {@link Trans.StringId}s into
a3b510ab
NR
14 * user-understandable text.
15 *
16 * @author niki
17 *
18 */
19public class Trans {
7f82bf68 20 private ResourceBundle map;
7da41ecd 21 private boolean utf = true;
9b8cb729 22 private Locale locale;
a3b510ab 23
a3b510ab 24 /**
668268fc 25 * Create a translation service with the default language.
a3b510ab 26 */
668268fc 27 public Trans() {
e7418457 28 setLanguage(null);
668268fc 29 }
a3b510ab 30
668268fc
NR
31 /**
32 * Create a translation service for the given language. (Will fall back to
33 * the default one i not found.)
34 *
35 * @param language
36 * the language to use
37 */
38 public Trans(String language) {
e7418457 39 setLanguage(language);
a3b510ab
NR
40 }
41
296a0b75
NR
42 /**
43 * Translate the given {@link StringId} into user text.
44 *
45 * @param stringId
46 * the ID to translate
9b8cb729
NR
47 * @param values
48 * the values to insert instead of the place holders in the
49 * translation
296a0b75 50 *
9b8cb729 51 * @return the translated text with the given value where required
296a0b75 52 */
9b8cb729 53 public String trans(StringId stringId, String... values) {
296a0b75 54 StringId id = stringId;
9b8cb729
NR
55 String result = null;
56
7da41ecd 57 if (!isUnicode()) {
296a0b75 58 try {
2a96e7b2 59 id = StringId.valueOf(stringId.name() + "_NOUTF");
296a0b75
NR
60 } catch (IllegalArgumentException iae) {
61 // no special _NOUTF version found
62 }
63 }
64
668268fc 65 if (id == StringId.NULL) {
9b8cb729
NR
66 result = "";
67 } else if (id == StringId.DUMMY) {
68 result = "[dummy]";
69 } else if (map.containsKey(id.name())) {
70 result = map.getString(id.name());
71 } else {
72 result = id.toString();
296a0b75
NR
73 }
74
9b8cb729
NR
75 if (values != null && values.length > 0)
76 return String.format(locale, result, (Object[]) values);
77 else
78 return result;
296a0b75
NR
79 }
80
81 /**
7da41ecd 82 * Check if unicode characters should be used.
296a0b75 83 *
7da41ecd 84 * @return TRUE to allow unicode
296a0b75 85 */
7da41ecd
NR
86 public boolean isUnicode() {
87 return utf;
88 }
a3b510ab 89
7da41ecd
NR
90 /**
91 * Allow or disallow unicode characters in the program.
92 *
93 * @param utf
94 * TRUE to allow unuciode, FALSE to only allow ASCII characters
95 */
96 public void setUnicode(boolean utf) {
97 this.utf = utf;
a3b510ab
NR
98 }
99
668268fc
NR
100 /**
101 * Initialise the translation mappings for the given language.
102 *
d3e940b6
NR
103 * @param language
104 * the language to initialise, in the form "en-GB" or "fr" for
105 * instance
668268fc 106 */
d3e940b6 107 private void setLanguage(String language) {
9b8cb729
NR
108 locale = getLocaleFor(language);
109 map = Bundles.getBundle("resources", locale);
a3b510ab 110 }
e7418457
NR
111
112 /**
113 * Create/update the translation .properties files. Will use the most likely
114 * candidate as base if the file does not already exists (for instance,
115 * "en_US" will use "en" as a base).
116 *
7da41ecd
NR
117 * @param path
118 * the path where the .properties files are
119 *
120 * @param language
121 * the language code to create/update (e.g.: <tt>fr-BE</tt>)
e7418457
NR
122 *
123 * @throws IOException
124 * in case of IO errors
125 */
7da41ecd
NR
126 static public void generateTranslationFile(String path, String language)
127 throws IOException {
128
129 Locale locale = getLocaleFor(language);
130 String code = locale.toString();
131 Trans trans = new Trans(code);
e7418457 132
7da41ecd
NR
133 File file = null;
134 if (code.length() > 0) {
135 file = new File(path + "resources_" + code + ".properties");
136 } else {
137 // Default properties file:
138 file = new File(path + "resources.properties");
139 }
e7418457 140
7da41ecd
NR
141 BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
142 new FileOutputStream(file), "UTF-8"));
e7418457 143
7da41ecd
NR
144 String name = locale.getDisplayCountry(locale);
145 if (name.length() == 0)
146 name = locale.getDisplayLanguage(locale);
147 if (name.length() == 0)
148 name = "default";
e7418457 149
7da41ecd
NR
150 if (code.length() > 0) {
151 name = name + " (" + code + ")";
152 }
153
154 writer.append("# " + name + " translation file (UTF-8)\n");
155 writer.append("# \n");
156 writer.append("# Note that any key can be doubled with a _NOUTF suffix\n");
157 writer.append("# to use when the flag --noutf is passed\n");
158 writer.append("# \n");
159 writer.append("# Also, the comments always refer to the key below them.\n");
160 writer.append("# \n");
161 writer.append("\n");
162
163 for (Field field : StringId.class.getDeclaredFields()) {
164 Meta meta = field.getAnnotation(Meta.class);
165 if (meta != null) {
166 StringId id = StringId.valueOf(field.getName());
167 String info = getMetaInfo(meta);
168 if (info != null) {
169 writer.append(info);
e7418457
NR
170 writer.append("\n");
171 }
e7418457 172
7da41ecd
NR
173 writer.append(id.name());
174 writer.append(" = ");
175 if (!trans.trans(id).equals(id.name()))
176 writer.append(trans.trans(id));
177 writer.append("\n");
178 }
e7418457 179 }
7da41ecd
NR
180
181 writer.close();
e7418457
NR
182 }
183
d3e940b6
NR
184 /**
185 * Return the {@link Locale} representing the given language.
186 *
187 * @param language
188 * the language to initialise, in the form "en-GB" or "fr" for
189 * instance
190 *
191 * @return the corresponding {@link Locale} or the default {@link Locale} if
192 * it is not known
193 */
194 static private Locale getLocaleFor(String language) {
195 Locale locale;
196
197 if (language == null) {
198 locale = Locale.getDefault();
199 } else {
200 language = language.replaceAll("_", "-");
201 String lang = language;
202 String country = null;
203 if (language.contains("-")) {
204 lang = language.split("-")[0];
205 country = language.split("-")[1];
206 }
207
208 if (country != null)
209 locale = new Locale(lang, country);
210 else
211 locale = new Locale(lang);
212 }
213
214 return locale;
215 }
216
e7418457
NR
217 /**
218 * Return formated, display-able information from the {@link Meta} field
219 * given. Each line will always starts with a "#" character.
220 *
221 * @param meta
222 * the {@link Meta} field
223 *
224 * @return the information to display or NULL if none
225 */
d3e940b6 226 static private String getMetaInfo(Meta meta) {
e7418457
NR
227 String what = meta.what();
228 String where = meta.where();
229 String format = meta.format();
230 String info = meta.info();
231
232 int opt = what.length() + where.length() + format.length();
233 if (opt + info.length() == 0)
234 return null;
235
236 StringBuilder builder = new StringBuilder();
237 builder.append("# ");
238
239 if (opt > 0) {
240 builder.append("(");
241 if (what.length() > 0) {
242 builder.append("WHAT: " + what);
243 if (where.length() + format.length() > 0)
244 builder.append(", ");
245 }
246
247 if (where.length() > 0) {
248 builder.append("WHERE: " + where);
249 if (format.length() > 0)
250 builder.append(", ");
251 }
252
253 if (format.length() > 0) {
254 builder.append("FORMAT: " + format);
255 }
256
9b8cb729
NR
257 builder.append(")");
258 if (info.length() > 0) {
259 builder.append("\n# ");
260 }
e7418457
NR
261 }
262
263 builder.append(info);
264
265 return builder.toString();
266 }
267
268 /**
269 * The enum representing textual information to be translated to the user as
270 * a key.
271 *
272 * Note that each key that should be translated MUST be annotated with a
273 * {@link Meta} annotation.
274 *
275 * @author niki
276 *
277 */
278 public enum StringId {
279 DUMMY, // <-- TODO : remove
280 NULL, // Special usage, no annotations so it is not visible in
281 // .properties files
282 @Meta(what = "a key to press", where = "action keys", format = "MUST BE 3 chars long", info = "Tab key")
283 KEY_TAB, // keys
284 @Meta(what = "a key to press", where = "action keys", format = "MUST BE 3 chars long", info = "Enter key")
285 KEY_ENTER, //
30a4aa17
NR
286 @Meta(what = "Action key", where = "All screens except the first (KEY_ACTION_QUIT)", format = "", info = "Go back to previous screen")
287 KEY_ACTION_BACK, //
288 @Meta(what = "Action key", where = "MainWindow", format = "", info = "Get help text")
e7418457 289 KEY_ACTION_HELP, //
30a4aa17
NR
290 @Meta(what = "Action key", where = "FileList", format = "", info = "View the selected card")
291 KEY_ACTION_VIEW_CARD, //
292 @Meta(what = "Action key", where = "ContactList", format = "", info = "View the selected contact")
293 KEY_ACTION_VIEW_CONTACT, //
294 @Meta(what = "Action key", where = "ContactDetails", format = "", info = "Edit the contact")
e7418457 295 KEY_ACTION_EDIT_CONTACT, //
30a4aa17 296 @Meta(what = "Action key", where = "ContactDetails", format = "", info = "Edit the contact in RAW mode")
3634193b 297 KEY_ACTION_EDIT_CONTACT_RAW, //
30a4aa17 298 @Meta(what = "Action key", where = "ContactList", format = "", info = "Save the whole card")
e7418457 299 KEY_ACTION_SAVE_CARD, //
30a4aa17 300 @Meta(what = "", where = "ContactList", format = "", info = "Delete the selected contact")
e7418457 301 KEY_ACTION_DELETE_CONTACT, //
30a4aa17 302 @Meta(what = "Action key", where = "ContactList", format = "", info = "Filter the displayed contacts")
e7418457 303 KEY_ACTION_SEARCH, //
30a4aa17 304 @Meta(what = "", where = "", format = "we could use: ' ', ┃, │...", info = "Field separator")
e7418457 305 DEAULT_FIELD_SEPARATOR, // MainContentList
30a4aa17
NR
306 @Meta(what = "Action key", where = "ContactDetails", format = "", info = "Invert the photo's colours")
307 KEY_ACTION_INVERT, //
308 @Meta(what = "Action key", where = "ContactDetails", format = "", info = "Show the photo in 'fullscreen'")
e7418457 309 KEY_ACTION_FULLSCREEN, //
30a4aa17 310 @Meta(what = "Action key", where = "ContactList, ContactDetails, ContactDetailsRaw", format = "", info = "Switch between the available display formats")
e7418457 311 KEY_ACTION_SWITCH_FORMAT, // multi-usage
9b8cb729
NR
312 @Meta(what = "Action key", where = "Contact list, Edit Contact", format = "", info = "Add a new contact/field")
313 KEY_ACTION_ADD, //
314 @Meta(what = "User question: TEXT", where = "Contact list", format = "", info = "New contact")
315 ASK_USER_CONTACT_NAME, //
316 @Meta(what = "User question: [Y|N]", where = "Contact list", format = "%s = contact name", info = "Delete contact")
317 CONFIRM_USER_DELETE_CONTACT, //
318 @Meta(what = "Error", where = "Contact list", format = "%s = contact name", info = "cannot delete a contact")
319 ERR_CANNOT_DELETE_CONTACT, //
e7418457 320 };
a3b510ab 321}