1 package be
.nikiroo
.utils
.resources
;
4 import java
.io
.IOException
;
6 import java
.util
.LinkedList
;
8 import java
.util
.Locale
;
9 import java
.util
.regex
.Pattern
;
12 * This class manages a translation-dedicated Bundle.
14 * Two special cases are handled for the used enum:
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>
21 * the enum to use to get values out of this class
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;
31 * Create a translation service with the default language.
34 * a runtime instance of the class of E
36 * the name of the {@link Bundles}
38 public TransBundle(Class
<E
> type
, Enum
<?
> name
) {
39 this(type
, name
, (Locale
) null);
43 * Create a translation service for the given language (will fall back to
44 * the default one i not found).
47 * a runtime instance of the class of E
49 * the name of the {@link Bundles}
51 * the language to use, can be NULL for default
53 public TransBundle(Class
<E
> type
, Enum
<?
> name
, String language
) {
54 super(type
, name
, null);
59 * Create a translation service for the given language (will fall back to
60 * the default one i not found).
63 * a runtime instance of the class of E
65 * the name of the {@link Bundles}
67 * the language to use, can be NULL for default
69 public TransBundle(Class
<E
> type
, Enum
<?
> name
, Locale language
) {
70 super(type
, name
, null);
75 * Translate the given id into user text.
80 * the values to insert instead of the place holders in the
83 * @return the translated text with the given value where required or NULL
84 * if not found (not present in the resource file)
86 public String
getString(E stringId
, Object
... values
) {
87 return getStringX(stringId
, "", values
);
91 * Translate the given id into user text.
96 * the values to insert instead of the place holders in the
99 * @return the translated text with the given value where required or NULL
100 * if not found (not present in the resource file)
102 public String
getStringNOUTF(E stringId
, Object
... values
) {
103 return getStringX(stringId
, "NOUTF", values
);
107 * Translate the given id suffixed with the runtime value "_suffix" (that
108 * is, "_" and suffix) into user text.
111 * the ID to translate
113 * the values to insert instead of the place holders in the
118 * @return the translated text with the given value where required or NULL
119 * if not found (not present in the resource file)
121 public String
getStringX(E stringId
, String suffix
, Object
... values
) {
125 String key
= id
.name()
126 + ((suffix
== null || suffix
.isEmpty()) ?
"" : "_"
127 + suffix
.toUpperCase());
130 if (containsKey(key
+ "_NOUTF")) {
135 if ("NULL".equals(id
.name().toUpperCase())) {
137 } else if ("DUMMY".equals(id
.name().toUpperCase())) {
138 result
= "[" + key
.toLowerCase() + "]";
139 } else if (containsKey(key
)) {
140 result
= getString(key
, null);
141 if (result
== null) {
142 result
= getMetaDef(id
.name());
148 if (values
!= null && values
.length
> 0 && result
!= null) {
149 return String
.format(locale
, result
, values
);
156 * Check if unicode characters should be used.
158 * @return TRUE to allow unicode
160 public boolean isUnicode() {
165 * Allow or disallow unicode characters in the program.
168 * TRUE to allow unuciode, FALSE to only allow ASCII characters
170 public void setUnicode(boolean utf
) {
175 * Return all the languages known by the program for this bundle.
177 * @return the known language codes
179 public List
<String
> getKnownLanguages() {
180 return getKnownLanguages(keyType
);
184 * The current language (which can be the default one, but NOT NULL).
186 * @return the language, not NULL
188 public Locale
getLocale() {
193 * The current language (which can be the default one, but NOT NULL).
195 * @return the language, not NULL, in a display format (fr-BE, en-GB, es,
198 public String
getLocaleString() {
199 String lang
= locale
.getLanguage();
200 String country
= locale
.getCountry();
201 if (country
!= null && !country
.isEmpty()) {
202 return lang
+ "-" + country
;
208 * Initialise the translation mappings for the given language.
211 * the language to initialise, in the form "en-GB" or "fr" for
214 private void setLocale(String language
) {
215 setLocale(getLocaleFor(language
));
219 * Initialise the translation mappings for the given language.
222 * the language to initialise, or NULL for default
224 private void setLocale(Locale language
) {
225 if (language
!= null) {
226 defaultLocale
= false;
229 defaultLocale
= true;
230 locale
= Locale
.getDefault();
233 setBundle(keyType
, locale
, false);
237 public void reload(boolean resetToDefault
) {
238 setBundle(keyType
, locale
, resetToDefault
);
242 public String
getString(E id
) {
243 return getString(id
, (Object
[]) null);
247 * Create/update the .properties files for each supported language and for
248 * the default language.
250 * Note: this method is <b>NOT</b> thread-safe.
253 * the path where the .properties files are
255 * @throws IOException
256 * in case of IO errors
259 public void updateFile(String path
) throws IOException
{
260 String prev
= locale
.getLanguage();
261 Object status
= takeSnapshot();
264 setLocale((Locale
) null);
265 if (prev
.equals(Locale
.getDefault().getLanguage())) {
266 // restore snapshot if default locale = current locale
267 restoreSnapshot(status
);
269 super.updateFile(path
);
271 for (String lang
: getKnownLanguages()) {
273 if (lang
.equals(prev
)) {
274 restoreSnapshot(status
);
276 super.updateFile(path
);
280 restoreSnapshot(status
);
284 protected File
getUpdateFile(String path
) {
285 String code
= locale
.toString();
287 if (!defaultLocale
&& code
.length() > 0) {
288 file
= new File(path
, keyType
.name() + "_" + code
+ ".properties");
290 // Default properties file:
291 file
= new File(path
, keyType
.name() + ".properties");
298 protected void writeHeader(Writer writer
) throws IOException
{
299 String code
= locale
.toString();
300 String name
= locale
.getDisplayCountry(locale
);
302 if (name
.length() == 0) {
303 name
= locale
.getDisplayLanguage(locale
);
306 if (name
.length() == 0) {
310 if (code
.length() > 0) {
311 name
= name
+ " (" + code
+ ")";
314 name
= (name
+ " " + getBundleDisplayName()).trim();
316 writer
.write("# " + name
+ " translation file (UTF-8)\n");
317 writer
.write("# \n");
318 writer
.write("# Note that any key can be doubled with a _NOUTF suffix\n");
319 writer
.write("# to use when the NOUTF env variable is set to 1\n");
320 writer
.write("# \n");
321 writer
.write("# Also, the comments always refer to the key below them.\n");
322 writer
.write("# \n");
326 protected void writeValue(Writer writer
, E id
) throws IOException
{
327 super.writeValue(writer
, id
);
329 String name
= id
.name() + "_NOUTF";
330 if (containsKey(name
)) {
331 String value
= getString(name
, null);
333 value
= getMetaDef(id
.name());
335 boolean set
= isSet(id
, false);
336 writeValue(writer
, name
, value
, set
);
341 * Return the {@link Locale} representing the given language.
344 * the language to initialise, in the form "en-GB" or "fr" for
347 * @return the corresponding {@link Locale} or NULL if it is not known
349 static private Locale
getLocaleFor(String language
) {
352 if (language
== null || language
.trim().isEmpty()) {
356 language
= language
.replaceAll("_", "-");
357 String lang
= language
;
358 String country
= null;
359 if (language
.contains("-")) {
360 lang
= language
.split("-")[0];
361 country
= language
.split("-")[1];
365 locale
= new Locale(lang
, country
);
367 locale
= new Locale(lang
);
373 * Return all the languages known by the program.
376 * the enumeration on which we translate
378 * @return the known language codes
380 static protected List
<String
> getKnownLanguages(Enum
<?
> name
) {
381 List
<String
> resources
= new LinkedList
<String
>();
383 String regex
= ".*" + name
.name() + "[_a-zA-Za]*\\.properties$";
385 for (String res
: TransBundle_ResourceList
.getResources(Pattern
387 String resource
= res
;
388 int index
= resource
.lastIndexOf('/');
389 if (index
>= 0 && index
< (resource
.length() - 1))
390 resource
= resource
.substring(index
+ 1);
391 if (resource
.startsWith(name
.name())) {
392 resource
= resource
.substring(0, resource
.length()
393 - ".properties".length());
394 resource
= resource
.substring(name
.name().length());
395 if (resource
.startsWith("_")) {
396 resource
= resource
.substring(1);
397 resources
.add(resource
);