fix changelog headers
[fanfix.git] / 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 }
133
134 return result;
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() {
163 return getKnownLanguages(keyType);
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);
176 setBundle(keyType, locale, false);
177 }
178
179 @Override
180 public void reload(boolean resetToDefault) {
181 setBundle(keyType, locale, resetToDefault);
182 }
183
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();
204 Object status = takeSnapshot();
205
206 // default locale
207 setLanguage(null);
208 if (prev.equals(getLocaleFor(null).getLanguage())) {
209 // restore snapshot if default locale = current locale
210 restoreSnapshot(status);
211 }
212 super.updateFile(path);
213
214 for (String lang : getKnownLanguages()) {
215 setLanguage(lang);
216 if (lang.equals(prev)) {
217 restoreSnapshot(status);
218 }
219 super.updateFile(path);
220 }
221
222 setLanguage(prev);
223 restoreSnapshot(status);
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) {
231 file = new File(path, keyType.name() + "_" + code + ".properties");
232 } else {
233 // Default properties file:
234 file = new File(path, keyType.name() + ".properties");
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 }