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)[1] 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.

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.
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[2]. To use the built-in Flex mx framework solutions, you first
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[6] 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…
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:
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?
Now realize that I would not condemn an industry standard without presenting a viable – and superior – solution.
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.[8]
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!
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:
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…
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:
I’m struggeling with the flex resourceManger and found your I10NInjection framework. After reading the posts and looking at example code I’m not sure if the framework solves the issues that I have with the resourceManager.
1. I have some widgets e.g. comboboxes which are filled via a dataprovider like this:
the dataprovider is initialised in the onApplicationComplete method of the view
comboArray = [{label:resourceManager.getString('language', 'form.value1.label'), data:"test1"},
{label:resourceManager.getString('language', 'form.value2.label'), data:"test2"}];
How could I inject data in this comboArray.
2. We have our own controller AS classes in which Alerts are invoked. How can I inject data there? With the resourceManager it works fine.
Regards,
Mario
@MarioS,
1) The array you defined is a “model” used a dataProvider. But unfortunately your model does not support databinding, so any changes to that dataProvider will NOT be announced/dispatched to the comboBox. This problem exists regardless of l10nInjection changes.
When the localized resources change, you must manually rebuild the dataProvider (with new localized values) and reassign the new dataProvider to the comboBox. In the LocalizationMap, you want to add custom method [in your script block] to manually rebuild the dataProvider. You can also use the SmartResourceInjector to get the instance(s) of the views that are parents to the comboBoxes.
2) Your controller class(es) manage Alerts and their contents… so the text resources in those Alerts should be in a “model”. When the locale changes, you the values used by “future” alerts must be updated… the model data must be updated. Then your controller class(es) will use that model data. See again the link below for examples.
Here is online, sample code at GitHub. Review the l10.map.LocalizationMap.mxml class. See the method LocalizationMap::onLocaleChanged().
Hello Thomas,
after reviewing your sample code it is quite obvious how it works. It is really a nice and clean solution.
Is it possible to have 2 LocalizedMaps in one project? I ask because in our project we use a library which includes some views which are used in different modules. This library contains ressource files and the modules contein ressource files, too. From my point of view I would have to implement 1 LocalizedMap in the library and one in the module.
I would like to implement a proof-of-concept but I cannot find the download for the .swc file.
@MarioS,
Look at the the online sample “Employee Intranet“; which uses Mate MVC and l10nInjection. That application has two modules (Admin, Contacts), each module has its OWN LocalizationMap. For modules, any <mx:Module /> subclass can instantiate its own custom localization map; or you can use IoC to inject an instance upon demand. Be sure to separate resources shared to one map and module-specific resources to a locale map specific to that locale. Then you also probably want to load your resource bundles externally… so check out the Multilanguage FlexStore to see how external bundles are loaded upon demand.
I have implemented a small proof-of-concept but now I’m facing a new problem. I’m not sure if depends on the framework or not. The module for which I want to implement the internationlization makes use of a library which is used in different modules. This library contains also ressources files. In my module I can only refer to a key from the library resource file otherwise the label or whatever stays blank. Do you have any idea why it happens?
@Mario,
Make sure the resources are available (loaded) to the resourceManager; use resourceManager.getString(bundleName, key, null, locale) to check. If that standard call works, then l10nInjection should also work. Meanwhile, have you loaded the module as a child (with access rights to its parent code)? I recommend placing all shared resources in a bundle or RSL that is loaded by the main app. Then the modules, when loaded, can use the RSL code or the bundles resources.
Well done Thomas! That’s what I was looking for
Is it somehow possible to use multiple “Java-style” property files in a single folder (like headings_DE.properties, headings_EN.properties, …)? That would make the use of Resource Editors like JRC-Editor (http://www.zaval.org/products/jrc-editor/index.html) or tongue tied (http://code.google.com/p/tongue-tied/) possible.
@Stefan
Flex can compile the properties files and embedded in the application or compile and build an external resource bundle that must be dynamically loaded at run-time. The Flex/Eclipse compile-embed process expects each locale to be stored in its own directory with 1 or more properties files (and maybe data and assets sub folders). Note that when you build your Flex localized resource bundle(s) (e.g. for EN or DE), you also compile the localized Flex framework resources; which are in the Flex SDK directory. This means that both your own custom resources and the framework resources are compiled and used. The construction of the external bundle, however, is done with Ant and compc.jar… you must build your own Ant script to do this. With your own custom Ant script, you could distribute the Java-Style properties files into separate directories, compile, and then cleanup. In this way you could integrate with the TongueTied or Zaval tools.
Your ‘Framework’ sounds really great. It solves big problems working with localization.
And the SmartLocaleMap would be a great help for working with your framework. Is there any updates available when/where this SmartLocaleMap is available?
@Bazu
Sorry for the confusion. The SmartLocaleMap was renamed to LocaleMap. Check out the GitHub repository for the latest source code and samples. Does that answer your question or did I mis-understand?
By the way, it is exciting to see major software vendors starting to use the Localization Framework (l10nInjection); vendors like Animoto, oTouch, and more.
Thanks for the “enlightment”
But there’s another question.
For example there is a class which contains some public Strings with some messages. Errormessages for example.
Is there a way to inject these messages through a LocalizationMap?
Another problem occured when I tried to inject something to a PresentationModel in mate, which is also a class without an creationComplete-event. If I’m right, the LocalizationMap listen to this creationComplete-event, which isn’t thrown by this classes.
So is there any solution or do I have to think to another direction?
@Bazu
The l10nInjection framework does listen for creationComplete events from GUI classes, but it also be configured to inject when viewStates change, when models change, and when locales change. l10nInjection can also be used on models and controllers [as you asked], but the engine must have “instance” references in order to inject. Injection can also occur into static variables with a little manual effort. Check out the CafeTownsend Swiz +l10n demo. The source for that application uses a custom LocalizationMap; lines 46 and 70 illustrate how to inject into non-GUI targets. Check out all the l10nInjection samples… there are some great solution examples.
You’re really fast
Thanks for your reply, that answear my question but raise a new question.
Please forgive my poor skills and knowledge but I’m I right when I assume that this [Inject] metadata Tag is only available in Swiz and RobotLegs?
So in mate I need to use an EventMap for Localization to inject the selectedEmployee?
@Bazu,
[Inject] is a Swiz metatadata tag true… that auto-injects an instance reference into the LocalizationMap. If you are using Mate, Cairngorm or your own framework you must decide how you will assign references to model variables in your custom LocalizationMap. That solution is completely separate from l10nInjection.
You use EventMap(s) in Mate to listen for events, fire actions, and update models. You use a LocalizationMap to inject localized resources into GUI and non-GUI targets. You could use a LocalizationMap tag inside the EventMap and then assign model references to property variables in the LocalizationMap… but the specifics are up to you. Just realize that EventMaps and LocalizatioMaps are two separate solutions.
Will it support robotlegs framework ?
@Sparky,
It should work without any issues. Simply include the l10nInjection.swc in your project, build a custom localizationMap and resource bundles… and then BAM! you have multi-language support.
l10nInjection has been tested with Flex3 and Flex4, with Cairngorm, Swiz, and Mate. They all work great. In fact, IoC frameworks like Swiz (and RobotLegs) are the easiest to use with l10nInjection.
I have a problem with injection and Flex 4 with its new “includeIn” syntax.
For example, if a label is not “includedIn” a component’s default state, injector will ignore it as the label hasn’t been created yet.
Since the injector doesn’t know when the label is created due to a state change, the label’s text stays empty.
What is the best way to go about this?
Would I need to create a public string variable to store the injected string and bind my label’s text to it?
This also applies to the contents of lazily created views in ViewStack/TabNavigator when creationPolicy is not set to “all”.
Am I missing something here?
Thanks.
@James,
Are you using the versions 1.0b1.1 or greater ?
The l10nInjection engine listens for STATE_CHANGE events in Flex4. And lazy instantiation is supported.
Have you checked the ViewState sample (#4)? It demonstrates both view state changes and ViewStack creationPolicy.AUTO.
Hi Thomas, I forgot to mention in my first post that I think your framework is excellent. Also for states, I must have missed the “trigger” property in the ResourceProxy that will listen to state changes
I still have a question though, the example used ResourceInjector to achieve this.Which is simple if you can access the view. But what if you use SmartResourceInjector for deferred views, is there a way to set the ResourceProxy’s trigger property to the injection target’s instance?
Thanks a lot!
@James,
You can set the trigger using <ResourceProxy state=”myCustomState” trigger=”{someInstance}” />. But this works only if you have a reference to the trigger instances; which is problematic using SmartResourceInjectors. The l10nInjection has been enhanced to “scan” each target instance (in the SmartResourceInjector watch cache). If the instance does not have states configured, then the parent is scanned… and so on. This allows you [for example] to smart inject in to a button instance but use the parent/document UI instance to define states… the smart injector will detect that and auto-add listeners to the document for STATE_CHANGE events; so inject re-occurs.
States and Trigger scanning works great in Flex with l10nInjection… give it a try.
Hi Thomas, I don’t know why you chose remove this part from my last comment but here it goes again.
Injection into lazily created Canvas/HBox/VBox works fine but does NOT work for NavigatorContent.
If you change the root tag of a deferred view inside a ViewStack to in the ViewState sample you mentioned you will see what I mean.
@James,
I updated the ViewStates demo source; the latest code all appears to work with Halo and Spark, with viewstates, and with l10nInjection changes. Please update both your l10nInjection library (from GitHub) and the ViewStates demo source. Recompile, clear your browser cache, and debug. After that, can you confirm it works as you expect?
Meanwhile check out the online demo with “view source” enabled.
Hi Thomas,
I tried the viewState and if you change the language without change the ‘Department’ tab on the second tabnavigator first, then there is no injection on this tab. The tab will show the old title ‘Department’. I guest this container is not ready so that the injection may not occur. Would you please take a look
thanks
Good work.:)
@Hung,
The tabNavigator has a creation policy of Auto; which means the Department contents are not created until that tab is selected for the first time. Thus injection will fail since the tab contents do not yet exist.
Meanwhile the tab label actually uses the “label” value of the content [Canvas] to set the text… but if the content creation is deferred, then the compiler uses the original “compiled” value (which is en_US “Departments”. Now when the viewStack children are instantiated, l10nInjection modifies the “label” property of each child. That changes triggers an “labelChanged” property change event which forces the TabBar to update its tabs with the new localized strings.
The l10nInjection is working properly. But the application code has not coded to handle this rare case of deferred children and localized labels within data-driven TabBars. We have a supported list of Feature requests and Issues logged in GitHub. Feel free to add to that list.
Hi Thomas,
we just started to use your localization framework and the proof-of-concept we created with your framework worked like a charm
Now we want to implement the framework to our application. In order to do that, we generated a LocalizationMap and the corresponding bundle files. Our current generated LocalizationMap is about 400 lines of codes. Our problem is that memory footprint of the application tends to increase as the LocalizationMap increases. Our current memory footprint is about 800MB with a LocalizationMap of 400 lines of code.
How can we solve this problem?
Here’s a snippet of the LocalizationMap in order to give you an idea:
Hope you can help us because we really believe in the strength of your framework.
Thanks in advance!
Mathieu
@Mathieu
800 Mb memory usage? Wow!
The l10nInjection framework is very lightweight. And the localizationMap simply references existing components in your bytecode. So these are not the culprits for the large memory usages.
Instead I image that you are using embedded resource bundles; perhaps including large bitmaps or non-Roman font sets?
BTW, the snippet you mentioned is not visible, so I could not review that portion.
Hi Thomas,
When I tried to load twenty resources bundles of two languages into my application. It takes too much time to load it.
I disabled debug mode, and compiled resource bundles into swc but it doesn’t help.
Do you have any idea how to fix this?
Thank you for the amazing work.
@James,
20 properties files per locale? That should not be an issue unless you are including large font and images in the bundle also?
How big is the compiled resource bundle (swf)? Are you embedding the compiled resources in your application. That would definitely impact application load/startup time.
For an answer to your issue, check out the notes on l10nInjection Q&A
Hi Thomas,
Thanks for your quick reply!
I embedded the swf bundles into the application but it is still the same. It takes 3 minutes to start the application.
When I run the application in debug mode, I check the variable LocalizationMap:LocaleMap and I see the structure like this:
LocationzationMap:
SmartResourceInjector 1
SmartResourceInjector 2
…
SmartResourceInjector 20
In the SmartResourceInjector 1, the structure is like this:
SmartResourceInjector 1
map: LocalizationMap
And in the map: LocationzationMap, the structure is the same as the LocationzationMap above and so on.
It seems that the map property of the SmartResourceInjector 1 contains the SmartResourceInjector 1 itself. I guess the recursions here make the application very slow
Is it supposed to be that way or is there any way to fix it?
Thank you.
@James
Do not use recursion. You should not nest LocalizationMaps… but you may have one custom Map for each module and one for the Application. You should not use more than that. LocalizationMaps should only have [in general] Injectors and Proxy child tags:
LocaleMap
– SmartInjector
—- ResourcProxy
—- PropertyProxy
– ResourceInjector
—- ResourceProxy
—- etc.
Hi!
First, very nice work!
I have a question: Can LocalizationMap and the DI principles described here be used WITHOUT ResourceBundles?
I have the following situation. My localized strings (only strings) come from a database backend (not a bundle). I’d still like to use the injection mechanism. I’m using Mate.
But as far as I can tell, the PropertyInjectors do not support the kind property chain expressions as ResourceProxy does.
So, is it possible to extract/use some part of your great l10n library for accomplishing this chain-expression injection? Or am I thinking too complicated here?
Thanks,
Seance
@Seance
Please see l10nInjection Q&A for a detailed answer regarding injecting with remote databases.
PropertyProxies should support the same syntax for property chains as that used in the ResourcProxy. See PropertyProxy lines 168-169. Please review sample RegsitrationDemo line 106. Please note that PropertyInjectors are not part of the l10nInjection framework. For property injection of model data with localized resources, we use a PropertyProxy.
Can you provide a sample of the issue?
[...] Thomas Burleson has created an great way to localize Flex applications. by using mate. He has created a class called LocalizationMap where he emulated the EventMap concept to handle the localization. [...]
Hi, I have a couple of questions…
Does this approach works with dinamically loaded modules?
Does SmartResourceInjector only works using h creationpolicy=all in containers?
(I would like to see a more complex example… using multiple forms and multiple modules…)
Thanks for your help.
1) If you want to use modules, I recomend creating a custom LocaleMap for each Module. Then when the module is created, the map is created (by that module) and injection occurs.
2) The sample application uses CreationPolicy.ALL, but AUTO (or others also works).
Remember that SmartResourceInjectors “listen” for creationComplete on UIComponent class instantiations. So if you create a custom non-UIComponent that you want to use with SmartResouceInjectors, you must manually dispatch a bubbling FLexEvent.CREATION_COMPLETE event.
If you would like a more complex example, then I encourage you to hire me to create one
LocalizationStageProcessor looks easier:
(see 2.1.2.25.1. The LocalizationStageProcessor class)
http://www.springactionscript.org/docs/reference/html/container-documentation.html
http://www.springactionscript.org/asdoc/org/springextensions/actionscript/localization/LocalizationStageProcessor.html
Probably springactionscript’s approach could be easily integrated with code generation tools.
I`l give it a try…
Your approach (or any other approach) could be better if a developer could generate SmartLocaleMap (or ResourceInjector …or other files …)
from existing files….
(for example… what if an app has more than 100 mxml files???)
Well.. It seems that I will have to create my own code generator… (and reverse enginnering tool…)
The LocalizationStageProcessor [form SpringActionScript] uses a resourceBundle naming convention as an attempt to auto-define resource mappings and auto-inject. That is an interesting concept and the l10nInjection perhaps could benefit from have that as an “option”. Unfortunately, that auto-mapping means that ONLY locale changes will trigger resource injection. The l10nInjection supports 4 triggers: locale changes, parameter changes, data model changes, and view state changes. This is a HUGE deal in real-world applications; critically important for enterprise Flex apps. I can imagine many areas where the LocalizationStageProcessor will not be sufficient for my applications. Also Spring Actionscript is XML-driven, which means that you lose the benefits of compile-time type checking and code hinting [which is available in l10nInjection].
Both LocalizationStageProcessor and LocalizationMaps (l10nInjection) could benefit from a code/mapping generator. Absolutely true. In fact, I am interested in having the LupoManager generate LocalizationMaps as an option. Or perhaps I should write a Groovy parser.
“The l10nInjection supports 4 triggers: locale changes, parameter changes, data model changes, and view state changes.”
Interesting… that`s a really cool feature
the “traditional” approach implies something like this:
[Bindable(event="languageChange")]
public function get myData():ArrayCollection{ …
LupoManager … wow… really useful!!! thanks for that link!!
LupoManager is a great idea.
Just remember that LupoManager mangles the view code… one of the main reasons I created the l10nInjection framework.
I do not recommend using it to modify your code (until it supports l10nInjection), but I do recommend it to quickly scan and generate properties files.
Then use l10nInjection to create a LocalizationMap and – voila – you are almost done…
Does this solution rely on features in the Mate framework? I’m thinking of adapting it to run under RobotLegs, using either Swift Suspenders or SmartyPants IOC for the DI functionality. Can you foresee any issues with this?
Best regards
The Mate framework is not required.
While the l10nInjection engine uses some Mate core classes, those classes are also included as part of the source for the l10nInjection library. You should be able to use ANY Flex framework (Suspenders, SmartyPants, Swiz, Cairngorm) simultaneously with l10nInjection. That is part of its “beauty”. I highly recommend that you do NOT adapt l10nInjection as an effort to embed it within other frameworks. You may want wrappers for l10nInjection for your desired framework. But if you literally want to modify the l10nInjection engine… I would ask “Why? That is like fixing something that is not broken.”
I do, however, recommend that you read the tutorial blog for full details, samples, and links to the GitHub repositories. Meanwhile, the Mate architects are merging the l10nInjection extensions into the Mate framework as a parallel effort.
Hi,
I think you method may be complex……
I made my solutions in a blog:
http://www.cnblogs.com/WideWeide/archive/2010/03/16/1687668.html
I think it may be more convenient
The l10nInjection – under the hood – is performing some complex things. But Flex is a complex framework with many variants of implementation and event flows. The good thing about the l10nInjection is that its use by developers is pretty simple and easily understood since a LocalizationMap leverages the use of MXML declarative tags. And the framework easily accommodates enterprise solutions (with hundreds of views). And – best of all – only 1 single file needs to be created for your entire application or for each module.
I looked at your classes and usage on your blog. Thank you for your creative thoughts for an alternative. The biggest issue with your solution is that a tag or instance is required within each view.
You do have an interesting approach in which your properties file uses key names that must MATCH the field ids in the views. So a explicit mapping from key to UI target is not needed. Unfortunately this approach produces a fragile coupling that can have scalability, maintenance, and debugging problems (especially if anyone every changes a instance ids).
I am not sure that you realize the full power of using the l10nInjection framework. Recall that at least four (4) different factors may trigger injection:
1. Locale Changes
2. Data model changes (for parameterized tokens)
3. View creation
4. View state changes
Your solution only partially addresses (1). My framework handles ALL of those concerns without any view changes at all. The biggest problem, however, is the use of your l18nInjection tags within views. This is a serious violation of IOC (inversion of control). While yours works and is better than the direct Flex ResourceManager use, it is not as powerful as the l10nInjection framework.
Here is a link to some diagrams that may help with understanding better the l10nInjection concepts. Thanks again for your creative ideas.
best of all – only 1 single file needs to be created for your entire application or for each module
—–
Premise: All visual components can be application to access,is it right?
so in LocalizationMap there is “signInPage.frmRegister.lblTitle”,if using PopUpManager.createPopUp ,only 1 single file is OK?
====================
Recall that at least four (4) different factors may trigger injection:
1. Locale Changes–OK:)
———-
2. Data model changes (for parameterized tokens)—Just implement IResourceManager & IResourceBundle that retrieve data from database or others…
———–
3. View creation–your locale:LocalizationMap locate in view too? in fact I provider a static function I18nInjection.injection(target:Object,bundleName:String=null). That bothers me too,where to injection? at last I use the tag within wiews,and I can use the setEnabled(false) to disable it.
—–
4. View state changes
What’s the relation of View State and i18n?
Is Not directly related to …,I think.
l10n injection must have references to target instances for the injection to work.
So if you have views show in PopupManager, you should use the SmartResourceInjector; which will detect new view creation and inject the localized resource in the Popup view.
How are view states and i18n related? Consider the power of view states (especially in Flex 4). View states allow components to change their representation based on context or “state”. These changes can be layouts, content, skinning, etc.
All of which may need to be localized appropriately. So view states changes withOUT l10nInjection support would make any localization solution worthless.
Thankfully l10n injection now works with Flex view state changes by internally listening for StateChangeEvent.CURRENT_STATE_CHANGE events. See Sample for an example of how a localization map should be configured. Notice the state=”small” trigger=”{ui}”attributes which essentially says “when the “ui” instance announces CURRENT_STATE_CHANGE, then inject localized text IF the current state is “small”.
Hi! I found the new version of this tool and tried to apply it in our project. One more question is that the performance. If the project is quite large and the map is thousands of strings, what about the performance? Another question is about cache. Does this tool cache the strings if two are the same? Or should I create a cache map?
Liang,
Thanks for the great questions.
1) Caching: l10nInjection does not cache strings or resources. Internally it makes resourceManager.getString() (or equivalent) calls, so if caching or memory issues occur it would be at the ResourceManager level.
2) Large Localization Maps: Remember that without l10nInjection, you would still have ALL those resourceManager requests from each view. LocalizationMaps define the sequence of calls and centralize all mappings, and inject. Additionally LocalizationMaps also determine if the target still exists before performing the internal resource lookup.
The only source of potential issue is if you have significant uses of data binding in the maps. If you have a very large localization map, I would be very interested in the results of a Flex Profiler investigation. Let us in the community know…
Thanks again,
ThomasB
Hi Thomas,
I used your localization framework and it works quite good except one thing I found. I’m not able to inject an array of strings, i.e. for a calendar component. Is it possible from the LocalizationMap at all? Nowadays I use the resourceManager.getStringArray() method.
Thanks for your suggestion.
Checkout ResourceInjector.as (line 404). The getStringArray() is supported but you must tell the ResourceProxy instance to that the localized bundle data is array; this is done by setting the “type” attribute of the ResourceProxy (please note that the default type value is string).
For example, let’s consider a signup form (id == frmSignup) with a Calendar control. We want to inject localized holidays (as array of dates) into the Calendar::holidays property. That information is in the schedule.properties file with the key scheduler.officialHolidays. So we use the following:
<ResourceProxy target=”{frmSignup.myCalendar}” property=”holidays” key=”scheduler.officialHolidays” type=”array” />
- Attention Developers -
All source code and sample apps are now available on GitHub:
http://github.com/ThomasBurleson/l10n_Injection
http://github.com/ThomasBurleson/l10nInjection_Samples
Hat off to you mate, this is exactly what I was looking for, thanks heaps for the post!
I’m using your localization framework with Mate and it works great for most Flex components except Renderers like AdvancedDataGrid Renderers because the grid instantiates a new Renderer per row and you don’t have a handle to the created object. Any thoughts or suggestions on how this would or could work with that?
Thanks.
Al V.
Al,
Lists and Grids use custom item renderer instances to render row information.
Consider the renderer “ADGRenderer1″ with contains a button “Edit” and some text [defined based on the set data(src:Object)]
Say your requirements need to localize the Edit button using l10n injection; the text is based on dynamic data that is not dependent on localized bundles.
You can use the SmartResourceInjector that “listens” for a creationComplete event from instances of the registered UIComponent class.
All instances are automatically cached by the SRI and current locale information is injected as defined in the ResourceProxies.
And any localization changes are immediately used to re-inject into those cached instances. E.g.
Hope that clarifies some advanced features of the l10n IOC
Sorry to trouble you again. I’m interested in this project but it can not work with flex 4 very well. If you use ResourceProxy on a NavigatorContent’s label attribute in flex 4, an error “can not convert from NavigatorContent to mx.core.Container” will occur. Maybe this is a bug?
Liang,
Can you provide a test app (.zip archive). I will debug and resolve quickly for you.
Thx,
ThomasB
Cheng,
I just tested your sample app. The Flex 4 error you encountered is a Flex 4 source code BUG in the Accordian.as class (line 2475).
This bug has nothing to do with the l10Injection solution.
Be sure to read Part 2: LocalizationMaps – A Video Tutorial and Source Code
Hi! I’m new to this post and I tried to added this swc file into my own project with mate framwork. But the file I wrote for translating is not work well. The strings need for translating is disapeared so I know it works but no translated strings added. Therefore it is comletly empty. I don’t know why… Please give me a hand. Thank you.
Liang,
1) Confirm that the ResourceManager loaded bundle.
2) Confirm that your fonts are available (if embedded fonts, these must already be loaded)
3) Confirm that your bundles compiled properly. Did you compile with unicode ranges? Are you using Chinese glyphs or English fonts?
As you can tell by my questions, these issues can be complex. I am available as a consultant if your company wishes to engage me to resolve these with you.
Best of luck,
ThomasB
Thanks for your help! I thought the ResourceBundle will be set automatically because I have set the field “bundleName” on ResourceInjector, so I didn’t add the Metadata as usual. When I added this everything is OK.
You’ve done a good job! Thank you!
Sorry, I have another question. I used this extension in Flex 4. It has the new state syntax. For example the label has different text attribute: text.main=”MAIN” and text.sub=”SUB”. How can I do this in the extension? I tried property=”text.main” and it seemed this can not work well. Maybe I had something wrong again? Thank you for answering me.
Liang,
Flex3 and Flex4 state changes are not yet fully supported in LocalizationMaps:
1) The ResourceInjector must listen for EnterState events dispatched from targets and target parents.
2) When the state changes, all ResourceInjectors (for that target state) need to re-inject the localized resources.
I will release an updated lib soon that will support State changes in Flex4.
Liang,
Thank you for the sample/demo app that illustrates the view state “bug”.
View states are NOW supported!
I have fixed the l10n Injection framework and your sample app.
Here is a link to an archive with the enhanced l10nInjection.swc, the framework source code, and your sample app. Please review the readme for details on specific changes.
Thanks for the feedback. Viewstates rock.
Stay tuned… soon I will write another blog entry that demonstrates the power features of l10n Injection and view states!
Liang,
The ResourceInjectors (and LocalizationMaps) do not need ResourceBundle tags in view code; but you must insure that the localized bundles are properly loaded. And dynamically loaded bundles must still be manually loaded upon demand.
Setting the MetaData [ResourceBundle()] tag only tells the compiler to link the compiled bundles into your application; you should not need to use the MetaData ResourceBundle tags.
Tom,
I’m getting error: unable to open ‘\l10nInjection\bin\l10nInjection.swc’
Is that one is missing in the zip sources?
Thanks.
I’m also seeing some dependencies on universalmind sources:
@ i18nController.as
import com.universalmind.cairngorm.control.FrontController
@ LoadAccountEvent.as
import com.universalmind.cairngorm.events.UMEvent
@ SaveAccountEvent.as
import com.universalmind.cairngorm.events.UMEvent
Please advise…
Dmitry,
I updated the LocalizationMaps.zip file to include the libs directory and a Readme.txt.
The libs dir now contains the l10Injection.swc, the CGX.swc (Cairngorm Extensions), and the Degrafa.swc… all used in the RegsitrationDemo app. The error with the UMEvent dependency is due to the fact that you need the Cairngorm Extensions library; which is now contained in the libs directory. The ReadMe.txt (in /l10n-src/*) also describes how you can rebuild the l10nIjection.swc; if needed.
Hope this helps. Thanks for the feedback regarding the source.
[...] Amazing i18n IOC solutions for Flex [...]
Awesome post Thomas! I am really looking forward to being able to use the same technique, as i am developing several projects with Mate
Do you have any updates or info on when the SmartLocaleMap will be made “public”?
Best regards,
Paw Suddergaard
Thanks Paw.
On Monday, I plan to publish “Using IOC for Flex l10n – Part 2″. Part 2 will contain both (a) a video on why and how to use the LocaleMaps for l10n IOC and (b) a live demo of a registration form with support for both English and Spanish. I will also publish the source code to the demo and the source code to the LocaleMap module. So stay tuned…
BTW, LocaleMaps can be used with Cairngorm, Swiz, Mate, or your own custom architecture.
Hi Thomas – Please put me on your “white paper requests” list – Thanks, Douglas
I see you’re loading the localizations from bundles. I misread the code snippets.
Tim,
You are correct.
The ResourceManager still uses runtime loaded (and compiled) resource bundles. And when the ResourceManager announces that the locale has changed, then the LocalizationMap does its magic.
The nice part about this approach is that if you added code to load locale strings from a remote database and integrated that “data load” with the ResourceManager, then the LocalizationMap would STILL work and the views would be updated again. This approach completely decouples the view updates from the mechanics of loading the resource strings/bundles.
Another great reason for LocalizationMaps is that the view updates occur upon any of 3 [independent] triggers:
1) when the ResourceManager announces locale changes
2) when new views instances are created… or
3) when parameterized values change; such as user fullname, etc.
This flexibility significantly reduces l10n complexities in your RIAs.
Social comments and analytics for this post…
This post was mentioned on Twitter by adobelove: Amazing i18n IOC solutions for Flex: Flex allows the developer to localize strings, skinning code/classes, and .. http://bit.ly/40rnzv...
Hi Thomas,
Thanks for article and for mentioning Lupo Manager & Lupo Translator.
I think your article does a great job at highlighting the various pitfalls of the “standard / Adobe documentation”(ResourceBundle metadata + ResourceManager reference in all your views) Flex i18n technique.
I’d just like to mention one or two things here.
First, the standard “Adobe documentation” technique is not the only way to work with Lupo, and in fact it’s not even recommended. You might have been confused because of my video presentation which demonstrates this (bad) technique. I did hesitate a lot before showing this technique in the video, but I thought doing otherwise might confuse people. I now realize that I was probably wrong.
I personnaly never work this way for my own applications or for my clients. That’s the reason why Lupo allows the user to work in a completely different way which is the way I recommend : using presentation pattern.
The use of a presentation pattern, like the Presentation Model for instance, allows the developer to externalize the i18n logic in a dedicated class that can be used through composition inside the presentation layer. In my own experience, that’s the best way to deal with i18n. Lupo was made to implement this technique, not the “Adobe documentation” technique.
Now, I realize that your approach also has significant advantages. Most are shared with the presentation pattern based techniques (like the fact that we keep our views “clean” of all the i18n garbage), but some are not.
One drawback I couldn’t help noticing in your approach is that it looks like you have to give an id to all your localizable views. Another is that your seem to have one i18nController instead of sharing it throughout all your views. Also, I’d like to see exactly how your ResourceProxy was implemented.
All in all, I think that is a very interesting approach, and I can’t wait to learn more about it.
David,
.
Thank you for the great feedback. While I lament a current “standard”, at least Adobe Flex offers a better solution than the Flash IDE
I agree that the presentation pattern is superior to the Adobe i18n techniques. More than likely your views use a databinding “pull” technique to self-update when the presentation model announces changes. This is a standard Flex technique.
I have found the IOC approaches, however, to be much more satisfying and robust… hence the LocalizationMap solution. With IOC, the views truly do not “know” about outside datafeeds. With IOC, views simply render data and state and announce user interactions. The data is somehow magically assigned
.
Two comments:
1) The i18nController is part of the Cairngorm MVC framework; not part of the Localization solution.
2) You do not need view IDs for the LocalizationMap to work. See line 79 (above) in the LocalizationMap source code.
On that line we see:
We this syntax, we auto-magically capture and remember all instantiations of the RequestNotification class. Whenever the locale or data models change, the SmartResourceInject re-injects into all those instances. A major benefit of this mechanism, is that it allows you to inject into instance attributes regardless of the instance’s relationship/nesting in the application’s view hierarchy.
Stay tuned for my video, where I will discuss all of this. I am sure you will love the SmartLocaleMap IOC approach for Lupo.
Best regards,
ThomasB