1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 or later
5    * as published by the Free Software Foundation. This program is distributed
6    * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7    * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *      http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * © CrossWire Bible Society, 2005 - 2016
18   *
19   */
20  package org.crosswire.common.util;
21  
22  import java.io.IOException;
23  import java.net.URI;
24  import java.net.URL;
25  import java.util.Locale;
26  import java.util.Map;
27  
28  import org.crosswire.common.config.ChoiceFactory;
29  import org.slf4j.LoggerFactory;
30  
31  /**
32   * Translations provides a list of locales that BibleDesktop has been translated
33   * into.
34   * 
35   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
36   * @author DM Smith
37   */
38  public final class Translations {
39      /**
40       * Singleton classes have private constructors.
41       */
42      private Translations() {
43          try {
44              loadSupportedTranslations();
45              PropertyMap props = ResourceUtil.getProperties(getClass());
46              translation = props.get(TRANSLATION_KEY);
47              if (translation == null || translation.length() == 0) {
48                  // check for a match against language and country
49                  // This pertains to zh_TW and zh_CN
50                  for (int i = 0; i < translations.length; i++) {
51                      Locale supportedLocale = new Locale(translations[i]);
52                      if (supportedLocale.getLanguage().equals(originalLocale.getLanguage()) && supportedLocale.getCountry().equals(originalLocale.getCountry())) {
53                          translation = translations[i];
54                          return;
55                      }
56                  }
57  
58                  // check for a match against just language
59                  for (int i = 0; i < translations.length; i++) {
60                      Locale supportedLocale = new Locale(translations[i]);
61                      if (supportedLocale.getLanguage().equals(originalLocale.getLanguage())) {
62                          translation = translations[i];
63                          return;
64                      }
65                  }
66  
67                  // if we don't have a matching locale then just use the default.
68                  translation = DEFAULT_TRANSLATION;
69              }
70          } catch (IOException e) {
71              translation = DEFAULT_TRANSLATION;
72          }
73      }
74  
75      /**
76       * All access to Translations is through this single instance.
77       * 
78       * @return the singleton instance
79       */
80      public static Translations instance() {
81          return instance;
82      }
83  
84      /**
85       * Gets a listing of all the translations that Bible Desktop supports.
86       * 
87       * @return an string array of translations in locale friendly names.
88       */
89      public PropertyMap getSupported() {
90          loadSupportedTranslations();
91  
92          // I18N(DMS) Collate these according to the current locale, putting the
93          // current locale's locale first.
94          PropertyMap names = new PropertyMap();
95  
96          for (int i = 0; i < translations.length; i++) {
97              names.put(translations[i], toString(translations[i]));
98          }
99  
100         return names;
101     }
102 
103     /**
104      * Get the locale for the current translation.
105      * 
106      * @return the translation's locale
107      */
108     public Locale getCurrentLocale() {
109         // If there is no particular translation, then return the default
110         // locale.
111         if (translation == null || DEFAULT_TRANSLATION.equals(translation)) {
112             return DEFAULT_LOCALE;
113         }
114 
115         // If the local consists of a language and a country then use both
116         if (translation.indexOf('_') != -1) {
117             String[] locale = StringUtil.split(translation, '_');
118             return new Locale(locale[0], locale[1]);
119         }
120 
121         // otherwise just use the country.
122         return new Locale(translation);
123     }
124 
125     /**
126      * Get the current translation as a human readable string.
127      * 
128      * @return the current translation
129      */
130     public String getCurrent() {
131         return toString(translation);
132     }
133 
134     /**
135      * Set the current translation, using human readable string.
136      * 
137      * @param newTranslation
138      *            the translation to use
139      */
140     public void setCurrent(String newTranslation) {
141         String found = DEFAULT_TRANSLATION;
142         String currentTranslation = "";
143         for (int i = 0; i < translations.length; i++) {
144             String trans = translations[i];
145             currentTranslation = toString(translation);
146 
147             if (trans.equals(newTranslation) || currentTranslation.equals(newTranslation)) {
148                 found = trans;
149                 break;
150             }
151         }
152 
153         try {
154             translation = found;
155             PropertyMap props = new PropertyMap();
156             if (!DEFAULT_TRANSLATION.equals(translation)) {
157                 props.put(TRANSLATION_KEY, translation);
158             }
159 
160             URI outputURI = CWProject.instance().getWritableURI(getClass().getName(), FileUtil.EXTENSION_PROPERTIES);
161             NetUtil.storeProperties(props, outputURI, "BibleDesktop UI Translation");
162         } catch (IOException ex) {
163             log.error("Failed to save BibleDesktop UI Translation", ex);
164         }
165     }
166 
167     /**
168      * Set the locale for the program to the one the user has selected. But
169      * don't set it to the default translation, so that the user's actual
170      * locale, is used for Bible book names.
171      * 
172      * This only makes sense after config has called setCurrentTranslation.
173      */
174     public void setLocale() {
175         Locale.setDefault(getCurrentLocale());
176     }
177 
178     /**
179      * Register this class with the common config engine.
180      */
181     public void register() {
182         ChoiceFactory.getDataMap().put(TRANSLATION_KEY, getSupportedTranslations());
183     }
184 
185     /**
186      * Get the current translation as a human readable string.
187      * 
188      * @return the current translation
189      */
190     public static String getCurrentTranslation() {
191         return Translations.instance().getCurrent();
192     }
193 
194     /**
195      * Set the current translation, using human readable string.
196      * 
197      * @param newTranslation
198      *            the translation to use
199      */
200     public static void setCurrentTranslation(String newTranslation) {
201         Translations.instance().setCurrent(newTranslation);
202     }
203 
204     /**
205      * Gets a listing of all the translations that Bible Desktop supports.
206      * 
207      * @return an string array of translations in locale friendly names.
208      */
209     public static Map<String, String> getSupportedTranslations() {
210         return Translations.instance().getSupported();
211     }
212 
213     /**
214      * Get a list of the supported translations
215      */
216     private void loadSupportedTranslations() {
217         if (translations == null) {
218             try {
219                 URL index = ResourceUtil.getResource(Translations.class, "translations.txt");
220                 translations = NetUtil.listByIndexFile(NetUtil.toURI(index));
221             } catch (IOException ex) {
222                 translations = new String[0];
223             }
224         }
225     }
226 
227     public String toString(String translationCode) {
228         StringBuilder currentTranslation = new StringBuilder(Languages.getName(translationCode));
229 
230         if (translationCode.indexOf('_') != -1) {
231             String[] locale = StringUtil.split(translationCode, '_');
232             currentTranslation.append(", ");
233             currentTranslation.append(Countries.getCountry(locale[1]));
234         }
235 
236         return currentTranslation.toString();
237     }
238 
239     /**
240      * The key used in config.xml
241      */
242     private static final String TRANSLATION_KEY = "translation-codes";
243 
244     /**
245      * The default translation, if the user has not chosen anything else.
246      */
247     public static final String DEFAULT_TRANSLATION = "en";
248 
249     /**
250      * The default Locale, it the user has not chosen anything else.
251      */
252     public static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
253 
254     /**
255      * The translation that BibleDesktop should use.
256      */
257     private String translation;
258 
259     /**
260      * List of available translations.
261      */
262     private String[] translations;
263 
264     /**
265      * The locale that the program starts with. This needs to precede
266      * "instance."
267      */
268     private static Locale originalLocale = Locale.getDefault();
269 
270     private static Translations instance = new Translations();
271 
272     /**
273      * The log stream
274      */
275     private static final org.slf4j.Logger log = LoggerFactory.getLogger(Translations.class);
276 }
277