GridLinked to RUX » i18n http://www.gridlinked.info Patterns with Flex, RUX, and software Fri, 03 Sep 2010 13:32:43 +0000 http://wordpress.org/?v=abc en hourly 1 Swiz, BabelFx, and Logginghttp://www.gridlinked.info/swiz-localization-l10n-logging/ http://www.gridlinked.info/swiz-localization-l10n-logging/#comments Sat, 05 Jun 2010 21:22:59 +0000 thomasb http://www.gridlinked.info/?p=788 For over 5 years, I have been a Cairngorm proponent and evangelist. During the last 6 months I have discovered a new framework passion called “Swiz.” Inversion of control, metadata processors, dependency injection… these are just some of the amazing features available in the Swiz framework.

With the release of Swiz 1.0rc1, several developers asked questions regarding the best way to use the BabelFx Framework (aka l10nInjection) to add multi-language features to Swiz RIA applications. So here is the modified version of the Swiz Flex 4 CafeTownsend application… now with extra features for Logging and BabelFx.

In fact, this application now uses Swiz Logging with the ThunderBolt AS3 classes to log output to the Firebug console. That is very cool!

Using BabelFx, the Swiz-version of CafeTownsend demo now supports both Spanish and English locales. And when you look at the code, you will see that none of the view code has been changed… yet click on the map buttons and the locale changes magically work. If you run the application from in debug mode, the trace console will show a full log output of the application activity.

Add-in the BabelFx framework with custom Swiz Logging and… wow! you will be amazed. With Swiz and “inversion of control”, the code for my own applications has become so clean and robust. Can’t you tell that I love Swiz?

Multi-language features using the Flex BabelFx localization framework!

The CafeTownsend application leverages Swiz v1.0rc1 and the [Inject] metadata tag functionality to inject models into controllers and views. Now do you recall that the BabelFx library also listens for “creationComplete” of GUI instances and injects localized resources into those target instances?

Did you know, however, that the BabelFx can also inject into data models and controllers? With Swiz it is almost trivial to accomplish that. Simply add the BabelFx LocalizationMap to the beanProvider list and then let Swiz Inject model instance references into your custom LocalizationMap. Below is a live demo of the Swiz, multi-language CafeTownsend application; with attached source code. Simply right-click to browse or download the source.

CafeTownsend (Swiz w/ l10n)
Figure 1: Swiz CafeTownsend (with l10n)

In order to add l10n features to the FlexStore, I needed to perform three (3) tasks in order to add multi-language features to the CafeTownsend application:

  1. Include the BabelFx.swc
  2. Compile and embed the resourceBundles inside the deployed swf.
  3. Create an BabelFx LocalizationMap to specify which resources are to be injected into which GUI and model targets (see source below)
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
<?xml version="1.0" encoding="utf-8"?>
<l10n:LocaleMap 	enableLog="true"
					xmlns:mx="http://www.adobe.com/2006/mxml" 
					xmlns:l10n="library://ns.mindspace.com/l10n/flex/" >
 
<mx:Metadata>
	<!-- References to bundles so embedding process works; 
		 if runtime loaded, then disable this section 
	-->
	[ResourceBundle("login")]
	[ResourceBundle("employees")]
	[ResourceBundle("language")]
</mx:Metadata>
 
<mx:Script>
<![CDATA[
	import com.cafetownsend.domain.Employee;
	import com.cafetownsend.model.AppModel;
	import com.cafetownsend.model.Constants;
	import com.cafetownsend.presentation.LoginPresentationModel;
	import com.cafetownsend.view.EmployeeDetail;
	import com.cafetownsend.view.EmployeeDetailNavigation;
	import com.cafetownsend.view.EmployeeListNavigation;
	import com.cafetownsend.view.LoginView;
	import com.cafetownsend.view.MainView;
 
	import l10n.views.LanguageBar;
 
	import mx.containers.Form;
	import mx.resources.IResourceManager;
	import mx.resources.ResourceManager;
 
	// ********************************************************************************
	// Public Mutators and Properties (injected by Swiz)
	// ********************************************************************************
 
	/**
	 * To support l10n injection into non-displayObjects (e.g. data models)
	 * the LocaleMap must have instance references. So we use Swiz to inject
	 * a reference to each model bean instance that will be used or modified
	 * when locales change.
	 * 
	 */			
	[Bindable]
	[Inject("employeeModel.selectedEmployee")]
	public function get selectedEmployee():Employee {
		return _selectedEmployee;
	}
	public function set selectedEmployee(value:Employee):void {
		_selectedEmployee = value;
		Constants.EMPLOYEE_CONFIRM_DELETE = buildConfirmMessage();
	}
 
	[Bindable]
	[Inject]    
	public var authenticated : AppModel = null;
 
	[Inject]
	public var model         : LoginPresentationModel = null;
 
 
	// ********************************************************************************
	// Private Methods
	// ********************************************************************************
 
	/**
	 * Manual injections into static variables stored in the Constants class. 
	 * 
	 */
	private function onLocaleChanged(event:Event):void {
	 var sri     : SmartResourceInjector = event.target as SmartResourceInjector;
	 var cntr	: LoginView 			= sri.targetInstances[0] as LoginView;
 
	 if(cntr != null) {
 
	 /**
	 * BabelFx or l10nInjection supports injection into both GUI and non-GUI instances (data models, 
	 * controllers, etc). But static variables cannot be directly modifed with injection.
	 * This is manual process; shown below.
	 */
	 Constants.LOGIN_FAILED_MESSAGE   	= _rMngr.getString("login","login.error.noMatch");
	 Constants.LOGIN_FAILED_TITLE     	= _rMngr.getString("login","login.error.title");
	 Constants.LOGIN_INVALID_USERNAME 	= _rMngr.getString("login","login.error.usernameRequired");
	 Constants.LOGIN_INVALID_PASSWORD 	= _rMngr.getString("login","login.error.passwordRequired");
 
	 Constants.EMPLOYEE_LOAD_ERROR 		= _rMngr.getString("employees","users.unableToLoad");
	 Constants.EMPLOYEE_INVALID_FIRSTNAME= _rMngr.getString("employees","editor.invalid.firstName");
	 Constants.EMPLOYEE_INVALID_LASTNAME= _rMngr.getString("employees","editor.invalid.lastName") ;
	 Constants.EMPLOYEE_CONFIRM_DELETE 	= buildConfirmMessage();
 
	 cntr.loginErrorTxt.text = "";				// Manually clear the error text...
	 cntr.loginBtn.skin.invalidateProperties();	// Force refresh on custom skin part 
	 if (model != null) {
		model.usernameError 					= "";	// Clear all validation errors... 
		model.passwordError						= "";
	 }
 
	 } else {
 
		var main : MainView = sri.targetInstances[0] as MainView;
		if (main != null) {
			main.btnLogout.skin.invalidateProperties();	// Force refresh on custom skin part
		}
	 }
	}
 
	private function buildConfirmMessage():String {
		var params : Array = selectedEmployee ? [selectedEmployee.firstName, selectedEmployee.lastName] : ["",""]; 
		return 	_rMngr.getString("employees","users.confirmDelete",params);
	}
 
	// ********************************************************************************
	// Private Attributes
	// ********************************************************************************
 
 
	private var _rMngr 	: IResourceManager 	= ResourceManager.getInstance();
	private var _selectedEmployee 	: Employee 			= null;
 
]]>
</mx:Script>
 
 
 <l10n:SmartResourceInjector bundleName="login" target="{MainView}" localeChange="onLocaleChanged(event);" >
	<l10n:ResourceProxy 		property="btnLogout.label" 		key="login.signOut" />
	<l10n:ResourceProxy 		property="txtWelcomeUser.text" 	key="login.welcome" parameters="{[authenticated.user.username]}" />
	<l10n:ResourceProxy 		property="imgHeader.source" 	key="login.header" type="class" />
	<!-- 
	      Would be better to inject a new styleName or a new styleSheet 
		  Note: The login and logout buttons have custom skins with states.
	            Because we are modifying the colors of skin part manually (instead of different styleName)
	            we must manually invalidate to force immediate redraws. (see lines 83/89).
	-->
	<l10n:ResourceProxy 		property="contentContainer.backgroundColor" key="content.background.color" />
	<l10n:ResourceProxy 		property="contentFrame.color" 				key="content.frame.color" />
	<l10n:ResourceProxy 		property="btnLogout.backgroundColor" 		key="content.frame.color" />
 </l10n:SmartResourceInjector>
 
 <l10n:SmartResourceInjector bundleName="login" target="{LoginView}" localeChange="onLocaleChanged(event);" >
	<l10n:ResourceProxy 		property="fiUserName.label" 	key="login.form.fiUserName" />
	<l10n:ResourceProxy 		property="fiPassword.label" 	key="login.form.fiPassword" />
	<l10n:ResourceProxy 		property="loginBtn.label" 		key="login.submit" />
	<l10n:ResourceProxy 		property="lblHint.text" 		key="login.tip" />
 
	<l10n:ResourceProxy 	property="loginBtn.backgroundColor" key="content.frame.color" />
 </l10n:SmartResourceInjector>
 
 <l10n:SmartResourceInjector bundleName="employees" target="{EmployeeListNavigation}" >
	<l10n:ResourceProxy 		property="btnCreate.label" 	key="users.buttons.create" />
	<l10n:ResourceProxy 		property="btnEdit.label" 	key="users.buttons.edit" />
	<l10n:ResourceProxy 		property="btnDelete.label" 	key="users.buttons.delete" />
 </l10n:SmartResourceInjector>
 
 <l10n:SmartResourceInjector bundleName="employees" target="{EmployeeDetailNavigation}" >
	<l10n:ResourceProxy 		property="btnBack.label" 	key="users.buttons.back" />
 </l10n:SmartResourceInjector>
 
 <l10n:SmartResourceInjector bundleName="employees" target="{EmployeeDetail}" >
	<l10n:ResourceProxy 		property="fiFirstName.label" 	key="form.fiFirstName" />
	<l10n:ResourceProxy 		property="fiLastName.label" 	key="form.fiLastName" />
	<l10n:ResourceProxy 		property="fiStartDate.label" 	key="form.fiStartDate" />
	<l10n:ResourceProxy 		property="fiEmail.label" 		key="form.fiEmail" />
	<l10n:ResourceProxy 		property="btnSubmit.label" 		key="form.buttons.submit" />
	<l10n:ResourceProxy 		property="btnDelete.label" 		key="form.buttons.delete" />
 
	<l10n:ResourceProxy 		property="scaleX" 				key="form.scaling" />
	<l10n:ResourceProxy 		property="scaleY" 				key="form.scaling" />
 </l10n:SmartResourceInjector>
 
 <l10n:SmartResourceInjector  bundleName="language" target="{LanguageBar}">
	<l10n:ResourceProxy 		property="lblBarHelp.text" 		key="languagebar.title" 	parameters="{['target.selectedLocale']}" />
	<!--
		See LocaleAssets for sort order on Flags 
	-->
	<l10n:ResourceProxy  		property="flags[0].toolTip" 	key="flag.toolTip.en_US" />
	<l10n:ResourceProxy  		property="flags[1].toolTip" 	key="flag.toolTip.es_ES" />
 </l10n:SmartResourceInjector>
 
