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[] keys, String fallback) {
143 return translate(localeProvider.get(), null, keys, fallback);
144 }
145
146 @Override
147 public String translate(LocaleProvider localeProvider, String basename, String[] keys) {
148 final Locale locale = localeProvider.getLocale();
149 return translate(locale, basename, keys, I18nText.NO_FALLBACK);
150 }
151
152 private String translate(Locale locale, String basename, String[] keys, String fallback) {
153 if (locale == null) {
154 throw new IllegalArgumentException("Locale can't be null");
155 }
156 if (keys == null || keys.length < 1) {
157 throw new IllegalArgumentException("Keys can't be null or empty");
158 }
159
160 if (basename != null) {
161 log.debug("Got an explicit basename ({}) for keys {}", basename, Arrays.asList(keys));
162 }
163
164 final String message = lookUpKeyUntilFound(keys, locale, basename);
165 if (message != null) {
166 return message;
167 } else {
168 return I18nText.NO_FALLBACK.equals(fallback) ? handleUnknownKey(locale, basename, keys) : fallback;
169 }
170 }
171
172 private String lookUpKeyUntilFound(final String[] keys, final Locale locale, final String basename) {
173 String message = null;
174
175 if (StringUtils.isNotBlank(basename)) {
176 log.debug("Looking up key [{}] with basename [{}] and Locale [{}] - legacy method", keys[0], basename, locale);
177 message = legacyLookup(locale, basename, keys[0]);
178 if (message != null) {
179 return message + (isDebug() ? this.addDebugInfo(keys, keys[0], locale, basename) : StringUtils.EMPTY);
180 }
181 }
182
183 if (message == null) {
184 log.debug("Looking up in global i18n message bundle with key [{}] and Locale [{}]", Arrays.asList(keys), locale);
185 message = doGetMessage(keys, locale);
186 }
187
188 if (message == null) {
189 final String country = locale.getCountry();
190 if (country != null) {
191 final Locale newLocale = new Locale(locale.getLanguage(), country);
192 message = doGetMessage(keys, newLocale);
193 }
194 }
195
196 if (message == null) {
197 final Locale newLocale = new Locale(locale.getLanguage());
198 message = doGetMessage(keys, newLocale);
199 }
200
201 if (message == null) {
202 message = doGetMessage(keys, getFallbackLocale());
203 }
204
205 return message;
206 }
207
208 private String addDebugInfo(final String[] keys, String currentlyUsedKey, final Locale locale, final String basename) {
209 return "\n" + Arrays.asList(keys).toString()
210 .replaceFirst("(?s)" + currentlyUsedKey + "(?!.*?" + currentlyUsedKey + ")", ">" + currentlyUsedKey + "<")
211 + locale.getClass().getSimpleName() + ":" + locale + (basename == null ? StringUtils.EMPTY : ",Using legacy i18n basename:" + basename);
212 }
213
214 protected String handleUnknownKey(Locale locale, String basename, String[] keys) {
215
216 log.debug("No translation found for any of {} with locale {} and basename {}", keys, locale, basename != null ? basename : "<unspecified>");
217 return keys[0] + (isDebug() ? this.addDebugInfo(keys, null, locale, basename) : StringUtils.EMPTY);
218 }
219
220
221
222
223 private String legacyLookup(Locale locale, String basename, String key) {
224
225 String message = MessagesManager.getMessages(basename, locale).get(key);
226 if (legacyMessageNotFound(message)) {
227 message = MessagesManager.getMessages(MessagesManager.DEFAULT_BASENAME, locale).get(key);
228 }
229 if (legacyMessageNotFound(message)) {
230
231 return null;
232 } else {
233 Document document = Jsoup.parseBodyFragment(message, "");
234 if (!CLEANER.isValid(document)) {
235 return CLEANER.clean(document).body().html();
236 } else {
237 return message;
238 }
239 }
240 }
241
242 private boolean legacyMessageNotFound(final String message) {
243 return message == null || message.startsWith("???");
244 }
245
246 private String doGetMessage(String[] keys, Locale locale) {
247 final Properties properties = defaultMessageBundlesLoaderProvider.get().getMessages().get(locale);
248 if (properties != null) {
249 for (String key : keys) {
250 if (key == null) {
251
252 continue;
253 }
254 final String message = properties.getProperty(key);
255 if (message != null) {
256 return message + (isDebug() ? this.addDebugInfo(keys, key, locale, null) : StringUtils.EMPTY);
257 }
258 }
259 }
260 return null;
261 }
262
263 private boolean isDebug() {
264 try {
265 return i18nModuleProvider.get() != null && i18nModuleProvider.get().isDebug();
266 } catch (RuntimeException exception) {
267
268
269
270 return false;
271 }
272 }
273
274
275 private Locale getFallbackLocale() {
276 return MessagesManager.getInstance().getDefaultLocale();
277 }
278
279
280
281
282 @Deprecated
283 @Override
284 public void reloadMessageBundles() {
285 log.warn("Not reloading message bundles. Please make sure to update [{}] instead.", DefaultMessageBundlesLoader.class.getName());
286 }
287
288 }