1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.services.i18n;
20
21 import java.io.File;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.text.MessageFormat;
27 import java.util.Enumeration;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.LinkedList;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34 import java.util.MissingResourceException;
35 import java.util.Properties;
36 import java.util.ResourceBundle;
37 import java.util.ResourceBundle.Control;
38 import java.util.Set;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41 import java.util.stream.Collectors;
42
43 import javax.xml.parsers.DocumentBuilder;
44
45 import org.apache.logging.log4j.LogManager;
46 import org.apache.logging.log4j.Logger;
47 import org.mycore.common.MCRSessionMgr;
48 import org.mycore.common.config.MCRConfiguration2;
49 import org.mycore.common.config.MCRConfigurationDir;
50 import org.mycore.common.config.MCRProperties;
51 import org.mycore.common.xml.MCRDOMUtils;
52 import org.w3c.dom.Document;
53 import org.w3c.dom.Element;
54
55
56
57
58
59
60
61
62 public class MCRTranslation {
63
64 private static final String MESSAGES_BUNDLE = "messages";
65
66 private static final String DEPRECATED_MESSAGES_PROPERTIES = "/deprecated-messages.properties";
67
68 private static final Logger LOGGER = LogManager.getLogger(MCRTranslation.class);
69
70 private static final Pattern ARRAY_DETECTOR = Pattern.compile(";");
71
72 private static final Control CONTROL = new MCRCombinedResourceBundleControl();
73
74 private static boolean DEPRECATED_MESSAGES_PRESENT = false;
75
76 private static Properties DEPRECATED_MAPPING = loadProperties();
77
78 private static Set<String> AVAILABLE_LANGUAGES = loadAvailableLanguages();
79
80 static {
81 debug();
82 }
83
84
85
86
87
88
89
90
91 public static String translate(String label) {
92 Locale currentLocale = getCurrentLocale();
93 return translate(label, currentLocale);
94 }
95
96
97
98
99
100
101
102 public static boolean exists(String label) {
103 try {
104 ResourceBundle message = getResourceBundle(MESSAGES_BUNDLE, getCurrentLocale());
105 message.getString(label);
106 } catch (MissingResourceException mre) {
107 LOGGER.debug(mre);
108 return false;
109 }
110 return true;
111 }
112
113
114
115
116
117
118
119
120
121
122
123 public static String translateWithBaseName(String label, String baseName) {
124 Locale currentLocale = getCurrentLocale();
125 return translate(label, currentLocale, baseName);
126 }
127
128
129
130
131
132
133
134
135
136 public static String translate(String label, Locale locale) {
137 return translate(label, locale, MESSAGES_BUNDLE);
138 }
139
140
141
142
143
144
145
146
147
148
149
150 public static String translate(String label, Locale locale, String baseName) {
151 LOGGER.debug("Translation for current locale: {}", locale.getLanguage());
152 ResourceBundle message;
153 try {
154 message = getResourceBundle(baseName, locale);
155 } catch (MissingResourceException mre) {
156
157 LOGGER.debug(mre.getMessage());
158 return "???" + label + "???";
159 }
160 String result = null;
161 try {
162 result = message.getString(label);
163 LOGGER.debug("Translation for {}={}", label, result);
164 } catch (MissingResourceException mre) {
165
166 if (!DEPRECATED_MESSAGES_PRESENT) {
167 LOGGER.warn("Could not load resource '" + DEPRECATED_MESSAGES_PROPERTIES
168 + "' to check for depreacted I18N keys.");
169 } else if (DEPRECATED_MAPPING.containsKey(label)) {
170 String newLabel = DEPRECATED_MAPPING.getProperty(label);
171 try {
172 result = message.getString(newLabel);
173 } catch (java.util.MissingResourceException e) {
174 }
175 if (result != null) {
176 LOGGER.warn("Usage of deprected I18N key '{}'. Please use '{}' instead.", label, newLabel);
177 return result;
178 }
179 }
180 result = "???" + label + "???";
181 LOGGER.debug(mre.getMessage());
182 }
183 return result;
184 }
185
186
187
188
189
190
191
192
193
194 public static Map<String, String> translatePrefix(String prefix) {
195 Locale currentLocale = getCurrentLocale();
196 return translatePrefix(prefix, currentLocale);
197 }
198
199
200
201
202
203
204
205
206
207
208 public static Map<String, String> translatePrefix(String prefix, Locale locale) {
209 LOGGER.debug("Translation for locale: {}", locale.getLanguage());
210 HashMap<String, String> map = new HashMap<>();
211 ResourceBundle message = getResourceBundle(MESSAGES_BUNDLE, locale);
212 Enumeration<String> keys = message.getKeys();
213 while (keys.hasMoreElements()) {
214 String key = keys.nextElement();
215 if (key.startsWith(prefix)) {
216 map.put(key, message.getString(key));
217 }
218 }
219 return map;
220 }
221
222
223
224
225
226
227
228
229
230
231 public static String translate(String label, Object... arguments) {
232 Locale currentLocale = getCurrentLocale();
233 String msgFormat = translate(label);
234 MessageFormat formatter = new MessageFormat(msgFormat, currentLocale);
235 String result = formatter.format(arguments);
236 LOGGER.debug("Translation for {}={}", label, result);
237 return result;
238 }
239
240
241
242
243
244
245
246
247
248
249
250
251
252 public static String translate(String label, String argument) {
253 return translate(label, (Object[]) getStringArray(argument));
254 }
255
256 public static Locale getCurrentLocale() {
257 String currentLanguage = MCRSessionMgr.getCurrentSession().getCurrentLanguage();
258 return getLocale(currentLanguage);
259 }
260
261 public static Locale getLocale(String language) {
262 if (language.equals("id")) {
263
264
265
266 language = "in";
267 LOGGER.debug("Translation for current locale: {}", language);
268 }
269 return new Locale(language);
270 }
271
272 public static Set<String> getAvailableLanguages() {
273 return AVAILABLE_LANGUAGES;
274 }
275
276 public static Document getAvailableLanguagesAsXML() {
277 DocumentBuilder documentBuilder = MCRDOMUtils.getDocumentBuilderUnchecked();
278 try {
279 Document document = documentBuilder.newDocument();
280 Element i18nRoot = document.createElement("i18n");
281 document.appendChild(i18nRoot);
282 for (String lang : AVAILABLE_LANGUAGES) {
283 Element langElement = document.createElement("lang");
284 langElement.setTextContent(lang);
285 i18nRoot.appendChild(langElement);
286 }
287 return document;
288 } finally {
289 MCRDOMUtils.releaseDocumentBuilder(documentBuilder);
290 }
291 }
292
293 static String[] getStringArray(String masked) {
294 List<String> a = new LinkedList<>();
295 boolean mask = false;
296 StringBuilder buf = new StringBuilder();
297 if (masked == null) {
298 return new String[0];
299 }
300 if (!isArray(masked)) {
301 a.add(masked);
302 } else {
303 for (int i = 0; i < masked.length(); i++) {
304 switch (masked.charAt(i)) {
305 case ';':
306 if (mask) {
307 buf.append(';');
308 mask = false;
309 } else {
310 a.add(buf.toString());
311 buf.setLength(0);
312 }
313 break;
314 case '\\':
315 if (mask) {
316 buf.append('\\');
317 mask = false;
318 } else {
319 mask = true;
320 }
321 break;
322 default:
323 buf.append(masked.charAt(i));
324 break;
325 }
326 }
327 a.add(buf.toString());
328 }
329 return a.toArray(String[]::new);
330 }
331
332 static boolean isArray(String masked) {
333 Matcher m = ARRAY_DETECTOR.matcher(masked);
334 while (m.find()) {
335 int pos = m.start();
336 int count = 0;
337 for (int i = pos - 1; i > 0; i--) {
338 if (masked.charAt(i) == '\\') {
339 count++;
340 } else {
341 break;
342 }
343 }
344 if (count % 2 == 0) {
345 return true;
346 }
347 }
348 return false;
349 }
350
351 static Properties loadProperties() {
352 Properties deprecatedMapping = new Properties();
353 try {
354 final InputStream propertiesStream = MCRTranslation.class
355 .getResourceAsStream(DEPRECATED_MESSAGES_PROPERTIES);
356 if (propertiesStream == null) {
357 LOGGER.warn("Could not find resource '" + DEPRECATED_MESSAGES_PROPERTIES + "'.");
358 return deprecatedMapping;
359 }
360 deprecatedMapping.load(propertiesStream);
361 DEPRECATED_MESSAGES_PRESENT = true;
362 } catch (IOException e) {
363 LOGGER.warn("Could not load resource '" + DEPRECATED_MESSAGES_PROPERTIES + "'.", e);
364 }
365 return deprecatedMapping;
366 }
367
368 static Set<String> loadAvailableLanguages() {
369
370 return MCRConfiguration2.getString("MCR.Metadata.Languages")
371 .map(MCRConfiguration2::splitValue)
372 .map(s -> s.collect(Collectors.toSet()))
373 .orElseGet(() -> loadLanguagesByMessagesBundle());
374 }
375
376 static Set<String> loadLanguagesByMessagesBundle() {
377 Set<String> languages = new HashSet<>();
378 for (Locale locale : Locale.getAvailableLocales()) {
379 try {
380 if (!locale.getLanguage().equals("")) {
381 ResourceBundle bundle = getResourceBundle(MESSAGES_BUNDLE, locale);
382 languages.add(bundle.getLocale().toString());
383 }
384 } catch (MissingResourceException e) {
385 LOGGER.debug("Could not load " + MESSAGES_BUNDLE + " for locale: {}", locale);
386 }
387 }
388 return languages;
389 }
390
391 public static ResourceBundle getResourceBundle(String baseName, Locale locale) {
392 return baseName.contains(".") ? ResourceBundle.getBundle(baseName, locale)
393 : ResourceBundle.getBundle("stacked:" + baseName, locale, CONTROL);
394 }
395
396
397
398
399 private static void debug() {
400 for (String lang : MCRTranslation.getAvailableLanguages()) {
401 ResourceBundle rb = MCRTranslation.getResourceBundle("messages", MCRTranslation.getLocale(lang));
402 Properties props = new MCRProperties();
403 rb.keySet().forEach(key -> props.put(key, rb.getString(key)));
404 File resolvedMsgFile = MCRConfigurationDir.getConfigFile("messages_" + lang + ".resolved.properties");
405 if (resolvedMsgFile != null) {
406 try (OutputStream os = new FileOutputStream(resolvedMsgFile)) {
407 props.store(os, "MyCoRe Messages for Locale " + lang);
408 } catch (IOException e) {
409 LOGGER.warn("Could not store resolved properties to {}", resolvedMsgFile.getAbsolutePath(), e);
410 }
411 }
412 }
413 }
414 }