</l10n:LocaleMap>

The BabelFx framework works with Flex 3 & 4, and with Cairngorm, Mate, Swiz, RobotLegs, and other MVC/IoC solutions. But it seems to work “best of all” (easiest to integrate) with Swiz. Swiz leverages dependency injection and listens for GUI instantiations. And BabelFx leverages resource injection and listens for GUI instantiations (independent of Swiz). These two frameworks are a “marriage made in heaven.”

An interesting challenge with the CafeTownsend application was its implementation of color borders with Flex 4 skins. In order to change the skin colors during locale changes, the skins need to redraw via an updateDisplayList phase. See LocalizationMap [line 92] for the easy resolution to invalidating the skin colors during locale changes. Alternate solutions are possible but I wanted to demonstrate that l10Injection even adapts to allow changes of color settings in container skins.
 

And let’s not forget custom logging with the new Swiz LogProcessor and the [Log] metadata tag!

Another important change to the CafeTownsend demo are the new logging features. With Swiz you can log (or trace to the output console) activity within the Swiz engine and within your own custom application code. And, remember that the BabelFx localization framework (l10nInjection) has its own logger to output its injection activity. While the BabelFx injection has its own logging automatically enabled within the CafeTownsend application (see bottom), the application code now uses a new metadatatag [Log] to inject a logger instance and enable super-easy log functionality.

Note that the [Log] tag is enabled simply by adding a LogProcessor instance to your swiz setup and then calling log.debug() from your controllers, models, and views. Developers should also note that the log.debug() methods do not have to specify times, dates, or even the classname… yet they all show up in the trace console. That feature is inherited from the LogProcessor.

Below is the Swiz setup class which activates the Swiz LogProcessor:

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
35
36
<?xml version="1.0" encoding="utf-8"?>
<sw:Swiz    xmlns:sw="http://swiz.swizframework.org"
		    xmlns:fx="http://ns.adobe.com/mxml/2009" 
		    xmlns:mx="library://ns.adobe.com/flex/mx" >
 
 <fx:Script>
 <![CDATA[
	import mx.controls.Alert;
	import mx.rpc.events.FaultEvent;
 
	private function genericFault( fe : FaultEvent ) : void {
		var message : String = fe.fault.faultDetail; 
		var code    : String = fe.fault.faultCode;
 
		Alert.show(message , code);
	}
 ]]>
 </fx:Script>
 
 <sw:beanProviders>
	<config:Beans 		 xmlns:config="com.cafetownsend.config.*" />
 </sw:beanProviders>
 
 <sw:config>
	<sw:SwizConfig 		 id="appConfig"
						 strict="true"
						 eventPackages="com.cafetownsend.event.*" 
						 viewPackages="com.cafetownsend.view.*"
						 defaultFaultHandler="{genericFault}"/>
 </sw:config>
 
 <sw:customProcessors>
	<sw:LogProcessor /> </sw:customProcessors>
 
</sw:Swiz>

Now that we have configured Swiz to use the custom LogProcessor, we can use the [Log] metadatatag in our custom application code. The [Log] tag tells Swiz to inject a custom instance of an ILogger. That instance has been pre-configured to auto-output the time, date and className for the class in which it has been injected. Below is a snippet of the EmployeeController class which illustrates how you configure the “almost magical” ILogger injection using the [Log] tag and how you use the log.debug() method. Notice how the developer only had to add the method name and the method argument as additional custom information that they wish to log.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class EmployeeController {
 
  [Log]			
  public var log : ILogger = null;		
 
  [Mediate(event="LoginEvent.COMPLETE")]
  public function loadEmployees(department:String=""):void {
	log.debug("loadEmployees( '{0}' )",department); 
	var agent : AsyncInterceptor = new AsyncInterceptor(parseEmployeesData);
	serviceRequestUtil.executeServiceCall(agent.intercept(delegate.loadEmployees()), onResults_loadEmployees);
  }
}

