1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.i18nsystem;
35
36 import info.magnolia.cms.i18n.MessagesManager;
37 import info.magnolia.event.EventBus;
38 import info.magnolia.event.SystemEventBus;
39 import info.magnolia.i18nsystem.module.I18nModule;
40 import info.magnolia.objectfactory.ComponentProvider;
41 import info.magnolia.objectfactory.Components;
42 import info.magnolia.resourceloader.ResourceOrigin;
43
44 import java.util.Arrays;
45 import java.util.Locale;
46 import java.util.Properties;
47
48 import javax.inject.Inject;
49 import javax.inject.Named;
50 import javax.inject.Provider;
51 import javax.inject.Singleton;
52
53 import org.apache.commons.lang3.StringUtils;
54 import org.jsoup.Jsoup;
55 import org.jsoup.nodes.Document;
56 import org.jsoup.safety.Cleaner;
57 import org.jsoup.safety.Whitelist;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61
62
63
64
65
66
67
68
69
70 @Singleton
71 public class TranslationServiceImpl implements TranslationService {
72 private static final Logger log = LoggerFactory.getLogger(TranslationServiceImpl.class);
73 private static final Cleaner CLEANER = new Cleaner(Whitelist.basic());
74
75 private final Provider<I18nModule> i18nModuleProvider;
76 private final Provider<DefaultMessageBundlesLoader> defaultMessageBundlesLoaderProvider;
77
78 @Inject
79 public TranslationServiceImpl(final Provider<I18nModule> i18nModuleProvider, final Provider<DefaultMessageBundlesLoader> defaultMessageBundlesLoaderProvider) {
80 this.i18nModuleProvider = i18nModuleProvider;
81 this.defaultMessageBundlesLoaderProvider = defaultMessageBundlesLoaderProvider;
82 }
83
84
85
86
87 @Deprecated
88 public TranslationServiceImpl(final Provider<I18nModule> i18nModuleProvider, final ComponentProvider componentProvider, final ResourceOrigin resourceOrigin, @Named(SystemEventBus.NAME) EventBus systemEventBus) {
89 this(i18nModuleProvider, new Provider<DefaultMessageBundlesLoader>() {
90 @Override
91 public DefaultMessageBundlesLoader get() {
92 return Components.getComponent(DefaultMessageBundlesLoader.class);
93 }
94 });
95 }
96
97
98
99
100 @Deprecated
101 public TranslationServiceImpl(Provider<I18nModule> i18nModuleProvider) {
102 this(i18nModuleProvider, new Provider<DefaultMessageBundlesLoader>() {
103 @Override
104 public DefaultMessageBundlesLoader get() {
105 return Components.getComponent(DefaultMessageBundlesLoader.class);
106 }
107 });
108 }
109
110
111
112
113 @Deprecated
114 public TranslationServiceImpl() {
115 this(new Provider<I18nModule>() {
116 @Override
117 public I18nModule get() {
118 return Components.getComponent(I18nModule.class);
119 }
120 }, new Provider<DefaultMessageBundlesLoader>() {
121 @Override
122 public DefaultMessageBundlesLoader get() {
123 return Components.getComponent(DefaultMessageBundlesLoader.class);
124 }
125 });
126 }
127
128
129
130
131 @Deprecated
132 protected DefaultMessageBundlesLoader setupMessageBundles() {
133 return defaultMessageBundlesLoaderProvider.get();
134 }
135
136 @Override
137 public String translate(LocaleProvider localeProvider, String[] keys) {
138 return translate(localeProvider, null, keys);
139 }
140
141 @Override
142 public String translate(LocaleProvider localeProvider, String basename, String[] keys) {
143 final Locale locale = localeProvider.getLocale();
144 if (locale == null) {
145 throw new IllegalArgumentException("Locale can't be null");
146 }
147 if (keys == null || keys.length < 1) {
148 throw new IllegalArgumentException("Keys can't be null or empty");
149 }
150
151 if (basename != null) {
152 log.debug("Got an explicit basename ({}) for keys {}", basename, Arrays.asList(keys));
153 }
154
155 final String message = lookUpKeyUntilFound(keys, locale, basename);
156 if (message != null) {
157 return message;
158 } else {
159 return handleUnknownKey(locale, basename, keys);
160 }
161 }
162
163 private String lookUpKeyUntilFound(final String[] keys, final Locale locale, final String basename) {
164 String message = null;
165
166 if (StringUtils.isNotBlank(basename)) {
167 log.debug("Looking up key [{}] with basename [{}] and Locale [{}] - legacy method", keys[0], basename, locale);
168 message = legacyLookup(locale, basename, keys[0]);
169 if (message != null) {
170 return message + (isDebug() ? this.addDebugInfo(keys, keys[0], locale, basename) : StringUtils.EMPTY);
171 }
172 }
173
174 if (message == null) {
175 log.debug("Looking up in global i18n message bundle with key [{}] and Locale [{}]", Arrays.asList(keys), locale);
176 message = doGetMessage(keys, locale);
177 }
178
179 if (message == null) {
180 final String country = locale.getCountry();
181 if (country != null) {
182 final Locale newLocale = new Locale(locale.getLanguage(), country);
183 message = doGetMessage(keys, newLocale);
184 }
185 }
186
187 if (message == null) {
188 final Locale newLocale = new Locale(locale.getLanguage());
189 message = doGetMessage(keys, newLocale);
190 }
191
192 if (message == null) {
193 message = doGetMessage(keys, getFallbackLocale());
194 }
195
196 if (message == null) {
197 message = handleUnknownKey(locale, basename, keys);
198 }
199
200 return message;
201 }
202
203 private String addDebugInfo(final String[] keys, String currentlyUsedKey, final Locale locale, final String basename) {
204 return "\n" + Arrays.asList(keys).toString()
205 .replaceFirst("(?s)" + currentlyUsedKey + "(?!.*?" + currentlyUsedKey + ")", ">" + currentlyUsedKey + "<")
206 + locale.getClass().getSimpleName() + ":" + locale + (basename == null ? StringUtils.EMPTY : ",Using legacy i18n basename:" + basename);
207 }
208
209 protected String handleUnknownKey(Locale locale, String basename, String[] keys) {
210
211 log.debug("No translation found for any of {} with locale {} and basename {}", keys, locale, basename != null ? basename : "<unspecified>");
212 return keys[0] + (isDebug() ? this.addDebugInfo(keys, null, locale, basename) : StringUtils.EMPTY);
213 }
214
215
216
217
218 private String legacyLookup(Locale locale, String basename, String key) {
219
220 String message = MessagesManager.getMessages(basename, locale).get(key);
221 if (legacyMessageNotFound(message)) {
222 message = MessagesManager.getMessages(MessagesManager.DEFAULT_BASENAME, locale).get(key);
223 }
224 if (legacyMessageNotFound(message)) {
225
226 return null;
227 } else {
228 Document document = Jsoup.parseBodyFragment(message, "");
229 if (!CLEANER.isValid(document)) {
230 return CLEANER.clean(document).body().html();
231 } else {
232 return message;
233 }
234 }
235 }
236
237 private boolean legacyMessageNotFound(final String message) {
238 return message == null || message.startsWith("???");
239 }
240
241 private String doGetMessage(String[] keys, Locale locale) {
242 final Properties properties = defaultMessageBundlesLoaderProvider.get().getMessages().get(locale);
243 if (properties != null) {
244 for (String key : keys) {
245 if (key == null) {
246
247 continue;
248 }
249 final String message = properties.getProperty(key);
250 if (message != null) {
251 return message + (isDebug() ? this.addDebugInfo(keys, key, locale, null) : StringUtils.EMPTY);
252 }
253 }
254 }
255 return null;
256 }
257
258 private boolean isDebug() {
259 try {
260 return i18nModuleProvider.get() != null && i18nModuleProvider.get().isDebug();
261 } catch (RuntimeException exception) {
262
263
264
265 return false;
266 }
267 }
268
269
270 private Locale getFallbackLocale() {
271 return MessagesManager.getInstance().getDefaultLocale();
272 }
273
274
275
276
277 @Deprecated
278 @Override
279 public void reloadMessageBundles() {
280 log.warn("Not reloading message bundles. Please make sure to update [{}] instead.", DefaultMessageBundlesLoader.class.getName());
281 }
282
283 }