Commit | Line | Data |
---|---|---|
ec1f3444 NR |
1 | package be.nikiroo.utils.resources; |
2 | ||
3 | import java.io.File; | |
4 | import java.io.IOException; | |
5 | import java.io.Writer; | |
6 | import java.util.LinkedList; | |
7 | import java.util.List; | |
8 | import java.util.Locale; | |
9 | import java.util.regex.Pattern; | |
10 | ||
ec1f3444 NR |
11 | /** |
12 | * This class manages a translation-dedicated Bundle. | |
13 | * <p> | |
14 | * Two special cases are handled for the used enum: | |
15 | * <ul> | |
16 | * <li>NULL will always will return an empty {@link String}</li> | |
17 | * <li>DUMMY will return "[DUMMY]" (maybe with a suffix and/or "NOUTF")</li> | |
18 | * </ul> | |
19 | * | |
db31c358 NR |
20 | * @param <E> |
21 | * the enum to use to get values out of this class | |
22 | * | |
ec1f3444 NR |
23 | * @author niki |
24 | */ | |
25 | public class TransBundle<E extends Enum<E>> extends Bundle<E> { | |
26 | private boolean utf = true; | |
27 | private Locale locale; | |
28 | private boolean defaultLocale = false; | |
29 | ||
30 | /** | |
31 | * Create a translation service with the default language. | |
32 | * | |
33 | * @param type | |
34 | * a runtime instance of the class of E | |
35 | * @param name | |
36 | * the name of the {@link Bundles} | |
37 | */ | |
38 | public TransBundle(Class<E> type, Enum<?> name) { | |
9695f591 | 39 | this(type, name, (Locale) null); |
ec1f3444 NR |
40 | } |
41 | ||
42 | /** | |
43 | * Create a translation service for the given language (will fall back to | |
44 | * the default one i not found). | |
45 | * | |
46 | * @param type | |
47 | * a runtime instance of the class of E | |
48 | * @param name | |
49 | * the name of the {@link Bundles} | |
50 | * @param language | |
9695f591 | 51 | * the language to use, can be NULL for default |
ec1f3444 NR |
52 | */ |
53 | public TransBundle(Class<E> type, Enum<?> name, String language) { | |
db31c358 | 54 | super(type, name, null); |
9695f591 NR |
55 | setLocale(language); |
56 | } | |
57 | ||
58 | /** | |
59 | * Create a translation service for the given language (will fall back to | |
60 | * the default one i not found). | |
61 | * | |
62 | * @param type | |
63 | * a runtime instance of the class of E | |
64 | * @param name | |
65 | * the name of the {@link Bundles} | |
66 | * @param language | |
67 | * the language to use, can be NULL for default | |
68 | */ | |
69 | public TransBundle(Class<E> type, Enum<?> name, Locale language) { | |
70 | super(type, name, null); | |
71 | setLocale(language); | |
ec1f3444 NR |
72 | } |
73 | ||
74 | /** | |
75 | * Translate the given id into user text. | |
76 | * | |
77 | * @param stringId | |
78 | * the ID to translate | |
79 | * @param values | |
80 | * the values to insert instead of the place holders in the | |
81 | * translation | |
82 | * | |
83 | * @return the translated text with the given value where required or NULL | |
84 | * if not found (not present in the resource file) | |
85 | */ | |
86 | public String getString(E stringId, Object... values) { | |
87 | return getStringX(stringId, "", values); | |
88 | } | |
89 | ||
90 | /** | |
91 | * Translate the given id into user text. | |
92 | * | |
93 | * @param stringId | |
94 | * the ID to translate | |
95 | * @param values | |
96 | * the values to insert instead of the place holders in the | |
97 | * translation | |
98 | * | |
99 | * @return the translated text with the given value where required or NULL | |
100 | * if not found (not present in the resource file) | |
101 | */ | |
102 | public String getStringNOUTF(E stringId, Object... values) { | |
103 | return getStringX(stringId, "NOUTF", values); | |
104 | } | |
105 | ||
106 | /** | |
107 | * Translate the given id suffixed with the runtime value "_suffix" (that | |
108 | * is, "_" and suffix) into user text. | |
109 | * | |
110 | * @param stringId | |
111 | * the ID to translate | |
112 | * @param values | |
113 | * the values to insert instead of the place holders in the | |
114 | * translation | |
115 | * @param suffix | |
116 | * the runtime suffix | |
117 | * | |
118 | * @return the translated text with the given value where required or NULL | |
119 | * if not found (not present in the resource file) | |
120 | */ | |
121 | public String getStringX(E stringId, String suffix, Object... values) { | |
122 | E id = stringId; | |
123 | String result = ""; | |
124 | ||
125 | String key = id.name() | |
126 | + ((suffix == null || suffix.isEmpty()) ? "" : "_" | |
127 | + suffix.toUpperCase()); | |
128 | ||
129 | if (!isUnicode()) { | |
130 | if (containsKey(key + "_NOUTF")) { | |
131 | key += "_NOUTF"; | |
132 | } | |
133 | } | |
134 | ||
135 | if ("NULL".equals(id.name().toUpperCase())) { | |
136 | result = ""; | |
137 | } else if ("DUMMY".equals(id.name().toUpperCase())) { | |
138 | result = "[" + key.toLowerCase() + "]"; | |
139 | } else if (containsKey(key)) { | |
13bfeea6 | 140 | result = getString(key, null); |
4840f1a4 NR |
141 | if (result == null) { |
142 | result = getMetaDef(id.name()); | |
143 | } | |
ec1f3444 NR |
144 | } else { |
145 | result = null; | |
146 | } | |
147 | ||
cd0c27d2 | 148 | if (values != null && values.length > 0 && result != null) { |
ec1f3444 | 149 | return String.format(locale, result, values); |
cd0c27d2 NR |
150 | } |
151 | ||
152 | return result; | |
ec1f3444 NR |
153 | } |
154 | ||
155 | /** | |
156 | * Check if unicode characters should be used. | |
157 | * | |
158 | * @return TRUE to allow unicode | |
159 | */ | |
160 | public boolean isUnicode() { | |
161 | return utf; | |
162 | } | |
163 | ||
164 | /** | |
165 | * Allow or disallow unicode characters in the program. | |
166 | * | |
167 | * @param utf | |
168 | * TRUE to allow unuciode, FALSE to only allow ASCII characters | |
169 | */ | |
170 | public void setUnicode(boolean utf) { | |
171 | this.utf = utf; | |
172 | } | |
173 | ||
174 | /** | |
0f7de31e | 175 | * Return all the languages known by the program for this bundle. |
ec1f3444 NR |
176 | * |
177 | * @return the known language codes | |
178 | */ | |
179 | public List<String> getKnownLanguages() { | |
db31c358 | 180 | return getKnownLanguages(keyType); |
ec1f3444 NR |
181 | } |
182 | ||
9695f591 NR |
183 | /** |
184 | * The current language (which can be the default one, but NOT NULL). | |
185 | * | |
186 | * @return the language, not NULL | |
187 | */ | |
188 | public Locale getLocale() { | |
189 | return locale; | |
190 | } | |
191 | ||
192 | /** | |
193 | * The current language (which can be the default one, but NOT NULL). | |
194 | * | |
195 | * @return the language, not NULL, in a display format (fr-BE, en-GB, es, | |
196 | * de...) | |
197 | */ | |
198 | public String getLocaleString() { | |
199 | String lang = locale.getLanguage(); | |
200 | String country = locale.getCountry(); | |
201 | if (country != null && !country.isEmpty()) { | |
202 | return lang + "-" + country; | |
203 | } | |
204 | return lang; | |
205 | } | |
206 | ||
ec1f3444 NR |
207 | /** |
208 | * Initialise the translation mappings for the given language. | |
209 | * | |
210 | * @param language | |
211 | * the language to initialise, in the form "en-GB" or "fr" for | |
212 | * instance | |
213 | */ | |
9695f591 NR |
214 | private void setLocale(String language) { |
215 | setLocale(getLocaleFor(language)); | |
216 | } | |
217 | ||
218 | /** | |
219 | * Initialise the translation mappings for the given language. | |
220 | * | |
221 | * @param language | |
222 | * the language to initialise, or NULL for default | |
223 | */ | |
224 | private void setLocale(Locale language) { | |
225 | if (language != null) { | |
226 | defaultLocale = false; | |
227 | locale = language; | |
228 | } else { | |
229 | defaultLocale = true; | |
230 | locale = Locale.getDefault(); | |
231 | } | |
232 | ||
db31c358 | 233 | setBundle(keyType, locale, false); |
ec1f3444 NR |
234 | } |
235 | ||
80383c14 | 236 | @Override |
e9ca6bb8 | 237 | public void reload(boolean resetToDefault) { |
db31c358 | 238 | setBundle(keyType, locale, resetToDefault); |
80383c14 NR |
239 | } |
240 | ||
ec1f3444 NR |
241 | @Override |
242 | public String getString(E id) { | |
243 | return getString(id, (Object[]) null); | |
244 | } | |
245 | ||
246 | /** | |
247 | * Create/update the .properties files for each supported language and for | |
248 | * the default language. | |
249 | * <p> | |
250 | * Note: this method is <b>NOT</b> thread-safe. | |
251 | * | |
252 | * @param path | |
253 | * the path where the .properties files are | |
254 | * | |
255 | * @throws IOException | |
256 | * in case of IO errors | |
257 | */ | |
258 | @Override | |
259 | public void updateFile(String path) throws IOException { | |
260 | String prev = locale.getLanguage(); | |
487926f7 | 261 | Object status = takeSnapshot(); |
ec1f3444 | 262 | |
e9ca6bb8 | 263 | // default locale |
9695f591 NR |
264 | setLocale((Locale) null); |
265 | if (prev.equals(Locale.getDefault().getLanguage())) { | |
e9ca6bb8 | 266 | // restore snapshot if default locale = current locale |
487926f7 | 267 | restoreSnapshot(status); |
e9ca6bb8 | 268 | } |
ec1f3444 NR |
269 | super.updateFile(path); |
270 | ||
271 | for (String lang : getKnownLanguages()) { | |
9695f591 | 272 | setLocale(lang); |
e9ca6bb8 | 273 | if (lang.equals(prev)) { |
487926f7 | 274 | restoreSnapshot(status); |
e9ca6bb8 | 275 | } |
ec1f3444 NR |
276 | super.updateFile(path); |
277 | } | |
278 | ||
9695f591 | 279 | setLocale(prev); |
487926f7 | 280 | restoreSnapshot(status); |
ec1f3444 NR |
281 | } |
282 | ||
283 | @Override | |
284 | protected File getUpdateFile(String path) { | |
285 | String code = locale.toString(); | |
286 | File file = null; | |
287 | if (!defaultLocale && code.length() > 0) { | |
db31c358 | 288 | file = new File(path, keyType.name() + "_" + code + ".properties"); |
ec1f3444 NR |
289 | } else { |
290 | // Default properties file: | |
db31c358 | 291 | file = new File(path, keyType.name() + ".properties"); |
ec1f3444 NR |
292 | } |
293 | ||
294 | return file; | |
295 | } | |
296 | ||
297 | @Override | |
298 | protected void writeHeader(Writer writer) throws IOException { | |
299 | String code = locale.toString(); | |
300 | String name = locale.getDisplayCountry(locale); | |
301 | ||
302 | if (name.length() == 0) { | |
303 | name = locale.getDisplayLanguage(locale); | |
304 | } | |
305 | ||
306 | if (name.length() == 0) { | |
307 | name = "default"; | |
308 | } | |
309 | ||
310 | if (code.length() > 0) { | |
311 | name = name + " (" + code + ")"; | |
312 | } | |
313 | ||
314 | name = (name + " " + getBundleDisplayName()).trim(); | |
315 | ||
316 | writer.write("# " + name + " translation file (UTF-8)\n"); | |
317 | writer.write("# \n"); | |
318 | writer.write("# Note that any key can be doubled with a _NOUTF suffix\n"); | |
319 | writer.write("# to use when the NOUTF env variable is set to 1\n"); | |
320 | writer.write("# \n"); | |
321 | writer.write("# Also, the comments always refer to the key below them.\n"); | |
322 | writer.write("# \n"); | |
323 | } | |
324 | ||
325 | @Override | |
326 | protected void writeValue(Writer writer, E id) throws IOException { | |
327 | super.writeValue(writer, id); | |
328 | ||
329 | String name = id.name() + "_NOUTF"; | |
330 | if (containsKey(name)) { | |
13bfeea6 | 331 | String value = getString(name, null); |
4840f1a4 NR |
332 | if (value == null) { |
333 | value = getMetaDef(id.name()); | |
334 | } | |
b15a4985 NR |
335 | boolean set = isSet(id, false); |
336 | writeValue(writer, name, value, set); | |
ec1f3444 NR |
337 | } |
338 | } | |
339 | ||
340 | /** | |
341 | * Return the {@link Locale} representing the given language. | |
342 | * | |
343 | * @param language | |
344 | * the language to initialise, in the form "en-GB" or "fr" for | |
345 | * instance | |
346 | * | |
9695f591 | 347 | * @return the corresponding {@link Locale} or NULL if it is not known |
ec1f3444 NR |
348 | */ |
349 | static private Locale getLocaleFor(String language) { | |
350 | Locale locale; | |
351 | ||
9695f591 NR |
352 | if (language == null || language.trim().isEmpty()) { |
353 | return null; | |
354 | } | |
ec1f3444 | 355 | |
9695f591 NR |
356 | language = language.replaceAll("_", "-"); |
357 | String lang = language; | |
358 | String country = null; | |
359 | if (language.contains("-")) { | |
360 | lang = language.split("-")[0]; | |
361 | country = language.split("-")[1]; | |
ec1f3444 NR |
362 | } |
363 | ||
9695f591 NR |
364 | if (country != null) |
365 | locale = new Locale(lang, country); | |
366 | else | |
367 | locale = new Locale(lang); | |
368 | ||
ec1f3444 NR |
369 | return locale; |
370 | } | |
371 | ||
372 | /** | |
373 | * Return all the languages known by the program. | |
374 | * | |
375 | * @param name | |
376 | * the enumeration on which we translate | |
377 | * | |
378 | * @return the known language codes | |
379 | */ | |
380 | static protected List<String> getKnownLanguages(Enum<?> name) { | |
381 | List<String> resources = new LinkedList<String>(); | |
382 | ||
383 | String regex = ".*" + name.name() + "[_a-zA-Za]*\\.properties$"; | |
384 | ||
385 | for (String res : TransBundle_ResourceList.getResources(Pattern | |
386 | .compile(regex))) { | |
387 | String resource = res; | |
388 | int index = resource.lastIndexOf('/'); | |
389 | if (index >= 0 && index < (resource.length() - 1)) | |
390 | resource = resource.substring(index + 1); | |
391 | if (resource.startsWith(name.name())) { | |
392 | resource = resource.substring(0, resource.length() | |
393 | - ".properties".length()); | |
394 | resource = resource.substring(name.name().length()); | |
395 | if (resource.startsWith("_")) { | |
396 | resource = resource.substring(1); | |
397 | resources.add(resource); | |
398 | } | |
399 | } | |
400 | } | |
401 | ||
402 | return resources; | |
403 | } | |
404 | } |