Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: er/extensions/ERXLocalizer.java


1   //
2   // ERXLocalizer.java
3   // Project armehaut
4   //
5   // Created by ak on Sun Apr 14 2002
6   //
7   package er.extensions;
8   
9   import com.webobjects.foundation.*;
10  import com.webobjects.appserver.*;
11  import java.util.*;
12  import java.lang.reflect.Constructor;
13  
14  /** KVC access to localization.
15  Monitors a set of files in all frameworks and returns a string given a key for a language.
16  In the current state, it's more a stub for things to come.
17  
18  These types of keys are acceptable in the monitored files:
19  
20      "this is a test" = "some test";
21      "unittest.key.path.as.string" = "some test";
22      "unittest" = {"key" = { "path" = { "as" = {"dict"="some test";};};};};
23  
24  Note that if you only call for "unittest", you'll get a dictionary. So you can localize more complex objects than strings.
25  
26  If you set the base class of your session to ERXSession, you can then use this code in your components:
27  
28     valueForKeyPath("session.localizer.this is a test")
29     valueForKeyPath("session.localizer.unittest.key.path.as.string")
30     valueForKeyPath("session.localizer.unittest.key.path.as.dict")
31  
32  For sessionless Apps, you must use another method to get at the requested language and then call the localizer via
33  
34    ERXLocalizer l = ERXLocalizer.localizerForLanguages(languagesThisUserCanHandle) or
35    ERXLocalizer l = ERXLocalizer.localizerForLanguage("German")
36  
37  These defaults can be set (listed with their current defaults):
38  
39  er.extensions.ERXLocalizer.defaultLanguage=English
40  er.extensions.ERXLocalizer.fileNamesToWatch=("Localizable.strings","ValidationTemplate.strings")
41  er.extensions.ERXLocalizer.availableLanguages=(English,German)
42  er.extensions.ERXLocalizer.frameworkSearchPath=(app,ERDirectToWeb,ERExtensions)
43  
44  TODO: chaining of Localizers
45  */
46  
47  public class ERXLocalizer implements NSKeyValueCoding, NSKeyValueCodingAdditions  {
48      protected static final ERXLogger log = ERXLogger.getERXLogger(ERXLocalizer.class);
49      protected static final ERXLogger createdKeysLog = (ERXLogger)ERXLogger.getLogger(ERXLocalizer.class.getName() + ".createdKeys");
50      private static boolean isLocalizationEnabled = false;
51      private static boolean isInitialized = false;
52      
53      public static final String LocalizationDidResetNotification = "LocalizationDidReset";
54      
55      public static class Observer {
56          public void fileDidChange(NSNotification n) {
57              ERXLocalizer.resetCache();
58              NSNotificationCenter.defaultCenter().postNotification(LocalizationDidResetNotification, null);
59          }
60  
61          public void compilerProxyDidCompileClasses(NSNotification n) {
62              // ENHANCEME: Should deal with ERXLocalizer subclasses too. 
63              if (! ERXCompilerProxy.isClassContainedBySet("er.extensions.ERXLocalizer", (NSSet)n.object())) {
64                  return;
65              }
66              ERXLocalizer.resetCache();
67              NSNotificationCenter.defaultCenter().postNotification(LocalizationDidResetNotification, null);
68          }
69      }
70  
71      private static Observer observer;
72      private static NSMutableArray monitoredFiles;
73  
74      public static void initialize() {
75          if(!isInitialized) {
76              observer = new Observer();
77              monitoredFiles = new NSMutableArray();
78              isLocalizationEnabled = ERXProperties.booleanForKeyWithDefault("er.extensions.ERXLocalizer.isLocalizationEnabled", true);
79              if (isLocalizationEnabled) {
80                  // To detect ERXLocalizer and its subclasses are recompiled at run-time. 
81                  NSNotificationCenter.defaultCenter().addObserver(
82                          observer, 
83                          new NSSelector("compilerProxyDidCompileClasses", ERXConstant.NotificationClassArray), 
84                          ERXCompilerProxy.CompilerProxyDidCompileClassesNotification, 
85                          null);
86              }
87              isInitialized = true;
88          }
89      }
90      
91      public static boolean isLocalizationEnabled() { return isLocalizationEnabled; }
92      public static void setIsLocalizationEnabled(boolean value) { isLocalizationEnabled = value; }
93      
94      static NSArray fileNamesToWatch;
95      static NSArray frameworkSearchPath;
96      static NSArray availableLanguages;
97      static String defaultLanguage;
98      
99      static NSMutableDictionary localizers = new NSMutableDictionary();
100     
101     protected NSMutableDictionary cache;
102     private NSMutableDictionary createdKeys;
103     private String NOT_FOUND = "**NOT_FOUND**";
104 
105     /**
106      * Resets the localizer cache. If WOCaching is
107      * enabled then after being reinitialize all of
108      * the localizers will be reloaded.
109      */
110     public static void resetCache() {
111         initialize();
112         if (WOApplication.application().isCachingEnabled()) {
113             Enumeration e = localizers.objectEnumerator();
114             while (e.hasMoreElements()) {
115                 ((ERXLocalizer)e.nextElement()).load();
116             }
117         } else {
118             localizers = new NSMutableDictionary();
119         }
120     }
121     
122     public static ERXLocalizer localizerForLanguages(NSArray languages) {
123         if (! isLocalizationEnabled)   
124             return createLocalizerForLanguage("Nonlocalized", false);
125 
126         if (languages == null || languages.count() == 0)  return localizerForLanguage(defaultLanguage());
127         ERXLocalizer l = null;
128         Enumeration e = languages.objectEnumerator();
129         while(e.hasMoreElements()) {
130             String language = (String)e.nextElement();
131             l = (ERXLocalizer)localizers.objectForKey(language);
132             if(l != null) {
133                 return l;
134             }
135             if(availableLanguages().containsObject(language)) {
136                 return localizerForLanguage(language);
137             }
138         }
139         return localizerForLanguage((String)languages.objectAtIndex(0));
140     }
141     
142     private static NSArray _languagesWithoutPluralForm = new NSArray(new Object [] {"Japanese"});
143     
144     public static ERXLocalizer localizerForLanguage(String language) {
145         if (! isLocalizationEnabled)   
146             return createLocalizerForLanguage("Nonlocalized", false);
147             
148         ERXLocalizer l = null;
149         l = (ERXLocalizer)localizers.objectForKey(language);
150         if(l == null) {
151             if(availableLanguages().containsObject(language)) {
152                 if (_languagesWithoutPluralForm.containsObject(language))
153                     l = createLocalizerForLanguage(language, false);
154                 else
155                     l = createLocalizerForLanguage(language, true);
156             } else {
157                 l = (ERXLocalizer)localizers.objectForKey(defaultLanguage());
158                 if(l == null) {
159                     if (_languagesWithoutPluralForm.containsObject(defaultLanguage()))
160                         l = createLocalizerForLanguage(defaultLanguage(), false);
161                     else
162                         l = createLocalizerForLanguage(defaultLanguage(), true);
163                     localizers.setObjectForKey(l, defaultLanguage());
164                 }
165            }
166             localizers.setObjectForKey(l, language);
167         }
168         return l;
169     }
170 
171     /**
172      * Creates a localizer for a given language and with an
173      * indication if the language supports plural forms. To provide
174      * your own subclass of an ERXLocalizer you can set the system
175      * property <code>er.extensions.ERXLocalizer.pluralFormClassName</code>
176      * or <code>er.extensions.ERXLocalizer.nonPluralFormClassName</code>.
177      * @param language name to construct the localizer for
178      * @param pluralForm denotes if the language supports the plural form
179      * @return a localizer for the given language
180      */
181     protected static ERXLocalizer createLocalizerForLanguage(String language, boolean pluralForm) {
182         ERXLocalizer localizer = null;
183         String className = null;
184         if (pluralForm) {
185             className = ERXProperties.stringForKeyWithDefault("er.extensions.ERXLocalizer.pluralFormClassName", "er.extensions.ERXLocalizer");
186         } else {
187             className = ERXProperties.stringForKeyWithDefault("er.extensions.ERXLocalizer.nonPluralFormClassName", "er.extensions.ERXNonPluralFormLocalizer");            
188         }
189         try {
190             Class localizerClass = Class.forName(className);
191             Constructor constructor = localizerClass.getConstructor(ERXConstant.StringClassArray);
192             localizer = (ERXLocalizer)constructor.newInstance(new Object[] {language});
193         } catch (Exception e) {
194             log.error("Unable to create localizer for class name: " + className + " exception: " + e.getMessage() + " will use default classes");
195         }
196         if (localizer == null) {
197             if (pluralForm)
198                 localizer = new ERXLocalizer(language);
199             else
200                 localizer = new ERXNonPluralFormLocalizer(language);                
201         }
202         return localizer;
203     }
204     
205     public static void setLocalizerForLanguage(ERXLocalizer l, String language) {
206         localizers.setObjectForKey(l, language);
207     }
208 
209     /**
210      * Cover method that calls <code>localizedStringForKey</code>.
211      * @param key to resolve a localized varient of
212      * @return localized string for the given key
213      */
214     public Object valueForKey(String key) {
215         return localizedValueForKey(key);
216     }
217     
218     public Object valueForKeyPath(String key) {
219         Object result = localizedValueForKey(key);
220         if(result == null) {
221             int indexOfDot = key.indexOf(".");
222             if(indexOfDot > 0) {
223                 String firstComponent = key.substring(0, indexOfDot);
224                 String otherComponents = key.substring(indexOfDot+1, key.length());
225                 result = cache.objectForKey(firstComponent);
226                 if(log.isDebugEnabled())
227                     log.debug("Trying " + firstComponent + " . " + otherComponents);
228                 if(result != null) {
229                     result = NSKeyValueCodingAdditions.Utility.valueForKeyPath(result, otherComponents);
230                     if(result != null) {
231                         cache.setObjectForKey(result, key);
232                     } else {
233                         cache.setObjectForKey(NOT_FOUND, key);
234                     }
235                 }
236             }
237         }
238         return result;
239     }
240     public void takeValueForKey(Object value, String key) {
241         cache.setObjectForKey(value, key);
242     }
243     public void takeValueForKeyPath(Object value, String key) {
244         cache.setObjectForKey(value, key);
245     }
246     
247     String language;
248     public ERXLocalizer(String aLanguage) {
249         language = aLanguage;
250         cache = new NSMutableDictionary();
251         createdKeys = new NSMutableDictionary();
252         load();
253     }
254 
255     public void load() {
256         cache.removeAllObjects();
257         createdKeys.removeAllObjects();
258 
259         if (log.isDebugEnabled())
260             log.debug("Loading templates for language: " + language + " for files: "
261                       + fileNamesToWatch() + " with search path: " + frameworkSearchPath());
262         
263         NSArray languages = new NSArray(language);
264         WOResourceManager rm = WOApplication.application().resourceManager();
265         Enumeration fn = fileNamesToWatch().objectEnumerator();
266         while(fn.hasMoreElements()) {
267             String fileName = (String)fn.nextElement();
268             Enumeration fr = frameworkSearchPath().reverseObjectEnumerator();
269             while(fr.hasMoreElements()) {
270                 String framework = (String)fr.nextElement();
271                 
272                 String path = rm.pathForResourceNamed(fileName, framework, languages);
273                 if(path != null) {
274                     if(!monitoredFiles.containsObject(path)) {
275                         ERXFileNotificationCenter.defaultCenter().addObserver(observer, new NSSelector("fileDidChange", ERXConstant.NotificationClassArray), path);
276                         monitoredFiles.addObject(path);
277                     }
278                     try {
279                         framework = "app".equals(framework) ? null : framework;
280                         log.debug("Loading: " + fileName + " - " 
281                             + (framework == null ? "app" : framework) + " - " 
282                             + languages + WOApplication.application().resourceManager()
283                                                 .pathForResourceNamed(fileName, framework, languages));
284                        NSDictionary dict = (NSDictionary)ERXExtensions.readPropertyListFromFileInFramework(fileName, framework, languages);
285                         cache.addEntriesFromDictionary(dict);
286                     } catch(Exception ex) {
287                         log.warn("Exception loading: " + fileName + " - " 
288                             + (framework == null ? "app" : framework) + " - " 
289                             + languages + ":" + ex);
290                     }
291                 } else if (log.isDebugEnabled()) {
292                     log.debug("Unable to create path for resource named: " + fileName 
293                         + " framework: " + (framework == null ? "app" : framework)
294                         + " languages: " + languages);
295                 }
296             }
297         }
298     }
299 
300     // CHECKME: Don't think that we need these any more now that we can have access to
301     //          the current localizer via thread storage
302     public static NSDictionary fakeSessionForLanguage(String language) {
303         ERXLocalizer localizer = localizerForLanguage(language);
304         return new NSDictionary(new Object[] {localizer,language}, new Object[] {"localizer", "language"} );
305     }
306 
307     // CHECKME: Don't think that we need these any more now that we can have access to
308     //          the current localizer via thread storage
309     public static NSDictionary fakeSessionForSession(Object session) {
310         ERXLocalizer localizer = localizerForSession(session);
311         return new NSDictionary(new Object[] {localizer,localizer.language()}, new Object[] {"localizer", "language"} );
312     }
313 
314     /**
315      * Returns the current localizer for the current thread.
316      * Note that the localizer for a given session is pushed
317      * onto the thread when a session awakes and is nulled out
318      * when a session sleeps.
319      * @return the current localizer that has been pushed into
320      *     thread storage.
321      */
322     public static ERXLocalizer currentLocalizer() {
323         return (ERXLocalizer)ERXThreadStorage.valueForKey("localizer");
324     }
325 
326     /**
327      * Sets a localizer for the current thread. This is accomplished
328      * by using the object {@link ERXThreadStorage}
329      * @param currentLocalizer to set in thread storage for the current
330      *    thread.
331      */
332     public static void setCurrentLocalizer(ERXLocalizer currentLocalizer) {
333         ERXThreadStorage.takeValueForKey(currentLocalizer, "localizer");
334     }
335 
336     /**
337      * Gets the localizer for the default language.
338      * @return localizer for the default language.
339      */
340     public static ERXLocalizer defaultLocalizer() {
341         return localizerForLanguage(defaultLanguage());
342     }
343     
344     public static ERXLocalizer localizerForSession(Object session) {
345         if(session instanceof ERXSession) return ((ERXSession)session).localizer();
346         if(session instanceof WOSession) return localizerForLanguages(((WOSession)session).languages());
347         if(session instanceof NSDictionary) {
348             NSDictionary dict = ((NSDictionary)session);
349             Object l = dict.valueForKey("localizer");
350             if(l != null)
351                 return (ERXLocalizer)l;
352             Object language = dict.valueForKey("language");
353             if(language != null)
354                 return localizerForLanguage((String)language);
355         }
356         return localizerForLanguage(defaultLanguage());
357     }
358     
359     public String language() { return language; }
360     public NSDictionary createdKeys() { return createdKeys; }
361 
362     public Object localizedValueForKeyWithDefault(String key) {
363         Object result = localizedValueForKey(key);
364         if(result == null) {
365             if(createdKeysLog.isDebugEnabled())
366                 createdKeysLog.debug("Default key inserted: '"+key+"'/"+language);
367             cache.setObjectForKey(key, key);
368             createdKeys.setObjectForKey(key, key);
369             result = key;
370         }
371         return result;
372     }
373 
374     public Object localizedValueForKey(String key) {
375         Object result = cache.objectForKey(key);
376         if(result == NOT_FOUND) return null;
377         if(result != null) return result;
378 
379         if(createdKeysLog.isDebugEnabled())
380             log.debug("Key not found: '"+key+"'/"+language);
381         cache.setObjectForKey(NOT_FOUND, key);
382         return null;
383     }
384 
385     public String localizedStringForKeyWithDefault(String key) {
386         return (String)localizedValueForKeyWithDefault(key);
387     }
388     public String localizedStringForKey(String key) {
389         return (String)localizedValueForKey(key);
390     }
391 
392     public String localizedTemplateStringForKeyWithObject(String key, Object o1) {
393         return localizedTemplateStringForKeyWithObjectOtherObject(key, o1, null);
394     }
395 
396     public String localizedTemplateStringForKeyWithObjectOtherObject(String key, Object o1, Object o2) {
397         String template = localizedStringForKey(key);
398         if (template != null)
399             return ERXSimpleTemplateParser.sharedInstance().parseTemplateWithObject(template, null, o1, o2);
400         return key;
401     }
402 
403     private String _plurify(String s, int howMany) {
404         String result=s;
405         if (s!=null && howMany!=1) {
406             if (s.endsWith("y"))
407                 result=s.substring(0,s.length()-1)+"ies";
408             else if (s.endsWith("s") && ! s.endsWith("ss")) {
409                 // we assume it's already plural. There are a few words this will break this heuristic
410                 // e.g. gas --> gases
411                 // but otherwise for Documents we get Documentses..
412             } else if (s.endsWith("s") || s.endsWith("ch") || s.endsWith("sh") || s.endsWith("x"))
413                 result+="es";
414             else
415                 result+= "s";
416         }
417         return result;
418     }
419 
420     private String _singularify(String value) {
421         String result = value;
422         if (value!=null) {
423             if (value.endsWith("ies"))
424                 result = value.substring(0,value.length()-3)+"y";
425             else if (value.endsWith("hes"))
426                 result = value.substring(0,value.length()-2);
427             else if (!value.endsWith("ss") && (value.endsWith("s") || value.endsWith("ses")))
428                 result = value.substring(0,value.length()-1);
429         }
430         return result;
431     }
432     
433     // name is already localized!
434     // subclasses can override for more sensible behaviour
435     public String plurifiedStringWithTemplateForKey(String key, String name, int count, Object helper) {
436         NSDictionary dict = new NSDictionary( new Object[] {plurifiedString(name, count), new Integer(count)},
437                                               new Object[] {"pluralString", "pluralCount"});
438         return localizedTemplateStringForKeyWithObjectOtherObject(key, dict, helper);
439     }
440 
441     public String plurifiedString(String name, int count) {
442         return _plurify(name, count);
443     }
444     
445     public String toString() { return "<" + getClass().getName() + " " + language + ">"; }
446 
447     public static String defaultLanguage() {
448         if (defaultLanguage == null) {
449             defaultLanguage = ERXProperties.stringForKeyWithDefault("er.extensions.ERXLocalizer.defaultLanguage", "English");
450         }
451         return defaultLanguage;
452     }
453     public static void setDefaultLanguage(String value) {
454         defaultLanguage = value;
455         resetCache();
456     }
457 
458     public static NSArray fileNamesToWatch() {
459         if (fileNamesToWatch == null) {
460             fileNamesToWatch = ERXProperties.arrayForKeyWithDefault("er.extensions.ERXLocalizer.fileNamesToWatch", new NSArray(new Object [] {"Localizable.strings", "ValidationTemplate.strings"}));
461             if (log.isDebugEnabled())
462                 log.debug("FileNamesToWatch: " + fileNamesToWatch);
463         }
464         return fileNamesToWatch;
465     }
466     public static void setFileNamesToWatch(NSArray value) {
467         fileNamesToWatch = value;
468         resetCache();
469     }
470 
471     public static NSArray availableLanguages() {
472         if(availableLanguages == null) {
473             availableLanguages = ERXProperties.arrayForKeyWithDefault("er.extensions.ERXLocalizer.availableLanguages", new NSArray(new Object [] {"English", "German", "Japanese"}));
474             if (log.isDebugEnabled())
475                 log.debug("AvailableLanguages: " + availableLanguages);
476         }
477         return availableLanguages;
478     }
479     public static void setAvailableLanguages(NSArray value) {
480         availableLanguages = value;
481         resetCache();
482     }
483 
484     public static NSArray frameworkSearchPath() {
485         if (frameworkSearchPath == null) {
486             frameworkSearchPath = ERXProperties.arrayForKeyWithDefault("er.extensions.ERXLocalizer.frameworkSearchPath", new NSArray(new Object [] {"app", "ERDirectToWeb", "ERExtensions"}));
487             if (log.isDebugEnabled())
488                 log.debug("FrameworkSearchPath: " + frameworkSearchPath);
489         }
490         return frameworkSearchPath;
491     }
492     public static void setFrameworkSearchPath(NSArray value) {
493         frameworkSearchPath = value;
494         resetCache();
495     }
496 }