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) { | |
db31c358 | 39 | super(type, name, null); |
ec1f3444 NR |
40 | setLanguage(null); |
41 | } | |
42 | ||
43 | /** | |
44 | * Create a translation service for the given language (will fall back to | |
45 | * the default one i not found). | |
46 | * | |
47 | * @param type | |
48 | * a runtime instance of the class of E | |
49 | * @param name | |
50 | * the name of the {@link Bundles} | |
51 | * @param language | |
52 | * the language to use | |
53 | */ | |
54 | public TransBundle(Class<E> type, Enum<?> name, String language) { | |
db31c358 | 55 | super(type, name, null); |
ec1f3444 NR |
56 | setLanguage(language); |
57 | } | |
58 | ||
59 | /** | |
60 | * Translate the given id into user text. | |
61 | * | |
62 | * @param stringId | |
63 | * the ID to translate | |
64 | * @param values | |
65 | * the values to insert instead of the place holders in the | |
66 | * translation | |
67 | * | |
68 | * @return the translated text with the given value where required or NULL | |
69 | * if not found (not present in the resource file) | |
70 | */ | |
71 | public String getString(E stringId, Object... values) { | |
72 | return getStringX(stringId, "", values); | |
73 | } | |
74 | ||
75 | /** | |
76 | * Translate the given id into user text. | |
77 | * | |
78 | * @param stringId | |
79 | * the ID to translate | |
80 | * @param values | |
81 | * the values to insert instead of the place holders in the | |
82 | * translation | |
83 | * | |
84 | * @return the translated text with the given value where required or NULL | |
85 | * if not found (not present in the resource file) | |
86 | */ | |
87 | public String getStringNOUTF(E stringId, Object... values) { | |
88 | return getStringX(stringId, "NOUTF", values); | |
89 | } | |
90 | ||
91 | /** | |
92 | * Translate the given id suffixed with the runtime value "_suffix" (that | |
93 | * is, "_" and suffix) into user text. | |
94 | * | |
95 | * @param stringId | |
96 | * the ID to translate | |
97 | * @param values | |
98 | * the values to insert instead of the place holders in the | |
99 | * translation | |
100 | * @param suffix | |
101 | * the runtime suffix | |
102 | * | |
103 | * @return the translated text with the given value where required or NULL | |
104 | * if not found (not present in the resource file) | |
105 | */ | |
106 | public String getStringX(E stringId, String suffix, Object... values) { | |
107 | E id = stringId; | |
108 | String result = ""; | |
109 | ||
110 | String key = id.name() | |
111 | + ((suffix == null || suffix.isEmpty()) ? "" : "_" | |
112 | + suffix.toUpperCase()); | |
113 | ||
114 | if (!isUnicode()) { | |
115 | if (containsKey(key + "_NOUTF")) { | |
116 | key += "_NOUTF"; | |
117 | } | |
118 | } | |
119 | ||
120 | if ("NULL".equals(id.name().toUpperCase())) { | |
121 | result = ""; | |
122 | } else if ("DUMMY".equals(id.name().toUpperCase())) { | |
123 | result = "[" + key.toLowerCase() + "]"; | |
124 | } else if (containsKey(key)) { | |
125 | result = getString(key); | |
126 | } else { | |
127 | result = null; | |
128 | } | |
129 | ||
cd0c27d2 | 130 | if (values != null && values.length > 0 && result != null) { |
ec1f3444 | 131 | return String.format(locale, result, values); |
cd0c27d2 NR |
132 | } |
133 | ||
134 | return result; | |
ec1f3444 NR |
135 | } |
136 | ||
137 | /** | |
138 | * Check if unicode characters should be used. | |
139 | * | |
140 | * @return TRUE to allow unicode | |
141 | */ | |
142 | public boolean isUnicode() { | |
143 | return utf; | |
144 | } | |
145 | ||
146 | /** | |
147 | * Allow or disallow unicode characters in the program. | |
148 | * | |
149 | * @param utf | |
150 | * TRUE to allow unuciode, FALSE to only allow ASCII characters | |
151 | */ | |
152 | public void setUnicode(boolean utf) { | |
153 | this.utf = utf; | |
154 | } | |
155 | ||
156 | /** | |
157 | * Return all the languages known by the program. | |
158 | * | |
159 | * | |
160 | * @return the known language codes | |
161 | */ | |
162 | public List<String> getKnownLanguages() { | |
db31c358 | 163 | return getKnownLanguages(keyType); |
ec1f3444 NR |
164 | } |
165 | ||
166 | /** | |
167 | * Initialise the translation mappings for the given language. | |
168 | * | |
169 | * @param language | |
170 | * the language to initialise, in the form "en-GB" or "fr" for | |
171 | * instance | |
172 | */ | |
173 | private void setLanguage(String language) { | |
174 | defaultLocale = (language == null || language.length() == 0); | |
175 | locale = getLocaleFor(language); | |
db31c358 | 176 | setBundle(keyType, locale, false); |
ec1f3444 NR |
177 | } |
178 | ||
80383c14 | 179 | @Override |
e9ca6bb8 | 180 | public void reload(boolean resetToDefault) { |
db31c358 | 181 | setBundle(keyType, locale, resetToDefault); |
80383c14 NR |
182 | } |
183 | ||
ec1f3444 NR |
184 | @Override |
185 | public String getString(E id) { | |
186 | return getString(id, (Object[]) null); | |
187 | } | |
188 | ||
189 | /** | |
190 | * Create/update the .properties files for each supported language and for | |
191 | * the default language. | |
192 | * <p> | |
193 | * Note: this method is <b>NOT</b> thread-safe. | |
194 | * | |
195 | * @param path | |
196 | * the path where the .properties files are | |
197 | * | |
198 | * @throws IOException | |
199 | * in case of IO errors | |
200 | */ | |
201 | @Override | |
202 | public void updateFile(String path) throws IOException { | |
203 | String prev = locale.getLanguage(); | |
487926f7 | 204 | Object status = takeSnapshot(); |
ec1f3444 | 205 | |
e9ca6bb8 NR |
206 | // default locale |
207 | setLanguage(null); | |
208 | if (prev.equals(getLocaleFor(null).getLanguage())) { | |
209 | // restore snapshot if default locale = current locale | |
487926f7 | 210 | restoreSnapshot(status); |
e9ca6bb8 | 211 | } |
ec1f3444 NR |
212 | super.updateFile(path); |
213 | ||
214 | for (String lang : getKnownLanguages()) { | |
215 | setLanguage(lang); | |
e9ca6bb8 | 216 | if (lang.equals(prev)) { |
487926f7 | 217 | restoreSnapshot(status); |
e9ca6bb8 | 218 | } |
ec1f3444 NR |
219 | super.updateFile(path); |
220 | } | |
221 | ||
222 | setLanguage(prev); | |
487926f7 | 223 | restoreSnapshot(status); |
ec1f3444 NR |
224 | } |
225 | ||
226 | @Override | |
227 | protected File getUpdateFile(String path) { | |
228 | String code = locale.toString(); | |
229 | File file = null; | |
230 | if (!defaultLocale && code.length() > 0) { | |
db31c358 | 231 | file = new File(path, keyType.name() + "_" + code + ".properties"); |
ec1f3444 NR |
232 | } else { |
233 | // Default properties file: | |
db31c358 | 234 | file = new File(path, keyType.name() + ".properties"); |
ec1f3444 NR |
235 | } |
236 | ||
237 | return file; | |
238 | } | |
239 | ||
240 | @Override | |
241 | protected void writeHeader(Writer writer) throws IOException { | |
242 | String code = locale.toString(); | |
243 | String name = locale.getDisplayCountry(locale); | |
244 | ||
245 | if (name.length() == 0) { | |
246 | name = locale.getDisplayLanguage(locale); | |
247 | } | |
248 | ||
249 | if (name.length() == 0) { | |
250 | name = "default"; | |
251 | } | |
252 | ||
253 | if (code.length() > 0) { | |
254 | name = name + " (" + code + ")"; | |
255 | } | |
256 | ||
257 | name = (name + " " + getBundleDisplayName()).trim(); | |
258 | ||
259 | writer.write("# " + name + " translation file (UTF-8)\n"); | |
260 | writer.write("# \n"); | |
261 | writer.write("# Note that any key can be doubled with a _NOUTF suffix\n"); | |
262 | writer.write("# to use when the NOUTF env variable is set to 1\n"); | |
263 | writer.write("# \n"); | |
264 | writer.write("# Also, the comments always refer to the key below them.\n"); | |
265 | writer.write("# \n"); | |
266 | } | |
267 | ||
268 | @Override | |
269 | protected void writeValue(Writer writer, E id) throws IOException { | |
270 | super.writeValue(writer, id); | |
271 | ||
272 | String name = id.name() + "_NOUTF"; | |
273 | if (containsKey(name)) { | |
274 | String value = getString(name); | |
275 | writeValue(writer, name, value); | |
276 | } | |
277 | } | |
278 | ||
279 | /** | |
280 | * Return the {@link Locale} representing the given language. | |
281 | * | |
282 | * @param language | |
283 | * the language to initialise, in the form "en-GB" or "fr" for | |
284 | * instance | |
285 | * | |
286 | * @return the corresponding {@link Locale} or the default {@link Locale} if | |
287 | * it is not known | |
288 | */ | |
289 | static private Locale getLocaleFor(String language) { | |
290 | Locale locale; | |
291 | ||
292 | if (language == null) { | |
293 | locale = Locale.getDefault(); | |
294 | } else { | |
295 | language = language.replaceAll("_", "-"); | |
296 | String lang = language; | |
297 | String country = null; | |
298 | if (language.contains("-")) { | |
299 | lang = language.split("-")[0]; | |
300 | country = language.split("-")[1]; | |
301 | } | |
302 | ||
303 | if (country != null) | |
304 | locale = new Locale(lang, country); | |
305 | else | |
306 | locale = new Locale(lang); | |
307 | } | |
308 | ||
309 | return locale; | |
310 | } | |
311 | ||
312 | /** | |
313 | * Return all the languages known by the program. | |
314 | * | |
315 | * @param name | |
316 | * the enumeration on which we translate | |
317 | * | |
318 | * @return the known language codes | |
319 | */ | |
320 | static protected List<String> getKnownLanguages(Enum<?> name) { | |
321 | List<String> resources = new LinkedList<String>(); | |
322 | ||
323 | String regex = ".*" + name.name() + "[_a-zA-Za]*\\.properties$"; | |
324 | ||
325 | for (String res : TransBundle_ResourceList.getResources(Pattern | |
326 | .compile(regex))) { | |
327 | String resource = res; | |
328 | int index = resource.lastIndexOf('/'); | |
329 | if (index >= 0 && index < (resource.length() - 1)) | |
330 | resource = resource.substring(index + 1); | |
331 | if (resource.startsWith(name.name())) { | |
332 | resource = resource.substring(0, resource.length() | |
333 | - ".properties".length()); | |
334 | resource = resource.substring(name.name().length()); | |
335 | if (resource.startsWith("_")) { | |
336 | resource = resource.substring(1); | |
337 | resources.add(resource); | |
338 | } | |
339 | } | |
340 | } | |
341 | ||
342 | return resources; | |
343 | } | |
344 | } |