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