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 fr.paris.lutece.portal.service.i18n;
35
36 import fr.paris.lutece.portal.service.util.AppException;
37 import fr.paris.lutece.portal.service.util.AppLogService;
38 import fr.paris.lutece.portal.service.util.AppPathService;
39 import fr.paris.lutece.portal.service.util.AppPropertiesService;
40 import fr.paris.lutece.util.ReferenceList;
41
42 import java.io.File;
43
44 import java.net.MalformedURLException;
45 import java.net.URL;
46 import java.net.URLClassLoader;
47
48 import java.text.DateFormat;
49 import java.text.MessageFormat;
50
51 import java.util.ArrayList;
52 import java.util.Collection;
53 import java.util.Collections;
54 import java.util.Date;
55 import java.util.Enumeration;
56 import java.util.HashMap;
57 import java.util.List;
58 import java.util.Locale;
59 import java.util.Map;
60 import java.util.MissingResourceException;
61 import java.util.ResourceBundle;
62 import java.util.StringTokenizer;
63 import java.util.regex.Matcher;
64 import java.util.regex.Pattern;
65
66
67
68
69
70
71 public final class I18nService
72 {
73 private static final String FORMAT_PACKAGE_PORTAL_RESOURCES_LOCATION = "fr.paris.lutece.portal.resources.{0}_messages";
74 private static final String FORMAT_PACKAGE_PLUGIN_RESOURCES_LOCATION = "fr.paris.lutece.plugins.{0}.resources.{0}_messages";
75 private static final String FORMAT_PACKAGE_MODULE_RESOURCES_LOCATION = "fr.paris.lutece.plugins.{0}.modules.{1}.resources.{1}_messages";
76 private static final Pattern PATTERN_LOCALIZED_KEY = Pattern.compile( "#i18n\\{(.*?)\\}" );
77 private static final String PROPERTY_AVAILABLES_LOCALES = "lutece.i18n.availableLocales";
78 private static final Locale LOCALE_DEFAULT = new Locale( "", "", "" );
79 private static final String PROPERTY_DEFAULT_LOCALE = "lutece.i18n.defaultLocale";
80 private static final String PROPERTY_FORMAT_DATE_SHORT_LIST = "lutece.format.date.short";
81 private static Map<String, String> _pluginBundleNames = Collections.synchronizedMap( new HashMap<String, String>( ) );
82 private static Map<String, String> _moduleBundleNames = Collections.synchronizedMap( new HashMap<String, String>( ) );
83 private static Map<String, String> _portalBundleNames = Collections.synchronizedMap( new HashMap<String, String>( ) );
84 private static final String PROPERTY_PATH_OVERRIDE = "path.i18n.override";
85 private static final ClassLoader _overrideLoader;
86 private static final Map<String, ResourceBundle> _resourceBundleCache = Collections.synchronizedMap( new HashMap<String, ResourceBundle>( ) );
87
88 static
89 {
90 File overridePath = null;
91
92 try
93 {
94 overridePath = new File( AppPathService.getPath( PROPERTY_PATH_OVERRIDE ) );
95 }
96 catch( AppException e )
97 {
98
99 AppLogService.error( "property {} is undefined. Message overriding will be disabled.", PROPERTY_PATH_OVERRIDE );
100 }
101
102 URL [ ] overrideURL = null;
103
104 if ( overridePath != null )
105 {
106 try
107 {
108 overrideURL = new URL [ ] {
109 overridePath.toURI( ).toURL( )
110 };
111 }
112 catch( MalformedURLException e )
113 {
114 AppLogService.error( "Error initializing message overriding: {}", e.getMessage( ), e );
115 }
116 }
117
118 if ( overrideURL != null )
119 {
120 _overrideLoader = new URLClassLoader( overrideURL, null );
121 }
122 else
123 {
124 _overrideLoader = null;
125 }
126 }
127
128
129
130
131 private I18nService( )
132 {
133 }
134
135
136
137
138
139
140
141
142
143
144
145
146 public static String localize( String strSource, Locale locale )
147 {
148 String result = strSource;
149
150 if ( strSource != null )
151 {
152 Matcher matcher = PATTERN_LOCALIZED_KEY.matcher( strSource );
153
154 if ( matcher.find( ) )
155 {
156 StringBuffer sb = new StringBuffer( );
157
158 do
159 {
160 matcher.appendReplacement( sb, getLocalizedString( matcher.group( 1 ), locale ) );
161 }
162 while ( matcher.find( ) );
163
164 matcher.appendTail( sb );
165 result = sb.toString( );
166 }
167 }
168
169 return result;
170 }
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189 public static String getLocalizedString( String strKey, Locale theLocale )
190 {
191 Locale locale = theLocale;
192 String strReturn = "";
193
194 try
195 {
196 int nPos = strKey.indexOf( '.' );
197
198 if ( nPos != -1 )
199 {
200 String strBundleKey = strKey.substring( 0, nPos );
201 String strStringKey = strKey.substring( nPos + 1 );
202
203 String strBundle;
204
205 if ( !strBundleKey.equals( "portal" ) )
206 {
207 if ( strBundleKey.equals( "module" ) )
208 {
209
210 nPos = strStringKey.indexOf( '.' );
211
212 String strPlugin = strStringKey.substring( 0, nPos );
213 strStringKey = strStringKey.substring( nPos + 1 );
214 nPos = strStringKey.indexOf( '.' );
215
216 String strModule = strStringKey.substring( 0, nPos );
217 strStringKey = strStringKey.substring( nPos + 1 );
218
219 strBundle = getModuleBundleName( strPlugin, strModule );
220 }
221 else
222 {
223
224 strBundle = getPluginBundleName( strBundleKey );
225 }
226 }
227 else
228 {
229 nPos = strStringKey.indexOf( '.' );
230
231 String strElement = strStringKey.substring( 0, nPos );
232 strStringKey = strStringKey.substring( nPos + 1 );
233
234 strBundle = getPortalBundleName( strElement );
235 }
236
237
238
239 if ( locale.getLanguage( ).equals( Locale.ENGLISH.getLanguage( ) ) )
240 {
241 locale = LOCALE_DEFAULT;
242 }
243
244 ResourceBundle rbLabels = getResourceBundle( locale, strBundle );
245 strReturn = rbLabels.getString( strStringKey );
246 }
247 }
248 catch( Exception e )
249 {
250 String strErrorMessage = "Error localizing key : '" + strKey + "' - " + e.getMessage( );
251
252 if ( e.getCause( ) != null )
253 {
254 strErrorMessage += ( " - cause : " + e.getCause( ).getMessage( ) );
255 }
256
257 AppLogService.error( strErrorMessage );
258 }
259
260 return strReturn;
261 }
262
263
264
265
266
267
268
269
270 private static String getPluginBundleName( String strBundleKey )
271 {
272 return _pluginBundleNames.computeIfAbsent( strBundleKey, s -> new MessageFormat( FORMAT_PACKAGE_PLUGIN_RESOURCES_LOCATION ).format( new String [ ] {
273 s
274 } ) );
275 }
276
277
278
279
280
281
282
283
284
285
286 private static String getModuleBundleName( String strPlugin, String strModule )
287 {
288 String key = strPlugin + strModule;
289 return _moduleBundleNames.computeIfAbsent( key, s -> new MessageFormat( FORMAT_PACKAGE_MODULE_RESOURCES_LOCATION ).format( new String [ ] {
290 strPlugin, strModule
291 } ) );
292 }
293
294
295
296
297
298
299
300
301 private static String getPortalBundleName( String strElement )
302 {
303 return _portalBundleNames.computeIfAbsent( strElement, s -> new MessageFormat( FORMAT_PACKAGE_PORTAL_RESOURCES_LOCATION ).format( new String [ ] {
304 s
305 } ) );
306 }
307
308
309
310
311
312
313
314
315
316
317
318
319 public static String getLocalizedString( String strKey, Object [ ] arguments, Locale locale )
320 {
321 String strMessagePattern = getLocalizedString( strKey, locale );
322
323 return MessageFormat.format( strMessagePattern, arguments );
324 }
325
326
327
328
329
330
331
332
333
334
335
336
337 public static String getLocalizedDate( Date date, Locale locale, int nDateFormat )
338 {
339 DateFormat dateFormatter = DateFormat.getDateInstance( nDateFormat, locale );
340 return dateFormatter.format( date );
341 }
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356 public static String getLocalizedDateTime( Date date, Locale locale, int nDateFormat, int nTimeFormat )
357 {
358 DateFormat dateFormatter = DateFormat.getDateTimeInstance( nDateFormat, nTimeFormat, locale );
359 return dateFormatter.format( date );
360 }
361
362
363
364
365
366
367 public static List<Locale> getAdminAvailableLocales( )
368 {
369 String strAvailableLocales = AppPropertiesService.getProperty( PROPERTY_AVAILABLES_LOCALES );
370 StringTokenizer strTokens = new StringTokenizer( strAvailableLocales, "," );
371 List<Locale> list = new ArrayList<>( );
372
373 while ( strTokens.hasMoreTokens( ) )
374 {
375 String strLanguage = strTokens.nextToken( );
376 Locale locale = new Locale( strLanguage );
377 list.add( locale );
378 }
379
380 return list;
381 }
382
383
384
385
386
387
388 public static Locale getDefaultLocale( )
389 {
390 String strDefaultLocale = AppPropertiesService.getProperty( PROPERTY_DEFAULT_LOCALE );
391
392 return new Locale( strDefaultLocale );
393 }
394
395
396
397
398
399
400
401
402 public static String getDateFormatShortPattern( Locale locale )
403 {
404 String strAvailableLocales = AppPropertiesService.getProperty( PROPERTY_FORMAT_DATE_SHORT_LIST );
405
406 if ( ( locale != null ) && ( strAvailableLocales != null ) && !strAvailableLocales.equals( "" ) )
407 {
408 StringTokenizer strTokens = new StringTokenizer( strAvailableLocales, "," );
409 String strToken = null;
410
411 for ( Locale adminLocale : getAdminAvailableLocales( ) )
412 {
413 if ( strTokens.hasMoreTokens( ) )
414 {
415 strToken = strTokens.nextToken( );
416 }
417
418 if ( adminLocale.getLanguage( ).equals( locale.getLanguage( ) ) )
419 {
420 return strToken;
421 }
422 }
423 }
424
425 return null;
426 }
427
428
429
430
431
432
433
434
435 public static ReferenceList getAdminLocales( Locale locale )
436 {
437 ReferenceListnceList.html#ReferenceList">ReferenceList list = new ReferenceList( );
438
439 for ( Locale l : getAdminAvailableLocales( ) )
440 {
441 list.addItem( l.getLanguage( ), l.getDisplayLanguage( l ) );
442 }
443
444 return list;
445 }
446
447
448
449
450
451
452
453
454
455
456
457
458
459 public static <E extends Localizable> Collection<E> localizeCollection( Collection<E> collection, Locale locale )
460 {
461 for ( Localizable object : collection )
462 {
463 object.setLocale( locale );
464 }
465
466 return collection;
467 }
468
469
470
471
472
473
474
475
476
477
478
479
480
481 public static <E extends Localizable> List<E> localizeCollection( List<E> list, Locale locale )
482 {
483 for ( Localizable object : list )
484 {
485 object.setLocale( locale );
486 }
487
488 return list;
489 }
490
491
492
493
494
495
496
497
498
499
500 private static ResourceBundle getResourceBundle( Locale locale, String strBundle )
501 {
502 String key = strBundle + locale.toString( );
503 return _resourceBundleCache.computeIfAbsent( key, k -> createResourceBundle( strBundle, locale ) );
504 }
505
506 private static ResourceBundle createResourceBundle( String strBundle, Locale locale )
507 {
508 ResourceBundle rbLabels = ResourceBundle.getBundle( strBundle, locale );
509
510 if ( _overrideLoader != null )
511 {
512 ResourceBundle overrideBundle = null;
513
514 try
515 {
516 overrideBundle = ResourceBundle.getBundle( strBundle, locale, _overrideLoader );
517 }
518 catch( MissingResourceException e )
519 {
520
521 return rbLabels;
522 }
523
524 return new CombinedResourceBundle( overrideBundle, rbLabels );
525 }
526 return rbLabels;
527 }
528
529
530
531
532
533
534 public static void resetCache( )
535 {
536 ResourceBundle.clearCache( );
537
538 if ( _overrideLoader != null )
539 {
540 ResourceBundle.clearCache( _overrideLoader );
541 }
542
543 _resourceBundleCache.clear( );
544 }
545
546
547
548
549
550
551
552 public static Enumeration<String> getPluginBundleKeys( String strPluginName )
553 {
554 ResourceBundle rb = getResourceBundle( getDefaultLocale(), getPluginBundleName( strPluginName ) );
555
556 return rb.getKeys( );
557 }
558 }