Update to version 1.5.0 (breaking Bundle/Meta)
[nikiroo-utils.git] / src / be / nikiroo / utils / resources / TransBundle.java
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
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 * @param <E>
21 * the enum to use to get values out of this class
22 *
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) {
39 super(type, name, null);
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) {
55 super(type, name, null);
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
130 if (values != null && values.length > 0 && result != null)
131 return String.format(locale, result, values);
132 else
133 return result;
134 }
135
136 /**
137 * Check if unicode characters should be used.
138 *
139 * @return TRUE to allow unicode
140 */
141 public boolean isUnicode() {
142 return utf;
143 }
144
145 /**
146 * Allow or disallow unicode characters in the program.
147 *
148 * @param utf
149 * TRUE to allow unuciode, FALSE to only allow ASCII characters
150 */
151 public void setUnicode(boolean utf) {
152 this.utf = utf;
153 }
154
155 /**
156 * Return all the languages known by the program.
157 *
158 *
159 * @return the known language codes
160 */
161 public List<String> getKnownLanguages() {
162 return getKnownLanguages(keyType);
163 }
164
165 /**
166 * Initialise the translation mappings for the given language.
167 *
168 * @param language
169 * the language to initialise, in the form "en-GB" or "fr" for
170 * instance
171 */
172 private void setLanguage(String language) {
173 defaultLocale = (language == null || language.length() == 0);
174 locale = getLocaleFor(language);
175 setBundle(keyType, locale, false);
176 }
177
178 @Override
179 public void reload(boolean resetToDefault) {
180 setBundle(keyType, locale, resetToDefault);
181 }
182
183 @Override
184 public String getString(E id) {
185 return getString(id, (Object[]) null);
186 }
187
188 /**
189 * Create/update the .properties files for each supported language and for
190 * the default language.
191 * <p>
192 * Note: this method is <b>NOT</b> thread-safe.
193 *
194 * @param path
195 * the path where the .properties files are
196 *
197 * @throws IOException
198 * in case of IO errors
199 */
200 @Override
201 public void updateFile(String path) throws IOException {
202 String prev = locale.getLanguage();
203 Object status = takeSnapshot();
204
205 // default locale
206 setLanguage(null);
207 if (prev.equals(getLocaleFor(null).getLanguage())) {
208 // restore snapshot if default locale = current locale
209 restoreSnapshot(status);
210 }
211 super.updateFile(path);
212
213 for (String lang : getKnownLanguages()) {
214 setLanguage(lang);
215 if (lang.equals(prev)) {
216 restoreSnapshot(status);
217 }
218 super.updateFile(path);
219 }
220
221 setLanguage(prev);
222 restoreSnapshot(status);
223 }
224
225 @Override
226 protected File getUpdateFile(String path) {
227 String code = locale.toString();
228 File file = null;
229 if (!defaultLocale && code.length() > 0) {
230 file = new File(path, keyType.name() + "_" + code + ".properties");
231 } else {
232 // Default properties file:
233 file = new File(path, keyType.name() + ".properties");
234 }
235
236 return file;
237 }
238
239 @Override
240 protected void writeHeader(Writer writer) throws IOException {
241 String code = locale.toString();
242 String name = locale.getDisplayCountry(locale);
243
244 if (name.length() == 0) {
245 name = locale.getDisplayLanguage(locale);
246 }
247
248 if (name.length() == 0) {
249 name = "default";
250 }
251
252 if (code.length() > 0) {
253 name = name + " (" + code + ")";
254 }
255
256 name = (name + " " + getBundleDisplayName()).trim();
257
258 writer.write("# " + name + " translation file (UTF-8)\n");
259 writer.write("# \n");
260 writer.write("# Note that any key can be doubled with a _NOUTF suffix\n");
261 writer.write("# to use when the NOUTF env variable is set to 1\n");
262 writer.write("# \n");
263 writer.write("# Also, the comments always refer to the key below them.\n");
264 writer.write("# \n");
265 }
266
267 @Override
268 protected void writeValue(Writer writer, E id) throws IOException {
269 super.writeValue(writer, id);
270
271 String name = id.name() + "_NOUTF";
272 if (containsKey(name)) {
273 String value = getString(name);
274 writeValue(writer, name, value);
275 }
276 }
277
278 /**
279 * Return the {@link Locale} representing the given language.
280 *
281 * @param language
282 * the language to initialise, in the form "en-GB" or "fr" for
283 * instance
284 *
285 * @return the corresponding {@link Locale} or the default {@link Locale} if
286 * it is not known
287 */
288 static private Locale getLocaleFor(String language) {
289 Locale locale;
290
291 if (language == null) {
292 locale = Locale.getDefault();
293 } else {
294 language = language.replaceAll("_", "-");
295 String lang = language;
296 String country = null;
297 if (language.contains("-")) {
298 lang = language.split("-")[0];
299 country = language.split("-")[1];
300 }
301
302 if (country != null)
303 locale = new Locale(lang, country);
304 else
305 locale = new Locale(lang);
306 }
307
308 return locale;
309 }
310
311 /**
312 * Return all the languages known by the program.
313 *
314 * @param name
315 * the enumeration on which we translate
316 *
317 * @return the known language codes
318 */
319 static protected List<String> getKnownLanguages(Enum<?> name) {
320 List<String> resources = new LinkedList<String>();
321
322 String regex = ".*" + name.name() + "[_a-zA-Za]*\\.properties$";
323
324 for (String res : TransBundle_ResourceList.getResources(Pattern
325 .compile(regex))) {
326 String resource = res;
327 int index = resource.lastIndexOf('/');
328 if (index >= 0 && index < (resource.length() - 1))
329 resource = resource.substring(index + 1);
330 if (resource.startsWith(name.name())) {
331 resource = resource.substring(0, resource.length()
332 - ".properties".length());
333 resource = resource.substring(name.name().length());
334 if (resource.startsWith("_")) {
335 resource = resource.substring(1);
336 resources.add(resource);
337 }
338 }
339 }
340
341 return resources;
342 }
343 }