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