Below is a trace console output sample from the CafeTownsend Swiz demo; output generated when using the custom Swiz LogProcessor and ILogger::debug() calls:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
6/6/2010 07:38:23.169 [DEBUG] com.cafetownsend.controller::AppController login(Flex)
6/6/2010 07:38:23.968 [DEBUG] com.cafetownsend.controller::AppController onResults_login(Flex)
6/6/2010 07:38:23.974 [DEBUG] com.cafetownsend.controller::EmployeeController loadEmployees('accounting')
6/6/2010 07:38:24.009 [DEBUG] com.cafetownsend.controller::NavigationController logInCompleteHandler()
6/6/2010 07:38:24.009 [DEBUG] com.cafetownsend.presentation::MainViewPresentationModel currentState==loggedIn
6/6/2010 07:38:24.198 [DEBUG] com.cafetownsend.presentation::EmployeeListPresentationModel set employees(); len=0
6/6/2010 07:38:24.200 [DEBUG] com.cafetownsend.presentation::EmployeeListPresentationModel selectedEmployeeIndex == -1
6/6/2010 07:38:24.201 [DEBUG] com.cafetownsend.presentation::EmployeeListPresentationModel selectedEmployeeIndex == -1
6/6/2010 07:38:24.476 [DEBUG] com.cafetownsend.controller::EmployeeController parseEmployeesData()
6/6/2010 07:38:24.477 [DEBUG] com.cafetownsend.controller::EmployeeController EmployeeUtil.getEmployeesFromXML()
6/6/2010 07:38:24.478 [DEBUG] com.cafetownsend.controller::EmployeeController onResults_loadEmployees(): employees=4
6/6/2010 07:38:24.481 [DEBUG] com.cafetownsend.presentation::EmployeeListPresentationModel set employees(); len=4
6/6/2010 07:38:24.481 [DEBUG] com.cafetownsend.presentation::EmployeeListPresentationModel set employees(); len=4
6/6/2010 07:38:25.071 [DEBUG] com.cafetownsend.presentation::EmployeeListPresentationModel set employees(); len=4
6/6/2010 07:38:25.072 [DEBUG] com.cafetownsend.presentation::EmployeeListPresentationModel selectedEmployeeIndex == -1
6/6/2010 07:38:25.073 [DEBUG] com.cafetownsend.presentation::EmployeeListPresentationModel selectedEmployeeIndex == -1

And since the the BabelFx framework has logging enabled by default, the BabelFx logging generates output that looks something like this:

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
07:40:02.599 [DEBUG] LocalizationMap register(com.cafetownsend.view::MainView)
07:40:02.600 [DEBUG] LocalizationMap register(com.cafetownsend.view::LoginView)
07:40:02.600 [DEBUG] LocalizationMap register(com.cafetownsend.view::EmployeeListNavigation)
07:40:02.601 [DEBUG] LocalizationMap register(com.cafetownsend.view::EmployeeDetailNavigation)
07:40:02.602 [DEBUG] LocalizationMap register(com.cafetownsend.view::EmployeeDetail)
07:40:02.603 [DEBUG] LocalizationMap register(l10n.views::LanguageBar)
07:40:02.604 [DEBUG] LocalizationMap addListenerProxy() Attaching listener for all  'creationComplete' event
07:40:02.859 [DEBUG] LocalizationMap onLoadLocale() announce 'changing' locale
07:40:02.861 [DEBUG] LocaleCommand loadLocale(en_US) - embedded.
07:40:02.961 [DEBUG] LocalizationMap onCreationComplete_Target() for 'l10n.views::LanguageBar'
07:40:02.962 [DEBUG] SmartResourceInjector onInstanceCreationComplete(l10n.views::LanguageBar)
07:40:02.965 [DEBUG] SmartResourceInjector inject 'Switch from English to locale:' into 'lblBarHelp.text' from resource language::languagebar.title
07:40:02.966 [DEBUG] SmartResourceInjector inject 'English (USA)' into 'flags[0].toolTip' from resource language::flag.toolTip.en_US
07:40:02.967 [DEBUG] SmartResourceInjector inject 'Spanish' into 'flags[1].toolTip' from resource language::flag.toolTip.es_ES
07:40:02.968 [DEBUG] LocalizationMap onCreationComplete_Target() for 'com.cafetownsend.view::MainView'
07:40:02.968 [DEBUG] SmartResourceInjector onInstanceCreationComplete(com.cafetownsend.view::MainView)
07:40:02.968 [DEBUG] SmartResourceInjector inject 'logout' into 'btnLogout.label' from resource login::login.signOut
07:40:02.969 [DEBUG] SmartResourceInjector inject 'Hello {0}!' into 'txtWelcomeUser.text' from resource login::login.welcome
07:40:02.970 [DEBUG] SmartResourceInjector inject '[class en_US$login_properties__embed_properties___assets_images_header_png_321309068]' into 'imgHeader.source' from resource login::login.header
07:40:02.970 [DEBUG] SmartResourceInjector inject '0xFFFFFF' into 'contentContainer.backgroundColor' from resource login::content.background.color
07:40:02.971 [DEBUG] SmartResourceInjector inject '0x9C0000' into 'contentFrame.color' from resource login::content.frame.color
07:40:02.971 [DEBUG] SmartResourceInjector inject '0x9C0000' into 'btnLogout.backgroundColor' from resource login::content.frame.color
07:40:03.399 [DEBUG] LocalizationMap onCreationComplete_Target() for 'loginView'
07:40:03.400 [DEBUG] SmartResourceInjector onInstanceCreationComplete(com.cafetownsend.view::LoginView)
07:40:03.400 [DEBUG] SmartResourceInjector inject 'Username:' into 'fiUserName.label' from resource login::login.form.fiUserName
07:40:03.401 [DEBUG] SmartResourceInjector inject 'Password:' into 'fiPassword.label' from resource login::login.form.fiPassword
07:40:03.401 [DEBUG] SmartResourceInjector inject 'Login' into 'loginBtn.label' from resource login::login.submit
07:40:03.402 [DEBUG] SmartResourceInjector inject '( Username: Flex   Password: Swiz )' into 'lblHint.text' from resource login::login.tip
07:40:03.402 [DEBUG] SmartResourceInjector inject '0x9C0000' into 'loginBtn.backgroundColor' from resource login::content.frame.color

As you can see, the logging can be a very powerful output and monitoring tool for developers. The custom Swiz LogProcessor is available on my GitHub repository. And the BabelFx logger is built-into the BabelFx framework.


© Thomas Burleson for GridLinked to RUX, 2010.             Subscribe by Email | Contact Author             Permalink | 9 comments

]]>
http://www.gridlinked.info/swiz-localization-l10n-logging/feed/ 9
FlexStore Revisited (with BabelFx)http://www.gridlinked.info/flexstore-revisited-with-i18n/ http://www.gridlinked.info/flexstore-revisited-with-i18n/#comments Tue, 06 Apr 2010 00:36:28 +0000 thomasb http://www.gridlinked.info/?p=711 Christophe Coenraets published a Flex4 update to the classic FlexStore application in his recent blog FlexStore Revisited. The Phone catalog data is actually loaded from an external XML file and a Filter bar allows users “quick filters” for rapid browsing. In the Flex 4 SDK version, he demonstrated the use of tile layouts and Spark DataGroups and experimented with cool hover effects in the “store” catalog display.

I will to demonstrate how easy it is to use the BabelFx framework (aka l10nInjection) to add multi-language ( l10n ) support to his FlexStore4 application. It is shockingly easy and results in an application (shown below) that is fun to demo.

With the BableFx solution, online visitors can now browse the FlexStore Phone catalog in English, Spanish, French, or Chinese. Meanwhile developers can continue with code implementation and enhancement efforts without almost no concerns for i18n requirements.

l10n FlexStore (Flex 4 SDK)
Figure 1: FlexStore Revisited with l10n (click to launch)

Source code is available for the application shown above. Simply right-click to see the “View Source” option. In order to add localization (l10n) features to the FlexStore, I needed to perform five (5) tasks to enable the application to support multi-languages:

  1. Include the BabelFx.swc framework in the FlexStore4 project
  2. Create an BabelFx LocalizationMap to specify which keys are used to inject resources into which targets.
  3. Compile and embed the resourceBundles inside the deployed swf.
  4. Translate the catalog.xml phone data to 4 localized versions

