Commit | Line | Data |
---|---|---|
a3b510ab NR |
1 | package be.nikiroo.jvcard.i18n; |
2 | ||
e7418457 NR |
3 | import java.io.BufferedWriter; |
4 | import java.io.File; | |
5 | import java.io.FileOutputStream; | |
6 | import java.io.IOException; | |
7 | import java.io.OutputStreamWriter; | |
8 | import java.lang.reflect.Field; | |
668268fc NR |
9 | import java.util.Locale; |
10 | import java.util.ResourceBundle; | |
296a0b75 | 11 | |
2a96e7b2 | 12 | import be.nikiroo.jvcard.resources.Bundles; |
296a0b75 NR |
13 | import be.nikiroo.jvcard.tui.UiColors; |
14 | ||
668268fc NR |
15 | import 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 | */ | |
24 | public class Trans { | |
668268fc | 25 | 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 | } |