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