The ONLY changes to the FlexStore source code was in the FlexStore4.mxml application source (shown below). Only 2 lines (14 and 17)… only 2 MXML tags. No other source code was changed. And yet now the application displays the Phone catalog information in four different locales.

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?xml version="1.0" encoding="utf-8"?>
<s:Application width="500" height="704" 
			   applicationComplete="catalogLoader.send()" 
			   viewSourceURL="srcview/index.html" 
			   xmlns:local="*" 
			   xmlns:fx="http://ns.adobe.com/mxml/2009" 
			   xmlns:s="library://ns.adobe.com/flex/spark" 
			   xmlns:mx="library://ns.adobe.com/flex/mx" 
			   xmlns:tiles="store.layouts.*" >
 
 
	<fx:Declarations>
		<s:HTTPService 					id="catalogLoader" url="assets/data/catalog.xml" result="onCatalogLoaded(event)"/>
		<l10nInjection:LocalizationMap 	id="injectors"	   phone="{_selectedPhone}"            xmlns:l10nInjection="l10n.map.*" />	</fx:Declarations>
 
	<l10n:LanguageBar id="barLang" right="19" left="19"   y="644" height="40" xmlns:l10n="l10n.views.*"/>
 
	<!-- Phone Catalog Display Area -->
	<s:Label id="lblCatalog" x="25" y="102" text="Catalog:" alpha=".3"/>
	<s:BorderContainer y="119" height="381" backgroundColor="#EDEDED" left="20" right="20">
		<s:DataGroup width="100%" height="100%" x="0" y="0"
					 dataProvider="{_items}" 
					 itemRenderer="store.renderers.Phone_Zoom" 
					 mouseMove="onHoverPhone(event);" 
					 contentBackgroundColor="#EDEDED" >
			<s:layout>
				<tiles:FilteredTileLayout id="filterLayout" filteredItems="{_filteredItems}" />
			</s:layout>
		</s:DataGroup>
	</s:BorderContainer>
 
	<s:Label id="lblDescription" x="25" y="514" text="Description:" alpha=".3" visible="{_selectedPhone != null}"/>
	<s:TextArea id="txtPhoneDescription" 
				alpha="{_selectedPhone ? 1 : .2}"
				text="{_selectedPhone ? _selectedPhone.description : ''}"
				top="532" left="20" right="20" 
				editable="false"  contentBackgroundColor="#EDEDED" color="#3D3D3D" 
				paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10"  height="100"/>
 
	<!-- Catalog Filter Options -->
	<mx:Canvas height="63" backgroundColor="#FAD1D1" borderAlpha="1.0" cornerRadius="5" borderColor="#080000" left="20" right="20" top="20" horizontalScrollPolicy="off" verticalScrollPolicy="off">
		<s:Label id="lblFilters" x="15" y="9" text="Filter Catalog:" color="#921010"/>
		<s:HGroup verticalAlign="middle" paddingLeft="8" x="6" y="30" height="28" >
			<s:Label id="lblMaxPrice" text="Max Price:" fontSize="11"/>
			<s:HSlider id="priceSlider" minimum="0" maximum="1000" snapInterval="100" value="@{_maxPrice}" change="onFiltersChanged()"/>
			<mx:Spacer width="20"/>
			<s:CheckBox id="chbxCamera"  label="Camera" selected="@{_camera}" change="onFiltersChanged()" fontSize="11"/>
			<s:CheckBox id="chbxVideo"   label="Video" selected="@{_video}" change="onFiltersChanged()" fontSize="11"/>
			<s:CheckBox id="chbxTriband" label="Triband" selected="@{_triband}" change="onFiltersChanged()" fontSize="11"/>
		</s:HGroup>
	</mx:Canvas>
 
</s:Application>

The LocalizationMap instance handles all of the complexities and issues of supporting multiple languages and resource bundles, updating your view code, responding to view state changes, data model changes, and more… Note that when the locale changes, the localized version of the XML data is also reloaded; see line 29. Each localized data file actually has a different set of phones in the catalog. When switching locales, the visitor can easily see the Phone display update along with localized text.

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?xml version="1.0" encoding="utf-8"?>
<LocaleMap 	xmlns="http://com.asfusion.mate/l10n"
			xmlns:mx="http://www.adobe.com/2006/mxml" >
 
	<mx:Metadata>
	<!--
		Using compiler arguments -locale=en_US,es_ES,fr_FR,zh_CN -allow-source-path-overlap=true -source-path+=locale/{locale}
	    with the metaTag below forces the compiler to EMBED the compiled resource bundles
	-->
	[ResourceBundle("store")]
	</mx:Metadata>
 
	<mx:Script>
		<![CDATA[
		import l10n.views.LanguageBar;
 
		[Bindable] 
		public var phone : Object = null;
 
		/**
		 *  EventHandler when locales change to new Catalog xml data to be loaded
		 *  This xml data has localized versions of the catalog.
		 */
		private function onLocaleChanged(event:Event):void {
			var sri : SmartResourceInjector  = (event.target as SmartResourceInjector);
			var app : FlexStore4             = sri.targetInstances[0] as FlexStore4;
 
			// Dynamically reload the XML data with localized text for each Phone catalog entry
			if (app != null) app.catalogLoader.send();		}			
		]]>
	</mx:Script>
 
	<SmartResourceInjector bundleName="store" target="{FlexStore4}" localeChange="onLocaleChanged(event);">
		<ResourceProxy property="catalogLoader.url" 			key="data.url" />
 
		<ResourceProxy property="lblDescription.text" 			key="details.title" parameters="{[phone.name]}"/>
		<ResourceProxy property="lblCatalog.text" 				key="catalog.title" />
 
		<ResourceProxy property="lblFilters.text" 				key="filters.title" />
		<ResourceProxy property="lblMaxPrice.text" 				key="filters.maxPrice" />
		<ResourceProxy property="chbxCamera.label" 				key="filters.option.camera" />
		<ResourceProxy property="chbxVideo.label" 				key="filters.option.video" />
		<ResourceProxy property="chbxTriband.label" 			key="filters.option.triband" />
	</SmartResourceInjector>
 
	<SmartResourceInjector bundleName="store" target="{LanguageBar}">
		<ResourceProxy property="lblBarHelp.text" 	key="languagebar.title" />
	</SmartResourceInjector>
 
</LocaleMap>

Another interesting addition is LanguageBar.mxml. The LanguagBar view component provides a visual toolbar to allow visitors to dynamically switch to alternate locales. I encourage the reader to check out the LanguageBar to see how the bar is constructed dynamically from a list of embedded resources. Note that selecting a Flag button in the LanguageBar dispatches a LocaleEvent.LOAD_LOCALE event, BabelFx responds, and “magic” happens to the entire FlexStore application.

A great, hidden feature of the new BabelFx framework is auto-detection. At application startup, BabelFx will auto-detect the visitors OS language preference and auto-switch the FlexStore to match the visitor’s locale/language settings (if that locale has been embedded).

I encourage developers to check out the GitHub repository for samples and the framework source code.

Last, but not least, I will be presenting an BabelFx/l10nInjection session at the upcoming cf.Objective( ) 2010 conference (Minneapolis, MN April 22-24, 2010). Below are the slides that will be presented that detail the issues with i18n and the processes within BabelFx.




