Commit | Line | Data |
---|---|---|
7da41ecd | 1 | package be.nikiroo.jvcard.resources; |
a3b510ab | 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 | |
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 | */ | |
19 | public 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, // | |
286 | @Meta(what = "", where = "", format = "", info = "") | |
287 | KEY_ACTION_BACK, // MainWindow | |
288 | @Meta(what = "", where = "", format = "", info = "") | |
289 | KEY_ACTION_HELP, // | |
290 | @Meta(what = "", where = "", format = "", info = "") | |
291 | KEY_ACTION_VIEW_CARD, // FileList | |
292 | @Meta(what = "", where = "", format = "", info = "") | |
293 | KEY_ACTION_VIEW_CONTACT, // ContactList | |
294 | @Meta(what = "", where = "", format = "", info = "") | |
295 | KEY_ACTION_EDIT_CONTACT, // | |
296 | @Meta(what = "", where = "", format = "", info = "") | |
3634193b NR |
297 | KEY_ACTION_EDIT_CONTACT_RAW, // |
298 | @Meta(what = "", where = "", format = "", info = "") | |
e7418457 NR |
299 | KEY_ACTION_SAVE_CARD, // |
300 | @Meta(what = "", where = "", format = "", info = "") | |
301 | KEY_ACTION_DELETE_CONTACT, // | |
302 | @Meta(what = "", where = "", format = "", info = "") | |
303 | KEY_ACTION_SEARCH, // | |
304 | @Meta(what = "", where = "", format = "", info = "we could use: ' ', ┃, │...") | |
305 | DEAULT_FIELD_SEPARATOR, // MainContentList | |
306 | @Meta(what = "", where = "", format = "", info = "") | |
307 | DEAULT_FIELD_SEPARATOR_NOUTF, // | |
308 | @Meta(what = "", where = "", format = "", info = "") | |
309 | KEY_ACTION_INVERT, // ContactDetails | |
310 | @Meta(what = "", where = "", format = "", info = "") | |
311 | KEY_ACTION_FULLSCREEN, // | |
312 | @Meta(what = "", where = "", format = "", info = "") | |
313 | KEY_ACTION_SWITCH_FORMAT, // multi-usage | |
9b8cb729 NR |
314 | @Meta(what = "Action key", where = "Contact list, Edit Contact", format = "", info = "Add a new contact/field") |
315 | KEY_ACTION_ADD, // | |
316 | @Meta(what = "User question: TEXT", where = "Contact list", format = "", info = "New contact") | |
317 | ASK_USER_CONTACT_NAME, // | |
318 | @Meta(what = "User question: [Y|N]", where = "Contact list", format = "%s = contact name", info = "Delete contact") | |
319 | CONFIRM_USER_DELETE_CONTACT, // | |
320 | @Meta(what = "Error", where = "Contact list", format = "%s = contact name", info = "cannot delete a contact") | |
321 | ERR_CANNOT_DELETE_CONTACT, // | |
e7418457 | 322 | }; |
a3b510ab | 323 | } |