Resources: now allow "--config" and external .properties files
[jvcard.git] / src / be / nikiroo / jvcard / i18n / Trans.java
CommitLineData
a3b510ab
NR
1package be.nikiroo.jvcard.i18n;
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
2a96e7b2 12import be.nikiroo.jvcard.resources.Bundles;
296a0b75
NR
13import be.nikiroo.jvcard.tui.UiColors;
14
668268fc
NR
15import com.googlecode.lanterna.input.KeyStroke;
16
a3b510ab 17/**
e7418457 18 * This class manages the translation of {@link Trans.StringId}s into
a3b510ab
NR
19 * user-understandable text.
20 *
21 * @author niki
22 *
23 */
24public class Trans {
7f82bf68 25 private ResourceBundle map;
a3b510ab 26
a3b510ab 27 /**
668268fc 28 * Create a translation service with the default language.
a3b510ab 29 */
668268fc 30 public Trans() {
e7418457 31 setLanguage(null);
668268fc 32 }
a3b510ab 33
668268fc
NR
34 /**
35 * Create a translation service for the given language. (Will fall back to
36 * the default one i not found.)
37 *
38 * @param language
39 * the language to use
40 */
41 public Trans(String language) {
e7418457 42 setLanguage(language);
a3b510ab
NR
43 }
44
296a0b75
NR
45 /**
46 * Translate the given {@link StringId} into user text.
47 *
48 * @param stringId
49 * the ID to translate
50 *
51 * @return the translated text
52 */
a3b510ab 53 public String trans(StringId stringId) {
296a0b75
NR
54 StringId id = stringId;
55 if (!UiColors.getInstance().isUnicode()) {
56 try {
2a96e7b2 57 id = StringId.valueOf(stringId.name() + "_NOUTF");
296a0b75
NR
58 } catch (IllegalArgumentException iae) {
59 // no special _NOUTF version found
60 }
61 }
62
668268fc
NR
63 if (id == StringId.NULL) {
64 return "";
65 }
66
67 if (id == StringId.DUMMY) {
68 return "[dummy]";
69 }
70
2a96e7b2
NR
71 if (map.containsKey(id.name())) {
72 return map.getString(id.name());
296a0b75
NR
73 }
74
75 return id.toString();
76 }
77
78 /**
79 * Translate the given {@link KeyStroke} into a user text {@link String} of
80 * size 3.
81 *
82 * @param key
83 * the key to translate
84 *
85 * @return the translated text
86 */
87 public String trans(KeyStroke key) {
88 String keyTrans = "";
89
90 switch (key.getKeyType()) {
91 case Enter:
92 if (UiColors.getInstance().isUnicode())
93 keyTrans = " ⤶ ";
94 else
668268fc 95 keyTrans = trans(StringId.KEY_ENTER);
296a0b75
NR
96 break;
97 case Tab:
98 if (UiColors.getInstance().isUnicode())
99 keyTrans = " ↹ ";
100 else
668268fc 101 keyTrans = trans(StringId.KEY_TAB);
296a0b75
NR
102
103 break;
104 case Character:
105 keyTrans = " " + key.getCharacter() + " ";
106 break;
107 default:
108 keyTrans = "" + key.getKeyType();
109 int width = 3;
110 if (keyTrans.length() > width) {
111 keyTrans = keyTrans.substring(0, width);
112 } else if (keyTrans.length() < width) {
113 keyTrans = keyTrans
114 + new String(new char[width - keyTrans.length()])
115 .replace('\0', ' ');
116 }
117 break;
a3b510ab
NR
118 }
119
296a0b75 120 return keyTrans;
a3b510ab
NR
121 }
122
668268fc
NR
123 /**
124 * Initialise the translation mappings for the given language.
125 *
d3e940b6
NR
126 * @param language
127 * the language to initialise, in the form "en-GB" or "fr" for
128 * instance
668268fc 129 */
d3e940b6
NR
130 private void setLanguage(String language) {
131 map = Bundles.getBundle("resources", getLocaleFor(language));
a3b510ab 132 }
e7418457
NR
133
134 /**
135 * Create/update the translation .properties files. Will use the most likely
136 * candidate as base if the file does not already exists (for instance,
137 * "en_US" will use "en" as a base).
138 *
139 * @param args
140 * the path where the .properties files are, then the languages
141 * to create/update
142 *
143 * @throws IOException
144 * in case of IO errors
145 */
d3e940b6 146 static public void main(String[] args) throws IOException {
e7418457
NR
147 String path = args[0];
148 for (int i = 1; i < args.length; i++) {
d3e940b6 149 Locale locale = getLocaleFor(args[i]);
e7418457
NR
150 String code = locale.toString();
151 Trans trans = new Trans(code);
152
153 File file = null;
154 if (code.length() > 0) {
155 file = new File(path + "resources_" + code + ".properties");
156 } else {
157 // Default properties file:
158 file = new File(path + "resources.properties");
159 }
160
161 BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
162 new FileOutputStream(file), "UTF-8"));
163
164 String name = locale.getDisplayCountry(locale);
165 if (name.length() == 0)
166 name = locale.getDisplayLanguage(locale);
167 if (name.length() == 0)
168 name = "default";
169
170 if (code.length() > 0) {
171 name = name + " (" + code + ")";
172 }
173
174 writer.append("# " + name + " translation file (UTF-8)\n");
175 writer.append("# \n");
176 writer.append("# Note that any key can be doubled with a _NOUTF suffix\n");
177 writer.append("# to use when the flag --noutf is passed\n");
178 writer.append("# \n");
179 writer.append("# Also, the comments always refer to the key below them.\n");
180 writer.append("# \n");
181 writer.append("\n");
182
183 for (Field field : StringId.class.getDeclaredFields()) {
184 Meta meta = field.getAnnotation(Meta.class);
185 if (meta != null) {
186 StringId id = StringId.valueOf(field.getName());
187 String info = getMetaInfo(meta);
188 if (info != null) {
189 writer.append(info);
190 writer.append("\n");
191 }
192
193 writer.append(id.name());
194 writer.append(" = ");
195 if (!trans.trans(id).equals(id.name()))
196 writer.append(trans.trans(id));
197 writer.append("\n");
198 }
199 }
200
201 writer.close();
202 }
203 }
204
d3e940b6
NR
205 /**
206 * Return the {@link Locale} representing the given language.
207 *
208 * @param language
209 * the language to initialise, in the form "en-GB" or "fr" for
210 * instance
211 *
212 * @return the corresponding {@link Locale} or the default {@link Locale} if
213 * it is not known
214 */
215 static private Locale getLocaleFor(String language) {
216 Locale locale;
217
218 if (language == null) {
219 locale = Locale.getDefault();
220 } else {
221 language = language.replaceAll("_", "-");
222 String lang = language;
223 String country = null;
224 if (language.contains("-")) {
225 lang = language.split("-")[0];
226 country = language.split("-")[1];
227 }
228
229 if (country != null)
230 locale = new Locale(lang, country);
231 else
232 locale = new Locale(lang);
233 }
234
235 return locale;
236 }
237
e7418457
NR
238 /**
239 * Return formated, display-able information from the {@link Meta} field
240 * given. Each line will always starts with a "#" character.
241 *
242 * @param meta
243 * the {@link Meta} field
244 *
245 * @return the information to display or NULL if none
246 */
d3e940b6 247 static private String getMetaInfo(Meta meta) {
e7418457
NR
248 String what = meta.what();
249 String where = meta.where();
250 String format = meta.format();
251 String info = meta.info();
252
253 int opt = what.length() + where.length() + format.length();
254 if (opt + info.length() == 0)
255 return null;
256
257 StringBuilder builder = new StringBuilder();
258 builder.append("# ");
259
260 if (opt > 0) {
261 builder.append("(");
262 if (what.length() > 0) {
263 builder.append("WHAT: " + what);
264 if (where.length() + format.length() > 0)
265 builder.append(", ");
266 }
267
268 if (where.length() > 0) {
269 builder.append("WHERE: " + where);
270 if (format.length() > 0)
271 builder.append(", ");
272 }
273
274 if (format.length() > 0) {
275 builder.append("FORMAT: " + format);
276 }
277
278 builder.append(")\n# ");
279 }
280
281 builder.append(info);
282
283 return builder.toString();
284 }
285
286 /**
287 * The enum representing textual information to be translated to the user as
288 * a key.
289 *
290 * Note that each key that should be translated MUST be annotated with a
291 * {@link Meta} annotation.
292 *
293 * @author niki
294 *
295 */
296 public enum StringId {
297 DUMMY, // <-- TODO : remove
298 NULL, // Special usage, no annotations so it is not visible in
299 // .properties files
300 @Meta(what = "a key to press", where = "action keys", format = "MUST BE 3 chars long", info = "Tab key")
301 KEY_TAB, // keys
302 @Meta(what = "a key to press", where = "action keys", format = "MUST BE 3 chars long", info = "Enter key")
303 KEY_ENTER, //
304 @Meta(what = "", where = "", format = "", info = "")
305 KEY_ACTION_BACK, // MainWindow
306 @Meta(what = "", where = "", format = "", info = "")
307 KEY_ACTION_HELP, //
308 @Meta(what = "", where = "", format = "", info = "")
309 KEY_ACTION_VIEW_CARD, // FileList
310 @Meta(what = "", where = "", format = "", info = "")
311 KEY_ACTION_VIEW_CONTACT, // ContactList
312 @Meta(what = "", where = "", format = "", info = "")
313 KEY_ACTION_EDIT_CONTACT, //
314 @Meta(what = "", where = "", format = "", info = "")
315 KEY_ACTION_SAVE_CARD, //
316 @Meta(what = "", where = "", format = "", info = "")
317 KEY_ACTION_DELETE_CONTACT, //
318 @Meta(what = "", where = "", format = "", info = "")
319 KEY_ACTION_SEARCH, //
320 @Meta(what = "", where = "", format = "", info = "we could use: ' ', ┃, │...")
321 DEAULT_FIELD_SEPARATOR, // MainContentList
322 @Meta(what = "", where = "", format = "", info = "")
323 DEAULT_FIELD_SEPARATOR_NOUTF, //
324 @Meta(what = "", where = "", format = "", info = "")
325 KEY_ACTION_INVERT, // ContactDetails
326 @Meta(what = "", where = "", format = "", info = "")
327 KEY_ACTION_FULLSCREEN, //
328 @Meta(what = "", where = "", format = "", info = "")
329 KEY_ACTION_SWITCH_FORMAT, // multi-usage
330 };
a3b510ab 331}