© Thomas Burleson for GridLinked to RUX, 2010.             Subscribe by Email | Contact Author             Permalink | 2 comments

]]>
http://www.gridlinked.info/flexstore-revisited-with-i18n/feed/ 2
Learning BabelFx: Video Tutorialhttp://www.gridlinked.info/flex-i18n-with-localizationmaps-video-tutorial-source/ http://www.gridlinked.info/flex-i18n-with-localizationmaps-video-tutorial-source/#comments Thu, 17 Dec 2009 20:41:34 +0000 thomasb http://www.gridlinked.info/?p=576 In a previous article, I blogged about Flex i18n and an “Inversion of Control” (IOC) approach to radically simplify localization issues within your Flex solutions. That article discussed the issues with the current, traditional localization implementations [used in Flex applications] and then presented the new LocalizationMap solution.

In this Part 2 on Flex l10n solutions, I offer a video tutorial, a demo of a Registration application localized to support Spanish and English, and an architecture diagram of LocalizationMap usages.

Also included with the live Flex demo is source code for both the demo and the l10n LocaleMap library.

Below is a screencast video tutorial; in which I provide a narrated overview of the issue involved with enabling your Flex application to support multiple languages (and locales). I also present LocalizationMaps as a great solution to easily and rapidly provide l10n (localization) features within your custom Flex apps.

Figure 3: Video Tutorial
Figure 1: Video Tutorial (click to play)

Note that the video/podcast tutorial may take 5-10 seconds for streaming to start.

Shown below is a live demo of the localized Registration application. This application has l10n (localization) support for English and Spanish. In the live demo, select the Spanish or English flags to change the current locale. Watch what happens to all the strings and styles.

Be sure to complete the registration fields and click the Register button to see the view state changes to show a localized Thank you with fullname and email parameters (sounds confusing… just try it)!

Registration Demo (with l10n)
Figure 2: RegistrationDemo w/ Locales (click to launch)

Simply right-click to access the source code for both the demo and the l10n LocaleMap library. Or click here to download the source code. Before you review the source code, however, I encourage you to review the architecture diagram shown Figure 3 and listen to the Screencast tutorial (Figure 1).

Here is a high-level diagram that illustrates how the LocalizationMap is used within your custom applications. I encourage you to conceptualize the LocalizationMap as a plug-in to your application… an extension that is completely transparent (hidden) from your presentation/GUI layer.

LocalizationMap Usage Diagram
Figure 3: LocalizationMap Usage Diagram (click to zoom)



Last, but not least, here is a presentation that I gave at the 360Flex conference (San Jose, Mar 2010) and the cfObjective conference (Minneapolis, Apr 2010).




© Thomas Burleson for GridLinked to RUX, 2009.             Subscribe by Email | Contact Author             Permalink | 45 comments

]]>
http://www.gridlinked.info/flex-i18n-with-localizationmaps-video-tutorial-source/feed/ 45
Building Multilanguage Flex RIAshttp://www.gridlinked.info/amazing-i18n-solutions-for-flex-3-4/ http://www.gridlinked.info/amazing-i18n-solutions-for-flex-3-4/#comments Fri, 13 Nov 2009 16:15:40 +0000 thomasb http://www.gridlinked.info/?p=453 At some point, Flash and Flex developers encounter the issues of modifying a Flex/Flash rich internet application (RIA) to support another language and accomodate software changes for specific cultural preferences. This article focuses on localization of Flash solutions implemented with Flex 3 or Flex 4. Both Flash CS4 and the Flex frameworks provide localization tools and programmatic support features for Flash applications and widgets. Unfortunately the support for localization differs significantly between the toolsets and frameworks offered.

Internationalization (i18n) is the process of generalizing a product so that it can handle multiple languages and cultural conventions without the need for redesign. i18n processes usually take place at the level of program design and documentation development. Localization (l10n) is the process of adapting products and services (web sites, documents, data files, videos, manuals, and software interfaces). Software localization is the adaptation of static text, skins, images, sytles, fonts [etc.] in accordance to linguistic, cultural, technical, and other locale-specific requirements of the target market.

With cloud-services, market globalization, and radical growths in both online and mobile applications, Flash i18n solutions will play critical roles in product differentiation and market acquisition. The importance and growth of the localization market is illustrated in the chart below.

Localization Market Growths

As mentioned earlier, localization is the process of including assets to support multiple locales. A locale is the combination of a language and a country code. This is required for country differences in language syntatics and culture. For example, an application in the en_US (USA) locale might spell the word "color", whereas the word would be "colour" in the en_GB (Great Britain) locale. Localization goes beyond just translating Strings used in your application. It can also include any type of asset such as audio files, images, and videos. Because the meanings of colors and images may vary based on the culture, you should change the styles used by your application – in addition to the language used – based on the locale.

What are the i18n technology impacts for Flash applications ?

In order to localize (l10n) Flex 4 applications, you may use built-in support for accessing compiled resource bundles or create your own solution to access localized assets stored in remote database tables. To use the built-in Flex mx framework solutions, you first

  • Create properties files (key/value pairs) that define the localized assets.
  • Use ANT scripts to compile these properties files into resource modules from the properties files
  • Use custom code to request specific localized bundles
  • Use custom code to on-demand load the "compiled resource bundles" OR statically compile all bundles into the application (not recommended).
  • Lastly, you must resolve i18n business processes and complexities when involving out-sourced translators; required to provide localized translations to each text string.

Flex allows the developer to localize strings, skinning code/classes, and embedding graphics. These allows Flex apps to localize for cultural differences also (stylesheets, colors, images, etc.) Note that many Flex applications load dynamic xml content; content that may also need to be localized. Using "best practices" approaches to localization will provide for easy maintenance and on-demand additions of localized resource bundles. As noted above, Java ANT scripts are usually employed to compile the localized files and assets into run-time loadable bundles.

Flash IDE tools do not support the Flex solution for resource bundles and compiled resource swfs. Instead a Locale class is provided that supports loading locale-based XML files and provides support for string look-ups by locale and ID codes. While localized content can by dynamically loaded at runtime, the GUI controls must be programmatically updated [after the localized text or XML data has loaded]. Note that the Locale class does not support loading of localized skins and images; only string-based content is supported.

Adobe and the developer community offer a plethora of articles and videos for developers to self-train and implement Flex l10n solutions. Recently, two Flex entrepreneurs created an open-source tool called ResourceManager and a Flex/AIR solution called Lupo Manager;both focused on providing tools to easily manage properties files and modify source code to accommodate l10n features.

Those tools are a step in the right direction and I give a big shout out to David Deraedt for his efforts. In fact, Adobe has provided significant documentation and recommended solutions to be used as an industry standard for localizing Flex apps. The Flex community has myriad blogs and snippets that discuss the issues. But…

Use the Adobe/industry localization standard… and you will be DISMAYED!

Now before you get really worried, realize that I deliberately used my last statement to (a) grab your attention, (b) highlight my dismay and (c) set the stage for a better solution. Let me show you what I mean. Consider a typical registration [shown below] that has validation, remembers last login, supports english error messages, and more. Note that this version of the application does not yet support l10n:

Registration Demo (w/out i18n)
Figure 1: Registration Demo (click to launch)



To appreciate the impacts of l10n support, let’s first look at the code used in the demo. In particular let’s look at the MXML BEFORE it has l10n-enabling code modifications:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--
       Component: Sign-in Form has "registration" and "already registered 
                           child components.
-->
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas id="registerArea"
		   x="20" top="20" bottom="20" 
		   cacheAsBitmap="true"
		   horizontalScrollPolicy="off" verticalScrollPolicy="off"
		   styleName="registrationForm" >
 
	<forms:RegistrationForm 	id="frmRegister" 	account="{visitor}" 		x="25" top="33"		width="386" height="290" mouseDown="frmSignin.clearErrors();"	/>
	<forms:AlreadyRegisteredForm 	id="frmSignin"		email="{visitor.email}" 	x="25" top="343"	width="386" height="111" mouseDown="frmRegister.clearErrors();"	/>
 
