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.module.ModulesStartedEvent;
41 import info.magnolia.objectfactory.ComponentProvider;
42 import info.magnolia.objectfactory.Components;
43 import info.magnolia.resourceloader.Resource;
44 import info.magnolia.resourceloader.ResourceOrigin;
45 import info.magnolia.resourceloader.util.PredicatedResourceVisitor;
46 import info.magnolia.resourceloader.util.VoidFunction;
47
48 import java.util.Arrays;
49 import java.util.Locale;
50 import java.util.Properties;
51
52 import javax.inject.Inject;
53 import javax.inject.Named;
54 import javax.inject.Provider;
55 import javax.inject.Singleton;
56
57 import org.apache.commons.lang3.StringUtils;
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
74 private final ResourceOrigin resourceOrigin;
75 private DefaultMessageBundlesLoader messageBundles;
76
77 private final Provider<I18nModule> i18nModuleProvider;
78 private final ComponentProvider componentProvider;
79
80 @Inject
81 public TranslationServiceImpl(final Provider<I18nModule> i18nModuleProvider,final ComponentProvider componentProvider, final ResourceOrigin resourceOrigin, @Named(SystemEventBus.NAME) EventBus systemEventBus) {
82 this.i18nModuleProvider = i18nModuleProvider;
83 this.componentProvider = componentProvider;
84 this.resourceOrigin = resourceOrigin;
85
86 systemEventBus.addHandler(ModulesStartedEvent.class, new ModulesStartedEvent.Handler() {
87 @Override
88 public void onModuleStartupCompleted(ModulesStartedEvent event) {
89 messageBundles = setupMessageBundles();
90 log.info("Starting monitoring of {} to load translation files", resourceOrigin);
91 resourceOrigin.watchForChanges(PredicatedResourceVisitor.with(DefaultMessageBundlesLoader.DIRECTORY_PREDICATE, DefaultMessageBundlesLoader.RESOURCE_PREDICATE, new VoidFunction<Resource>() {
92 @Override
93 public void doWith(Resource input) {
94 reloadMessageBundles();
95 }
96 }));
97 }
98 });
99 }
100
101
102
103
104 @Deprecated
105 public TranslationServiceImpl(Provider<I18nModule> i18nModuleProvider) {
106 this(i18nModuleProvider, Components.getComponentProvider(), Components.getComponent(ResourceOrigin.class), Components.getComponentWithAnnotation(EventBus.class, Components.named(SystemEventBus.NAME)));
107 }
108
109
110
111
112 @Deprecated
113 public TranslationServiceImpl() {
114 this(new Provider<I18nModule>() {
115 @Override
116 public I18nModule get() {
117 return Components.getComponent(I18nModule.class);
118 }
119 }, Components.getComponentProvider(), Components.getComponent(ResourceOrigin.class), Components.getComponentWithAnnotation(EventBus.class, Components.named(SystemEventBus.NAME)));
120 }
121
122 protected DefaultMessageBundlesLoader setupMessageBundles() {
123 return componentProvider.newInstance(DefaultMessageBundlesLoader.class, resourceOrigin);
124 }
125
126 @Override
127 public String translate(LocaleProvider localeProvider, String[] keys) {
128 return translate(localeProvider, null, keys);
129 }
130
131 @Override
132 public String translate(LocaleProvider localeProvider, String basename, String[] keys) {
133 final Locale locale = localeProvider.getLocale();
134 if (locale == null) {
135 throw new IllegalArgumentException("Locale can't be null");
136 }
137 if (keys == null || keys.length < 1) {
138 throw new IllegalArgumentException("Keys can't be null or empty");
139 }
140
141 if (basename != null) {
142 log.debug("Got an explicit basename ({}) for keys {}", basename, Arrays.asList(keys));
143 }
144
145 final String message = lookUpKeyUntilFound(keys, locale, basename);
146 if (message != null) {
147 return message;
148 } else {
149 return handleUnknownKey(locale, basename, keys);
150 }
151 }
152
153 private String lookUpKeyUntilFound(final String[] keys, final Locale locale, final String basename) {
154 String message = null;
155
156 if (StringUtils.isNotBlank(basename)) {
157 log.debug("Looking up key [{}] with basename [{}] and Locale [{}] - legacy method", keys[0], basename, locale);
158 message = legacyLookup(locale, basename, keys[0]);
159 if (message != null) {
160 return message + (i18nModuleProvider.get().isDebug() ? this.addDebugInfo(keys, keys[0], locale, basename) : StringUtils.EMPTY);
161 }
162 }
163
164 if (message == null) {
165 log.debug("Looking up in global i18n message bundle with key [{}] and Locale [{}]", Arrays.asList(keys), locale);
166 message = doGetMessage(keys, locale);
167 }
168
169 if (message == null) {
170 final String country = locale.getCountry();
171 if (country != null) {
172 final Locale newLocale = new Locale(locale.getLanguage(), country);
173 message = doGetMessage(keys, newLocale);
174 }
175 }
176
177 if (message == null) {
178 final Locale newLocale = new Locale(locale.getLanguage());
179 message = doGetMessage(keys, newLocale);
180 }
181
182 if (message == null) {
183 message = doGetMessage(keys, getFallbackLocale());
184 }
185
186 if (message == null) {
187 message = handleUnknownKey(locale, basename, keys);
188 }
189
190 return message;
191 }
192
193 private String addDebugInfo(final String[] keys, String currentlyUsedKey, final Locale locale, final String basename) {
194 return "\n" + Arrays.asList(keys).toString()
195 .replaceFirst("(?s)" + currentlyUsedKey + "(?!.*?" + currentlyUsedKey + ")", ">" + currentlyUsedKey + "<")
196 + locale.getClass().getSimpleName() + ":" + locale + (basename == null ? StringUtils.EMPTY : ",Using legacy i18n basename:" + basename);
197 }
198
199 protected String handleUnknownKey(Locale locale, String basename, String[] keys) {
200
201 log.debug("No translation found for any of {} with locale {} and basename {}", keys, locale, basename != null ? basename : "<unspecified>");
202 return keys[0] + (i18nModuleProvider.get().isDebug() ? this.addDebugInfo(keys, null, locale, basename) : StringUtils.EMPTY);
203 }
204
205
206
207
208 private String legacyLookup(Locale locale, String basename, String key) {
209
210 String message = MessagesManager.getMessages(basename, locale).get(key);
211 if (legacyMessageNotFound(message)) {
212 message = MessagesManager.getMessages(MessagesManager.DEFAULT_BASENAME, locale).get(key);
213 }
214 if (legacyMessageNotFound(message)) {
215
216 return null;
217 } else {
218 return message;
219 }
220 }
221
222 private boolean legacyMessageNotFound(final String message) {
223 return message == null || message.startsWith("???");
224 }
225
226 private String doGetMessage(String[] keys, Locale locale) {
227 final Properties properties = messageBundles != null && messageBundles.getMessages() != null ? messageBundles.getMessages().get(locale) : null;
228 if (properties != null) {
229 for (String key : keys) {
230 final String message = properties.getProperty(key);
231 if (message != null) {
232 return message + (i18nModuleProvider.get().isDebug() ? this.addDebugInfo(keys, key, locale, null) : StringUtils.EMPTY);
233 }
234 }
235 }
236 return null;
237 }
238
239
240 private Locale getFallbackLocale() {
241 return MessagesManager.getInstance().getDefaultLocale();
242 }
243
244 @Override
245 public void reloadMessageBundles() {
246 log.info("Reloading message bundles");
247 this.messageBundles = setupMessageBundles();
248 }
249
250 }