GridLinked to RUX » Mate 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 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