</mx:Canvas>


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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<!-- 
       Component:  New Account Registration  
-->
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas 	width="386" height="290"
			clipContent="false"
		 	xmlns:mx="http://www.adobe.com/2006/mxml" >
 
<mx:Script>
	<![CDATA[
		/** Removed while displaying in blog... */
	]]>
</mx:Script>
 
<mx:Array id="registrationValidators">
	<mx:StringValidator source="{txtFirstName}" 	property="text" 		 required="true" minLength="2" requiredFieldError="First name is required!"/>
	<mx:StringValidator source="{txtLastName}" 		property="text" 		 required="true" minLength="2" requiredFieldError="Last name is required!"/>
	<mx:EmailValidator  source="{txtEmail}" 		property="text"			 required="true" 			   requiredFieldError="A valid email is required!"/>
	<mx:StringValidator source="{txtCompany}" 		property="text" 		 required="true" minLength="2" requiredFieldError="A company name is required!"/>
	<mx:StringValidator source="{txtPostal}" 		property="text" 		 required="true" minLength="2" requiredFieldError="What is your Postal Code?"/>
	<mx:PhoneNumberValidator source="{txtPhone}"	property="text"			 required="true" minDigits="10" requiredFieldError="A valid phone # is required; e.g (515.221.3216)"/>
	<mx:NumberValidator source="{cmbCountry}"		property="selectedIndex" required="true" minValue="0"  lowerThanMinError="Please select your Country of residence" />
	<mx:NumberValidator source="{cmbState}"			property="selectedIndex" required="true" minValue="0"  lowerThanMinError="Please select a US State of residence" enabled="{cmbCountry.selectedItem == Countries.USA}"/>			
</mx:Array>
 
<mx:Label id="lblTitle" 	text="Register Now" styleName="siHeader"   y="0"/>
<mx:Label id="lblSubTitle" 	text="It only takes a few seconds" styleName="siText"  x="2" y="35"/>
<mx:Label id="lblMsg" 		text="(All fields are required)" styleName="siText" fontStyle="italic" textAlign="right" y="37" x="232" fontSize="10"/>
 
<mx:Label 	id="lblFirstName" 	text="First Name:" styleName="siFieldLabel"   y="65" x="-2"/>
<mx:TextInput 	id="txtFirstName" 	text="{_account.firstName}" 	minWidth="168" 								
									focusOut="onCheckValidation(event);"  x="0" y="81" tabIndex="1"/>
<mx:Label 	id="lblLastName"	text="Last Name:" x="181"  minWidth="70" y="65"			styleName="siFieldLabel"  />
<mx:TextInput 	id="txtLastName"  	text="{_account.lastName}"		width="168" x="183" 						
									focusOut="onCheckValidation(event);"  y="81" tabIndex="2"/>
 
<mx:Label 	id="lblEmail" 		text="eMail:" width="70"  x="-3" y="111" 	styleName="siFieldLabel"/>
<mx:TextInput 	id="txtEmail" 		text="{_account.email}"			width="168" y="127" 						
									focusOut="onCheckValidation(event);"  tabIndex="3"/>
<mx:Label 	id="lblCompany"		text="Company:" x="181"	 width="70" y="111"			styleName="siFieldLabel" 												  />
<mx:TextInput 	id="txtCompany" 	text="{_account.company}" 		width="168" y="127" x="183" 				
									focusOut="onCheckValidation(event);"   tabIndex="4"/>
 
<mx:Label 	id="lblZip" 		text="Postal"	 width="70" y="159"   x="240"  styleName="siFieldLabel"/>
<mx:TextInput 	id="txtPostal"		text="{_account.postal}" 		width="109" x="242" 						
									focusOut="onCheckValidation(event);"  y="175" tabIndex="6"/>
 
<mx:Label 	id="lblCountry"		text="Country" x="-2" width="70" y="159"			styleName="siFieldLabel" 	  />
<mx:ComboBox 	id="cmbCountry" 	dataProvider="{_countries}"		width="230" x="0" y="176"					
									change="onCountryChanged();"			
									prompt="Please select your Country..."  tabIndex="5"/>
 
<mx:Label	id="lblState"		text="State:"	 width="70" x="-1" y="205"	styleName="siFieldLabel" 					 
									alpha="{(cmbCountry.selectedItem == Countries.USA) ? 1 : .3}"/>
<mx:ComboBox 	id="cmbState"		dataProvider="{_states}"		width="230" x="0" y="222" 					
									change="onStateChanged();"	 			selectedIndex="-1"									
									prompt="Please select a State..." 	tabIndex="7"		/>
 
<mx:Label 	id="lblPhone" 		text="Phone:" width="70" x="240" y="205"     styleName="siFieldLabel"/>
<mx:TextInput 	id="txtPhone"		text="{_account.phone}" 		width="109" x="242" y="222"  				
									restrict="0-9,.-" focusOut="onCheckValidation(event);"   tabIndex="8"/>
 
<mx:Image id="btnRegister" click="onSaveProfile()" x="2" y="258"  tabIndex="9" source="@Embed('/assets/images/btn_register.png')" useHandCursor="true" buttonMode="true" focusEnabled="true"/>
 
</mx:Canvas>

A design-mode view from FlashBuilder shows nothing surprising… just your standard WYSIWYG renderering shown in Figure 2. Now let’s apply Adobe-recommended code changes to support localization (l10n) for English and Spanish. Figure 3 shows the design-view impacts as a result of source code modifications; necessary to databind to the resourceManager.










Are here is the MXML code associated with the Figure 3 design-view snapshot:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas width="386" height="290"
			  clipContent="false"
			  xmlns:frm="com.thunderbay.i18nDemo.views.forms.*"
			  xmlns:comp="com.thunderbay.i18nDemo.control.*"
			  xmlns:mx="http://www.adobe.com/2006/mxml" >
 
<mx:Metadata>
	[ResourceBundle("registration")]</mx:Metadata>	
 
<mx:Script>
	<![CDATA[
		/** Removed while displaying in blog... */  
	]]>
</mx:Script>
 
<mx:Array id="registrationValidators">
	<mx:StringValidator source="{txtFirstName}" 	property="text" 		 required="true" minLength="2" requiredFieldError="{resourceManager.getString('registration','register.validationError.firstName')}"/>
	<mx:StringValidator source="{txtLastName}" 		property="text" 		 required="true" minLength="2" requiredFieldError="{resourceManager.getString('registration','register.validationError.lastName')}"/>
	<mx:EmailValidator  source="{txtEmail}" 		property="text"			 required="true" 			   requiredFieldError="{resourceManager.getString('registration','register.validationError.email')}"/>
	<mx:StringValidator source="{txtCompany}" 		property="text" 		 required="true" minLength="2" requiredFieldError="{resourceManager.getString('registration','register.validationError.company')}"/>
	<mx:StringValidator source="{txtPostal}" 		property="text" 		 required="true" minLength="2" requiredFieldError="{resourceManager.getString('registration','register.validationError.postal')}"/>
	<mx:PhoneNumberValidator source="{txtPhone}"	property="text"			 required="true" minDigits="10" requiredFieldError="{resourceManager.getString('registration','register.validationError.phone')}"/>
	<mx:NumberValidator source="{cmbCountry}"		property="selectedIndex" required="true" minValue="0"  lowerThanMinError="{resourceManager.getString('registration','register.validationError.country')}" />
 
	<mx:NumberValidator source="{cmbState}"			property="selectedIndex" required="true" minValue="0"  lowerThanMinError="{resourceManager.getString('registration','register.validationError.state')}" 
</mx:Array>
 
<mx:Label id="lblTitle" 	text="{resourceManager.getString('registration','register.title')}" styleName="siHeader"   y="0" width="351"/>
 
