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)) { | |
140 | result = getString(key); | |
141 | } else { | |
142 | result = null; | |
143 | } | |
144 | ||
cd0c27d2 | 145 | if (values != null && values.length > 0 && result != null) { |
ec1f3444 | 146 | return String.format(locale, result, values); |
cd0c27d2 NR |
147 | } |
148 | ||
149 | return result; | |
ec1f3444 NR |
150 | } |
151 | ||
152 | /** | |
153 | * Check if unicode characters should be used. | |
154 | * | |
155 | * @return TRUE to allow unicode | |
156 | */ | |
157 | public boolean isUnicode() { | |
158 | return utf; | |
159 | } | |
160 | ||
161 | /** | |
162 | * Allow or disallow unicode characters in the program. | |
163 | * | |
164 | * @param utf | |
165 | * TRUE to allow unuciode, FALSE to only allow ASCII characters | |
166 | */ | |
167 | public void setUnicode(boolean utf) { | |
168 | this.utf = utf; | |
169 | } | |
170 | ||
171 | /** | |
172 | * Return all the languages known by the program. | |
173 | * | |
174 | * | |
175 | * @return the known language codes | |
176 | */ | |
177 | public List<String> getKnownLanguages() { | |
db31c358 | 178 | return getKnownLanguages(keyType); |
ec1f3444 NR |
179 | } |
180 | ||
9695f591 NR |
181 | /** |
182 | * The current language (which can be the default one, but NOT NULL). | |
183 | * | |
184 | * @return the language, not NULL | |
185 | */ | |
186 | public Locale getLocale() { | |
187 | return locale; | |
188 | } | |
189 | ||
190 | /** | |
191 | * The current language (which can be the default one, but NOT NULL). | |
192 | * | |
193 | * @return the language, not NULL, in a display format (fr-BE, en-GB, es, | |
194 | * de...) | |
195 | */ | |
196 | public String getLocaleString() { | |
197 | String lang = locale.getLanguage(); | |
198 | String country = locale.getCountry(); | |
199 | if (country != null && !country.isEmpty()) { | |
200 | return lang + "-" + country; | |
201 | } | |
202 | return lang; | |
203 | } | |
204 | ||
ec1f3444 NR |
205 | /** |
206 | * Initialise the translation mappings for the given language. | |
207 | * | |
208 | * @param language | |
209 | * the language to initialise, in the form "en-GB" or "fr" for | |
210 | * instance | |
211 | */ | |
9695f591 NR |
212 | private void setLocale(String language) { |
213 | setLocale(getLocaleFor(language)); | |
214 | } | |
215 | ||
216 | /** | |
217 | * Initialise the translation mappings for the given language. | |
218 | * | |
219 | * @param language | |
220 | * the language to initialise, or NULL for default | |
221 | */ | |
222 | private void setLocale(Locale language) { | |
223 | if (language != null) { | |
224 | defaultLocale = false; | |
225 | locale = language; | |
226 | } else { | |
227 | defaultLocale = true; | |
228 | locale = Locale.getDefault(); | |
229 | } | |
230 | ||
db31c358 | 231 | setBundle(keyType, locale, false); |
ec1f3444 NR |
232 | } |
233 | ||
80383c14 | 234 | @Override |
e9ca6bb8 | 235 | public void reload(boolean resetToDefault) { |
db31c358 | 236 | setBundle(keyType, locale, resetToDefault); |
80383c14 NR |
237 | } |
238 | ||
ec1f3444 NR |
239 | @Override |
240 | public String getString(E id) { | |
241 | return getString(id, (Object[]) null); | |
242 | } | |
243 | ||
244 | /** | |
245 | * Create/update the .properties files for each supported language and for | |
246 | * the default language. | |
247 | * <p> | |
248 | * Note: this method is <b>NOT</b> thread-safe. | |
249 | * | |
250 | * @param path | |
251 | * the path where the .properties files are | |
252 | * | |
253 | * @throws IOException | |
254 | * in case of IO errors | |
255 | */ | |
256 | @Override | |
257 | public void updateFile(String path) throws IOException { | |
258 | String prev = locale.getLanguage(); | |
487926f7 | 259 | Object status = takeSnapshot(); |
ec1f3444 | 260 | |
e9ca6bb8 | 261 | // default locale |
9695f591 NR |
262 | setLocale((Locale) null); |
263 | if (prev.equals(Locale.getDefault().getLanguage())) { | |
e9ca6bb8 | 264 | // restore snapshot if default locale = current locale |
487926f7 | 265 | restoreSnapshot(status); |
e9ca6bb8 | 266 | } |
ec1f3444 NR |
267 | super.updateFile(path); |
268 | ||
269 | for (String lang : getKnownLanguages()) { | |
9695f591 | 270 | setLocale(lang); |
e9ca6bb8 | 271 | if (lang.equals(prev)) { |
487926f7 | 272 | restoreSnapshot(status); |
e9ca6bb8 | 273 | } |
ec1f3444 NR |
274 | super.updateFile(path); |
275 | } | |
276 | ||
9695f591 | 277 | setLocale(prev); |
487926f7 | 278 | restoreSnapshot(status); |
ec1f3444 NR |
279 | } |
280 | ||
281 | @Override | |
282 | protected File getUpdateFile(String path) { | |
283 | String code = locale.toString(); | |
284 | File file = null; | |
285 | if (!defaultLocale && code.length() > 0) { | |
db31c358 | 286 | file = new File(path, keyType.name() + "_" + code + ".properties"); |
ec1f3444 NR |
287 | } else { |
288 | // Default properties file: | |
db31c358 | 289 | file = new File(path, keyType.name() + ".properties"); |
ec1f3444 NR |
290 | } |
291 | ||
292 | return file; | |
293 | } | |
294 | ||
295 | @Override | |
296 | protected void writeHeader(Writer writer) throws IOException { | |
297 | String code = locale.toString(); | |
298 | String name = locale.getDisplayCountry(locale); | |
299 | ||
300 | if (name.length() == 0) { | |
301 | name = locale.getDisplayLanguage(locale); | |
302 | } | |
303 | ||
304 | if (name.length() == 0) { | |
305 | name = "default"; | |
306 | } | |
307 | ||
308 | if (code.length() > 0) { | |
309 | name = name + " (" + code + ")"; | |
310 | } | |
311 | ||
312 | name = (name + " " + getBundleDisplayName()).trim(); | |
313 | ||
314 | writer.write("# " + name + " translation file (UTF-8)\n"); | |
315 | writer.write("# \n"); | |
316 | writer.write("# Note that any key can be doubled with a _NOUTF suffix\n"); | |
317 | writer.write("# to use when the NOUTF env variable is set to 1\n"); | |
318 | writer.write("# \n"); | |
319 | writer.write("# Also, the comments always refer to the key below them.\n"); | |
320 | writer.write("# \n"); | |
321 | } | |
322 | ||
323 | @Override | |
324 | protected void writeValue(Writer writer, E id) throws IOException { | |
325 | super.writeValue(writer, id); | |
326 | ||
327 | String name = id.name() + "_NOUTF"; | |
328 | if (containsKey(name)) { | |
329 | String value = getString(name); | |
330 | writeValue(writer, name, value); | |
331 | } | |
332 | } | |
333 | ||
334 | /** | |
335 | * Return the {@link Locale} representing the given language. | |
336 | * | |
337 | * @param language | |
338 | * the language to initialise, in the form "en-GB" or "fr" for | |
339 | * instance | |
340 | * | |
9695f591 | 341 | * @return the corresponding {@link Locale} or NULL if it is not known |
ec1f3444 NR |
342 | */ |
343 | static private Locale getLocaleFor(String language) { | |
344 | Locale locale; | |
345 | ||
9695f591 NR |
346 | if (language == null || language.trim().isEmpty()) { |
347 | return null; | |
348 | } | |
ec1f3444 | 349 | |
9695f591 NR |
350 | language = language.replaceAll("_", "-"); |
351 | String lang = language; | |
352 | String country = null; | |
353 | if (language.contains("-")) { | |
354 | lang = language.split("-")[0]; | |
355 | country = language.split("-")[1]; | |
ec1f3444 NR |
356 | } |
357 | ||
9695f591 NR |
358 | if (country != null) |
359 | locale = new Locale(lang, country); | |
360 | else | |
361 | locale = new Locale(lang); | |
362 | ||
ec1f3444 NR |
363 | return locale; |
364 | } | |
365 | ||
366 | /** | |
367 | * Return all the languages known by the program. | |
368 | * | |
369 | * @param name | |
370 | * the enumeration on which we translate | |
371 | * | |
372 | * @return the known language codes | |
373 | */ | |
374 | static protected List<String> getKnownLanguages(Enum<?> name) { | |
375 | List<String> resources = new LinkedList<String>(); | |
376 | ||
377 | String regex = ".*" + name.name() + "[_a-zA-Za]*\\.properties$"; | |
378 | ||
379 | for (String res : TransBundle_ResourceList.getResources(Pattern | |
380 | .compile(regex))) { | |
381 | String resource = res; | |
382 | int index = resource.lastIndexOf('/'); | |
383 | if (index >= 0 && index < (resource.length() - 1)) | |
384 | resource = resource.substring(index + 1); | |
385 | if (resource.startsWith(name.name())) { | |
386 | resource = resource.substring(0, resource.length() | |
387 | - ".properties".length()); | |
388 | resource = resource.substring(name.name().length()); | |
389 | if (resource.startsWith("_")) { | |
390 | resource = resource.substring(1); | |
391 | resources.add(resource); | |
392 | } | |
393 | } | |
394 | } | |
395 | ||
396 | return resources; | |
397 | } | |
398 | } |