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