<mx:Label id="lblSubTitle" 	text="{resourceManager.getString('registration','register.subtitle')}" styleName="siText"  x="2" y="35" width="199"/>
<mx:Label id="lblMsg" 		text="{resourceManager.getString('registration','register.allRequired')}" styleName="siText" fontStyle="italic" textAlign="right" y="37" x="232" fontSize="10"/>
 
<!-- Each text input is horizontally aligned on column markers each at 184 pixels wide -->
<mx:Label 	id="lblFirstName" 	text="{resourceManager.getString('registration','register.firstName')}"				
									styleName="siFieldLabel"   y="65" x="-2" width="170"/>
<mx:TextInput 	id="txtFirstName" 	text="{_account.firstName}" 	width="168" 								
									focusOut="onCheckValidation(event);"  x="0" y="81" tabIndex="1"/>
<mx:Label 	id="lblLastName"	text="{resourceManager.getString('registration','register.lastName')}" x="181" 		
									width="170" y="65"			styleName="siFieldLabel"  />
<mx:TextInput 	id="txtLastName"  	text="{_account.lastName}"		width="168" x="183" 						
									focusOut="onCheckValidation(event);"  y="81" tabIndex="2"/>
 
<mx:Label 	id="lblEmail" 		text="{resourceManager.getString('registration','register.email')}"				
									width="171"  x="-3" y="111" 	styleName="siFieldLabel"/>
<mx:TextInput 	id="txtEmail" 		text="{_account.email}"			width="168" y="127" 						
									focusOut="onCheckValidation(event);"  tabIndex="3"/>
<mx:Label 	id="lblCompany"		text="{resourceManager.getString('registration','register.company')}" x="181"		
									width="170" y="111"			styleName="siFieldLabel" 												  />
<mx:TextInput 	id="txtCompany" 	text="{_account.company}" 		width="168" y="127" x="183" 				
									focusOut="onCheckValidation(event);"   tabIndex="4"/>
 
<mx:Label 	id="lblZip" 		text="{resourceManager.getString('registration','register.postal')}"				
									width="111" y="159"   x="240"  styleName="siFieldLabel"/>
<mx:TextInput 	id="txtPostal"		text="{_account.postal}" 		width="109" x="242" 						
									focusOut="onCheckValidation(event);"  y="175" tabIndex="6"/>
 
<mx:Label 	id="lblCountry"		text="{resourceManager.getString('registration','register.country')}" x="-2" 		
									width="234" y="159"			styleName="siFieldLabel" 	  />
<mx:ComboBox 	id="cmbCountry" 	dataProvider="{_countries}"		width="230" x="0" y="176"					
									change="onCountryChanged();"			
									prompt="{resourceManager.getString('registration','register.prompt.country')}"  tabIndex="5"/>
 
<mx:Label	id="lblState"		text="{resourceManager.getString('registration','register.state')}"				
									width="233" x="-1" y="205"	styleName="siFieldLabel" 					 
									alpha="{(cmbCountry.selectedItem == Countries.USA) ? 1 : .3}"/>
<mx:ComboBox 	id="cmbState"		dataProvider="{_states}"		width="230" x="0" y="222" 					
									change="onStateChanged();"	 			selectedIndex="-1"									
									prompt="{resourceManager.getString('registration','register.prompt.state')}" 	tabIndex="7"		/>
 
<mx:Label 	id="lblPhone" 		text="{resourceManager.getString('registration','register.phone')}"				
									width="111" x="240" y="205"     styleName="siFieldLabel"/>
<mx:TextInput 	id="txtPhone"		text="{_account.phone}" 		width="109" x="242" y="222"  				
									restrict="0-9,.-" focusOut="onCheckValidation(event);"   tabIndex="8"/>
 
<mx:Image 	id="btnSubmit" 		click="onSaveProfile()" x="2" y="258"  tabIndex="9" 
									source="{resourceManager.getClass('registration','register.btnRegister'}" 
									useHandCursor="true" buttonMode="true" />
 
</mx:Canvas>

The changes for l10n results in a maintenance nightmare and is – in my opinion – completely unusable! So what are the biggest issues with these industry l10n code techniques/solutions using the Flex resourceManager?

Imagine a pigeon pooping all over your code!
  1. The Design-view is no longer usable.
  2. No compile-time checks are performed to determine whether or not the bundle name are correct. No checks to validate spellings of the key or existence of the key.
  3. Each and every .mxml and .as file needs similar changes in order to support l10n.
  4. No facility for centralized management of bundles, keys, and locales.
  5. No accommodations for localized effects to text compression or text expansion

Now realize that I would not condemn an industry standard without presenting a viable – and superior – solution.

Why not use Flex Behavior Injection techniques to inject localized assets during runtime?

For those familiar with my previous presentations, talks, and blogs on Flex Behavior Injection, let’s consider a new approach to l10n. Instead of modifying your source code with ResourceManager invocations, let us “inject” the results of the locale changes “into” your components. Now your views and components no longer know anything about locale changes and resource bundle requirements. Now your design views render properly.

This process of injection is very similar to that used in the SWIZ, Mate, and other IOC frameworks. So now you, as the developer, never need to embed ResourceManager calls in any of your view components. That is really COOL!

Use a LocalizationMap and one (1) single mxml tag to easily add multiple language support to your entire RIA!

So how does this new l10n injection work? What changes are needed to your source code? Below is the application MXML code for the demo. This code (and demo shown above) does NOT support l10n.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<mx:Application pageTitle="i18n Demo without l10n"
				preloader="com.thunderbay.i18nDemo.views.preloader.Preloader"
				creationComplete="signIn.visible = true;"
				backgroundColor="#777777"  backgroundGradientColors="[#777777,#BABABA]"
				layout="absolute" horizontalScrollPolicy="off" verticalScrollPolicy="off"
				xmlns:mx="http://www.adobe.com/2006/mxml">
 
	<mx:Style source="locale/en_US/assets/css/styles.css" />
 
	<!-- Classic Cairngorm MVC structures -->
	<model:Model id="_model" 							xmlns:model="com.thunderbay.i18nDemo.model.*"/>
	<mvc:i18nController									xmlns:mvc="com.thunderbay.i18nDemo.control.*"/>
 
	<views:SignInPage  id="signIn"						xmlns:views="com.thunderbay.i18nDemo.views.*"
					   visitor="{_model.account}"
					   visible="false" 
					   width="854" height="526" 
					   horizontalCenter="0" 
					   verticalCenter="0" />
</mx:Application>

And below is the same MXML application code with 1 line change to support l10n for the entire application:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<mx:Application pageTitle="i18n Demo with LocalizationMaps"
				preloader="com.thunderbay.i18nDemo.views.preloader.Preloader"
				creationComplete="signIn.visible = true;"
				backgroundColor="#777777"  backgroundGradientColors="[#777777,#BABABA]"
				layout="absolute" horizontalScrollPolicy="off" verticalScrollPolicy="off"
				xmlns:mx="http://www.adobe.com/2006/mxml">
 
	<mx:Style source="locale/en_US/assets/css/styles.css" />
 
	<!-- Classic Cairngorm MVC structures -->
	<model:Model id="_model" 							xmlns:model="com.thunderbay.i18nDemo.model.*"/>
	<mvc:i18nController									xmlns:mvc="com.thunderbay.i18nDemo.control.*"/>
 
	<views:SignInPage  id="signIn"						xmlns:views="com.thunderbay.i18nDemo.views.*"
					   visitor="{_model.account}"
					   visible="false" 
					   width="854" height="526" 
					   horizontalCenter="0" 
					   verticalCenter="0" />
	<!-- Single tag used to provide l10n features for the entire application and all sub views 	-->
	<local:LocalizationMap  account="{_model.account}"	xmlns:local="*" /> 
</mx:Application>

