joo.ResourceBundleAwareClassLoader.as Maven / Gradle / Ivy
/*
* Copyright 2009 CoreMedia AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS
* IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package joo {
/**
* A class loader that provides special treatment of resource bundle classes.
* It maintains a current locale and loads a locale-specific subclass of any
* class ending with _properties
.
* The locale is saved in and retrieved from a Cookie whose name, path, and domain
* can be configured.
* In your JavaScript code, after including the Jangaroo Runtime, activate
* locale-specific resource bundle class loading as follows:
*
* new joo.ResourceBundleAwareClassLoader(["en", "de"]);
*
* You can create resource bundle classes by creating *_locale.properties
* files below the src/main/joo
directory. These files are translated to AS3
* code by the Jangaroo properties compiler (Maven goal: properties
).
* Each resource bundle must consist of properties files for each supported locale, where
* the default locale is suppressed.
* For example for supported locales ["en", "de"]
(which makes "en"
* the default locale), you need properties files My.properties
and My_de.properties
.
* To change the locale, your application code must call
*
* joo.ResourceBundleAwareClassLoader.INSTANCE.setLocale(newLocale);
*
*
* @see #ResourceBundleAwareClassLoader
* @see #getLocale
* @see #setLocale
*
* @author Manuel Ohlendorf
* @author Frank Wienberg
*/
public class ResourceBundleAwareClassLoader extends DynamicClassLoader {
private static const DAYS_TILL_LOCALE_COOKIE_EXPIRY:int = 10*356;
/**
* The ResourceBundleAwareClassLoader singleton, which is created or overwritten
* by invoking the constructor.
* @see #ResourceBundleAwareClassLoader
*/
public static var INSTANCE:ResourceBundleAwareClassLoader;
private var supportedLocales:Array;
private var localeCookieName:String;
private var localeCookiePath:String;
/**
* Set before any resource bundle is loaded.
* @see #getLocale
*/
public var localeCookieDomain:String = null;
private var preferredLocales:Array;
private var locale:String;
/**
* Create a new ResourceBundleAwareClassLoader with the given supported locales, default locale,
* and locale Cookie name, and set it as the default Jangaroo class loader (joo.classLoader
)
* and as the singleton ResourceBundleAwareClassLoader (INSTANCE
).
* @param supportedLocales (default: ["en"]
) An Array of supported locales (String).
* The current locale is guaranteed to be an item of this list.
* The first element of this list is used as the default locale to use when all other attempts to determine
* a locale fail.
* @param localeCookieName (default: "joo.locale"
) The name of the Cookie to load and store locale
* information on the client.
* @param localeCookiePath (default: window.location.pathname
) The path of the Cookie to load and
* store locale information on the client.
* @param localeCookieDomain (default: null
) The domain of the Cookie to load and store locale
* information on the client.
* @param preferredLocales the locales preferred by the current user, in order of preference, which will be used
* to determine the best supported locale if no locale Cookie is set.
* @see joo.classLoader
* @see #INSTANCE
* @see #getLocale
*/
public function ResourceBundleAwareClassLoader(supportedLocales:Array = undefined,
localeCookieName:String = undefined,
localeCookiePath:String = undefined,
localeCookieDomain:String = undefined,
preferredLocales:Array = undefined) {
INSTANCE = this;
super();
this.preferredLocales = preferredLocales || localization.preferredLocales || [];
this.supportedLocales = supportedLocales || localization.supportedLocales || ["en"];
this.localeCookieName = localeCookieName || localization.localeCookieName || "joo.locale";
this.localeCookiePath = localeCookiePath || localization.localeCookiePath || getQualifiedObject("location.pathname");
this.localeCookieDomain = localeCookieDomain || localization.localeCookieDomain || null;
}
//noinspection JSUnusedGlobalSymbols
public function getSupportedLocales():Array {
return supportedLocales.concat();
}
public function getDefaultLocale():String {
return supportedLocales[0];
}
override protected function createClassDeclaration(packageDef:String, metadata:Object, classDef:String, inheritanceLevel:int, memberFactory:Function, publicStaticMethodNames:Array, dependencies:Array):JooClassDeclaration {
var cd : JooClassDeclaration = JooClassDeclaration(super.createClassDeclaration(packageDef, metadata, classDef, inheritanceLevel, memberFactory, publicStaticMethodNames, dependencies));
if (cd.fullClassName.match(NativeClassDeclaration.RESOURCE_BUNDLE_PATTERN)) {
cd.getDependencies().push(getLocalizedResourceClassName(cd));
}
return cd;
}
//noinspection JSUnusedGlobalSymbols
/**
* Used internally by code generated by the properties compiler.
* In case your want to implement a custom resource bundle, use the following code to
* generate a locale-specific instance of your bundle class:
*
* public static const INSTANCE:My_properties = My_properties(ResourceBundleAwareClassLoader.INSTANCE.createSingleton(My_properties));
*
* @param resourceBundle the resource bundle class for which the subclass corresponding to the current locale
* is to be instantiated.
* @return Object an instance of the resource bundle class or the subclass corresponding to the current locale
* @see #getLocale
*/
public function createSingleton(resourceBundle:Class):Object {
var cd:NativeClassDeclaration = NativeClassDeclaration(resourceBundle['$class']);
var fullLocalizedClassName:String = getLocalizedResourceClassName(cd);
var LocalizedResourceBundle:Class = getQualifiedObject(fullLocalizedClassName);
return new LocalizedResourceBundle();
}
private function readLocaleFromCookie():String {
return findSupportedLocale(getCookie(localeCookieName));
}
private static function escape(s:String):String {
return s.replace(/([.*+?^${}()|[\]\/\\])/g, "\\$1");
}
private static function getCookie(name:String):String {
var cookieKey:String = escape(name);
var document:* = getQualifiedObject("document");
var match:Array = document.cookie.match("(?:^|;)\\s*" + cookieKey + "=([^;]*)");
return match ? decodeURIComponent(match[1]) : null;
}
private static function setCookie(name:String, value:String,
path:String = null,
expires:Date = null,
domain:String = null,
secure:Boolean = false):void {
var document:* = getQualifiedObject("document");
document.cookie =
name + "=" + encodeURIComponent(value || "") +
((expires === null) ? "" : ("; expires=" + expires.toGMTString())) +
((path === null) ? "" : ("; path=" + path)) +
((domain === null) ? "" : ("; domain=" + domain)) +
(secure ? "; secure" : "");
}
private static function getLocaleCookieExpiry():Date {
var date:Date = new Date();
date.setTime(date.getTime() + (DAYS_TILL_LOCALE_COOKIE_EXPIRY * 24 * 60 * 60 * 1000));
return date;
}
private function getLocaleFromPreferredLocales():String {
for (var i:int = 0; i < preferredLocales.length; i++) {
var preferredLocale:String = findSupportedLocale(preferredLocales[i]);
if (preferredLocale) {
return preferredLocale;
}
}
return null;
}
private function readLocaleFromNavigator():String {
var navigator:* = getQualifiedObject("navigator");
if (navigator) {
var locale:String = navigator['language'] || navigator['browserLanguage']
|| navigator['systemLanguage'] || navigator['userLanguage'];
if (locale) {
return findSupportedLocale(locale.replace(/-/g, "_"));
}
}
return null;
}
/**
* Sets the current locale to the given locale or, if the given locale is not supported,
* to the longest match in the supported locales, or the default locale if there is no match.
* @param newLocale the locale to use for resource bundle class loading
* @return String the supported locale that has actually been set
* @see #ResourceBundleAwareClassLoader
*/
public function setLocale(newLocale:String):String {
locale = findSupportedLocale(newLocale);
// either create, update or remove (if locale===null) the Cookie:
setCookie(localeCookieName, locale, localeCookiePath, locale ? getLocaleCookieExpiry() : Date(0), localeCookieDomain);
return getLocale(); // use getter to re-compute fallback logic for locale==null and cache the result
}
public function findSupportedLocale(locale:String):String {
if (!locale) {
return null;
}
// find longest match of locale in supported locales
var longestMatch:String = "";
for (var i:int = 0; i < supportedLocales.length; i++) {
if (locale.indexOf(supportedLocales[i]) === 0
&& supportedLocales[i].length > longestMatch.length) {
longestMatch = supportedLocales[i];
}
}
return longestMatch ? longestMatch : null;
}
/**
* Return the locale currently used for resource bundle class loading.
* On the first call of this method, the locale is read from the Cookie given by
* localeCookieName
(default "joo.locale"), localeCookiePath
* (window.location.pathname
) and localeCookieDomain
* (null
).
* If there is no such Cookie, the browser's navigator
object is asked for its
* language. If the locale still is not determined, the defaultLocale
is used.
* This value if stored using setLocale()
.
*
* @return the locale currently used for resource bundle class loading.
*
* @see #ResourceBundleAwareClassLoader
* @see #setLocale
*/
public function getLocale():String {
if (!locale) {
locale = readLocaleFromCookie() || getLocaleFromPreferredLocales() || readLocaleFromNavigator() || getDefaultLocale();
}
return locale;
}
private function getLocalizedResourceClassName(cd:NativeClassDeclaration):String {
var localizedResourceClassName:String = cd.fullClassName;
var locale:String = getLocale();
if (locale !== getDefaultLocale()) {
localizedResourceClassName += "_" + locale;
}
return localizedResourceClassName;
}
}
}