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