Now, whenever the locale changes (at app startup detection, or based on user profile, or user interaction) ALL your views and application l10n features are auto-magically updated. How is this achieved? In the code above, we used a LocalizationMap to configure or “map” injectors to “inject” localized values into your runtime components and models.

Here is the LocalizationMap source for the demo presented in this blog:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<?xml version="1.0" encoding="utf-8"?>
<LocaleMap     enableLog="true"
            xmlns="library://ns.mindspace.com/l10n/flex/" 
            xmlns:mx="http://www.adobe.com/2006/mxml" >
 
    <!-- Support for Locale ResourceBundle changes -->
    <mx:Script>
        <![CDATA[
            import com.thunderbay.i18nDemo.views.SignInPage;
            import com.thunderbay.i18nDemo.views.forms.AlreadyRegisteredForm;
            import com.thunderbay.i18nDemo.views.forms.RegistrationForm;
            import com.thunderbay.i18nDemo.views.forms.dummy.SignInButton;
            import com.thunderbay.i18nDemo.vo.AccountVO;
 
            import mx.controls.Image;                    // Injection of data model
 
			[Bindable]
			public var account : AccountVO     = null;
 
 
            /**
             *  EventHandler when locales change to force the SignIn form to any existing
             *  validation errors. 
             */
            private function onClearValidationErrors(event:Event):void {
                var source     : SmartResourceInjector = (event.target as SmartResourceInjector); 
                var signInPage : SignInPage            = source.targetInstances[0] as SignInPage;
 
                if (signInPage != null) {
                    // Tooltip error messages are updated upon validation until validation recycles...
                    signInPage.clearErrors();    
                }
            }
 
        ]]>
    </mx:Script>
 
        <!-- 
             To clear validation errors, we have a special use of SRI to maintain reference to 
             class instance of SignInPage. After locale changes, we clear any validation errors. 
        -->
 
    <SmartResourceInjector bundleName="registration" target="{SignInPage}" localeChange="onClearValidationErrors(event);" />
 
        <!--
             SmartResourceInjectors for targeted class instantiations. Instead of dependency injection of UI instances,       
             we use SRI(s) here to remove any need to know view hierarchy relationships... we simply want a reference
             to the UI instance when ready.                                                                                   
        -->
 
    <SmartResourceInjector bundleName="registration" target="{AlreadyRegisteredForm}">
        <ResourceProxy property="alertTitle"                     key="signing.invalid" />
        <ResourceProxy property="eMailValidator.requiredFieldError" key="signin.validationError.email" />
        <ResourceProxy property="lblTitle.text"                 key="signin.title" />
        <ResourceProxy property="lblEmail.text"                 key="signin.email" />
    </SmartResourceInjector>
 
    <SmartResourceInjector bundleName="registration" target="{RegistrationForm}">
        <ResourceProxy property="registrationValidators[0].requiredFieldError"     key="register.validationError.firstName" />
        <ResourceProxy property="registrationValidators[1].requiredFieldError"     key="register.validationError.lastName" />
        <ResourceProxy property="registrationValidators[2].requiredFieldError"     key="register.validationError.email" />
        <ResourceProxy property="registrationValidators[3].requiredFieldError"     key="register.validationError.company" />
        <ResourceProxy property="registrationValidators[4].requiredFieldError"     key="register.validationError.postal" />
        <ResourceProxy property="registrationValidators[5].requiredFieldError"     key="register.validationError.phone" />
        <ResourceProxy property="registrationValidators[6].lowerThanMinError"     key="register.validationError.country" />
        <ResourceProxy property="registrationValidators[7].lowerThanMinError"     key="register.validationError.state" />
 
        <ResourceProxy property="alertTitle"                                     key="register.invalidProfile" />
 
        <ResourceProxy property="lblTitle.text"                                 key="register.title" />
        <ResourceProxy property="lblSubTitle.text"                                 key="register.subtitle" />
        <ResourceProxy property="lblMsg.text"                                     key="register.allRequired" />
 
        <ResourceProxy property="lblFirstName.text"                             key="register.firstName" />
 
        <ResourceProxy property="lblLastName.text"                                 key="register.lastName" />
        <ResourceProxy property="lblEmail.text"                                 key="register.email" />
        <ResourceProxy property="lblPhone.text"                                 key="register.phone" />
        <ResourceProxy property="lblCompany.text"                                 key="register.company" />
        <ResourceProxy property="lblZip.text"                                     key="register.postal" />
        <ResourceProxy property="lblState.text"                                 key="register.state" />
        <ResourceProxy property="cmbState.prompt"                                 key="register.prompt.state" />
        <ResourceProxy property="lblCountry.text"                                 key="register.country" />
        <ResourceProxy property="cmbCountry.prompt"                             key="register.prompt.country" />
 
        <!-- 
            Sample of elegant support for view state changes to fire localized resource injections: see "state" attribute
            When state is != "", then the injection only occurs for state == <ui instance>.currentState.
            We could specify the announcer/trigger for the state changes, but if not specified we use 
            the cached instance of the UI [RegistrationForm] class. 
        -->
 
        <ResourceProxy property="lblThanks.htmlText"         state="showConfirmEmail"     key="request.thanks.title"         parameters="{[account.fullName]}"  />
        <ResourceProxy property="lblThanksMessage.htmlText"    state="showConfirmEmail"    key="request.thanks.message"     parameters="{[account.email]}"    />
 
    </SmartResourceInjector>
 
 
        <!-- 
             SRIs used here when we expect multiple instances of class. We can inject specifically based on instance ID or
             we can also override the target Class desired on a ResourceProxy basis. 
             Here we are injecting localized PNGs into the <Image instance>.source properties
        -->
 
    <SmartResourceInjector bundleName="registration" target="{Image}"    >
        <ResourceProxy                                 targetID="btnRegister"  property="source"         key="button.skin.register"     type="class" />
        <ResourceProxy target="{SignInButton}"        targetID="btnSignIn"     property="source"         key="button.skin.signin"     type="class" />
        <PropertyProxy                                 targetID="btnFlagUSA"     property="toolTip"         source="{account}"             sourceKey="email" />
    </SmartResourceInjector>
 
</LocaleMap>

Here are some key points regarding the amazing benefits of using a LocalizationMap:

  1. All localized changes are in one file
  2. Compile-time type-checking is easily supported
  3. Runtime instantiation injection is supported (for deferred creation)
  4. Parameterized values are injected (see account.email)
  5. Property chains are supported
  6. Model changes can trigger updates with current localized values
  7. Skins and Embedded assets (images, sounds) are supported
  8. Easily extended to allow scan cross-referencing for errors is key names or orphaned keys
  9. Easily extended to allow transparent access to remote databases instead of compiled bundles
  10. Easily extended to allow runtime support for stylesheets

Best of all, is the fact that the LocaleMap can be used as with Mate, Cairngorm (2.2 or 3), Swiz, or PureMVC, or with a MVC framework of your choice…

Stay tuned for a new name “BabelFx

The BabelFx framework (aka l10nInjection) will soon have a new webstite which will be the same open-source framework but will also include better documentation and online resources.

Notice that LocalizationMap extends LocaleMap; which is one of the classes published in the open-sourced framework BabelFx (aka l10nInjection). Live online samples – with source code – are also available. Part #2 of this article LocalizationMaps – A Video Tutorial and Source Code has important additions that for interested readers:

  1. A video walkthru demonstrating to you the construction of the LocalizationMap and the logic that works behind the scenes.
  2. A localized version of this demo; with runtime support for switching between English and Spanish
  3. Illustrations that detail how BabelFx works with Flex applicatons
  4. Slides presented at 360|Flex





© Thomas Burleson for GridLinked to RUX, 2009.             Subscribe by Email | Contact Author             Permalink | 76 comments

]]>
http://www.gridlinked.info/amazing-i18n-solutions-for-flex-3-4/feed/ 76