In a previous article, I blogged about Flex i18n[1] 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.
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)!
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.
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).
excellent work!
Wow. I must say this is seriously a nice improvement to my Flex project. I’m currently porting a Flex3/Cairngorm project to Flex4/Mate and your library really simplifies my life. No more intrusive localization code in my views, this is fantastic. Even more, it’s faster than only using Flex’s resource bundle.
Great job and thanks!
@Mathieu,
Performance has always been a critical focus. I am glad you like the framework.
The next set of features will include an online tool that uses Google Translation API to auto-translate your properties files.
l10nInjection will soon have a new name: “BabelFx” And stay tuned for a better site and documentation.
Great stuff!
One question concerning the size of the file itself… would it make more sense to split the file in sections (say per screen or logical business section) rather than having one big file with all the mappings for the entire application?
@Marco
Great question! Remember that the map is intended to centralize all localization injections for the application. The only time, IMHO, that using multiple maps makes sense is when you are using Modules or dynamically loaded sub-apps. I recommend 1 LocalizationMap class per module and 1 per application. The Intranet demo show this and it works great. Minimizing LocalizationMaps helps you maintain context and centralize management.
Now you may ask about the performance impacts when using larger localization maps… One developer has over 400 ResourceProxy tags in their custom LocalizationMap. The LocalizationMap [extends LocaleMap] performs very well and has a very lightweight impact on both memory and CPU. In fact, the l10nInjection engine actually performs faster and requires less memory than if you used traditional approach of data binding with the resourceManager. The traditional approach is NOT a good solution and does not scale nearly as well.
Thank you very much for the presentation and the I10N injection framework. It is so nice to work with.
I have a question though. One of my parameters is a date.
But as far as I can see I have no way to specify my formatString as part of the message.
I.e: “Case closed on the {0}”
I would have loved to do something like this:
“Case closed on the {0,YY/MM/DD}”
But as far as I can see you are using the StringUtil to substitute the parameters. And then that is not possible.
Or maybe I’m missing the point of how to work with dates.
Any help would be appreciated
Regards, Flemming
@Flemming,
You are right that the l10nInjection only expects to inject into target properties; it does not format parameters but it will do parameter substitution. The StringUtil.substitute() is a printf() analogy and is an accepted programmatic standard.
Consider the tag <ResourceProxy property=”text” key=”caseClosed” parameters=”{[dateClosed]} /> where the localized value for “caseClosed” is “Cased closed on the {0}”. The dateClosed should be a string that is ALREADY formatted; otherwise you are attempting to put logic or DSL syntax in localized resources. Now if the dateClosed format depends on the locale (which it usually does) then I recommend that you inject the date format string into a target property BEFORE you inject caseClosed; you can inject the date format using target implicit setter function set dateFormat(val:String):void { … }. Then the date is already formatted when you inject the value for caseClosed.
As you can tell, there are a myriad solutions and complications to Localization… that is why I created the l10nInjection. Hope this helps. Good luck.
That is a very interesting solution. May I commend you on your excellent doco/video and support.
The only thing is there’s a bit of typing to do (ok, unavoidable in message-bundle-based i18n solutions). Have you considered utilising a bit of convention over configuration? For example, would it be possible to default the property to “text”. That way if you want to apply your label to the text property you simply wouldn’t have to specify a property at all in your xml.
@Fletch
Thank you for your kind words and suggestion regarding a default “property”. < ResourceProxy … /> can be used to injection resources into any public property or mutator. This means that colors, sizes, text, skins, states (etc.) all can be changed via localization injection. My concern is that the ResourceProxy default property “text” may not be an intuitive default. If we made that change, however, and the default “text” property did not exist on the target instance, the l10nInjection framework could still LOG.warn() a warning. Let me deliberate and consider the impacts.
- ThomasB
Hi Thomas,
The framework is very powerful.
Thanks for sharing this framework and thanks for your support.
Hi Thomas,
It’s necessary to make a localization in a DatePicker/Calendar component.
How can I do to inject the array values in the properties: dayNames and monthNames.
Thanks
@Diego
That is pretty easy. When using a ResourceProxy specify a type attribute equal to “array”. In your properties file you should use a key whose value is a comma delimited string. Let’s consider the properties file “data.properties” witha key/value pair such as:
calendar.myDayNames = Sabido,Domingo,Lunes,Martes,Miércoles,Jueves,Viernes
Then in your custom LocalizaitonMap, create an injector for the date picker:
<SmartResourceInjector target=”{DateChooser}” bundleName=”dates”>
<ResourceProxy property=”dayNames” key=”calendar.myDayNames” type=”array” />
</SmartResourceInjector>
I had a nice surprise this morning. It seems that as soon as I set my application’s locale to French (fr_CA), all date related objects show French strings. One thing really annoys me though: month names start with a capital letter in French, and they shouldn’t. Do you know where Flex takes these names? SDK? Flash Player IE/Firefox plugin? Even the DateFormatter uses those. It would be great if I could only fix this capital letter problem without specifying my own month names using injection.
Thanks for any idea you might have.
@Mathieu,/frameworks/locale/{locale}. You may need to use CopyLocale.exe to create a localization bundle for a locale that does not already exist.
The framework localized resources are part of the SDK’s pre-compiled resource bundles; e.g.
Once you have your custom resource and the frameworks bundles, you can either embed the localized resources into your app (using compiler arguments) or load from external, compiled resource bundle SWFs.
RE> Where are the specifications for the names of Dates (months)?
For the French locale, check out:
Hi Thomas,
Beatyful idea I wonder if you have used/integrated your localization maps with patterns like presentation model or passive view.
kind regards,
Polaco.
@Polaco
As long as the LocalizationMap has a reference to the model or view it can inject into the instance or pull-from the instance to use in the paramaters attribute.
See the Mate Employee Intranet application for source examples.
[...] Check it out ! [...]
For basic localisation, I got this running without much effort. Now I’ve hit a snag combining parameterised text with a SmartResourceInjector.
In your registration example, you show parameterised values being pulled from a VO in the model. What I need to do is populate them with values from the class that’s currently being localised.
For instance, in my properties file I have this line:
item.position= {0} of {1} items
In my … I’m trying to do this:
I’ve tried this.index, MyClass.index, and other variations but I can’t get the index and total values from MyClass.
Hope you can help!
@stephan
As long as the parameterized values are coming from a [Bindable] source then changes to the source will announce propertyChange events and the injectors will refire with the updated source values. So if your class instance is referenced as “inst” then the following should work:
< ResourceProxy key=”item.position” property=”txtInfo.text” parameters=”{[inst.propertyZero, inst.propertyOne]}” />
Is your class instance (that is being localized) also [Bindable] and does it announce change events properly?
Attention developers!
Both the l10nInjection library and the sample apps on GitHub have been updated.
I fixed significant internal issues with the SmartResourceInjectors. And I fixed a Flex 4 issue with the LocaleMap and SmartResourceInjectors.
The ViewStates sample app has an example of using DataGridColumn for injection:
<SmartResourceInjector target="{DataGrid}" bundleName="commons">
<ResourceProxy targetID="grdAccounts" property="columns[0].headerText" key="title.normal" />
<ResourceProxy targetID="grdDepartments" property="columns[1].headerText" key="introView.title" />
</SmartResourceInjector>
And the RegistrationDemo sample app (LocalizationMap.mxml) now has sample of using targetIDs in SmartResourceInjectors as well as sample use of PropertyProxy to inject data model changes in targets.
<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>
Checkout the latest code and samples. I think your feature request will now work as expected/desired.
I just published online slides for a presentation given at 360|Flex earlier this month. The presentation “Building Multi-language (i18n) Flex Applications” has also been posted in the blog (above). The slides have more conceptual diagrams and more learning content.
Check it out!
Hello Thomas,
excellent work!
What do you recommend when you have a project where:
- all views extend SkinnableComponent;
- all views have a corresponding skinClass (extends Skin);
- the user can change the skin at runtime.
When the user changes the skin, all the localization is gone. I have the following localization map:
[ResourceBundle("airgile")]
My LoginView extends SkinnableComponent and has a
[SkinPart]
public var welcomeLbl:Label;
I have two LoginViewSkin files, in different packages, which correspond to different skins that the user can switch. When the switch happens, the welcomeLbl is reset with the new skin, but Mate injectors are not triggered, and so no localization is set for the new skin.
Any recommendations?
Joao,
Can you create an issue report on GitHub“? Please attach a source code for a sample application that demonstrates the issue. I will then fix/enhance the l10nInjection framework.
Thanks, ThomasB
Attention Developers!
Source code and sample projects are now under version control and available on GitHub:
Source Code
Sample Applications
Great! I have added a setup guiide on your github wiki for how to setup flashbuilder 4. http://wiki.github.com/ThomasBurleson/l10nInjection_Samples/
Hope it helps!
Great solution. Has anyone tried this with a XMLList with menuitem elements which is DataProvider for a MenuBar? I’m able to set the “label” property via map, but the change does not appear. Regards
Sascha,
I imagine you are replacing elements or attributes of elements in the XMLList? If so, then the menubar will not update because XMLList does not support databinding notifications. Or are you injecting an entirely new XMLList instance?
Note that your resource bundles can contain embedded XML datasets. These datasets can then be injected on the fly by the l10n injectors.
Please report this as a bug or issue on l10n Injection Git Hub. It would be helpful if you also attached [to the issue] source for a sample app that demonstrates this issue. I will review and attempt to propose or provide a viable solution.
-ThomasB
Thomas,
indeed I’m replacing attributes of elements in the XMLList. Placing the datasets in the resource bundles sound like a workaround with quite a lot redundancy, since only the label changes. But when XMLList does not support databinding notifications, this will be the only possibility, won’t it? I’ll report this issue to your Git Hub soon. Anyway, once again: great solution
Thank you for your support Thomas
Sascha,
Here is a suggestion.
Embed the raw xml in the properties file; e.g.
calendar.holidays = Embed(‘assets/data/holidays.xml’)
So each locale has its localized version. Then use l10nInjection to inject the xml into an implicit setter on your view. The setter will use e4x to determine the repeating attributes which will then be assigned as localized dataProviders.
great article!
I tried to create a ResourceProxy programmatically doing:
var rp:ResourceProxy = new ResourceProxy(obj,
“label”,”app.control.bar.maintenance”);
But Error 1009 is traised. (obj is not null)
¿is it this correct?
thanks!
You discovered a bug in the ResourceProxy constructor; a bug which the <ResourceProxy /> tag implementation sidesteps.
I have fixed that issue; please check the Git Repository for updated code.
- Developer Notes -
ResourceProxy only establishes the mapping to the target. It does not do the injection. It is the ResourceInjector or SmartResourceInjector that iterates all the ResourceProxy instances and performs the injection action for each. So when using a programmatic approach, don’t forget to register your ResourceProxy instances [1 or more] into the registry property of a <ResourceInjector /> instance.
Hi, yes it’s very nice solution. I really like your idea. But. I understand how to set ResourceProxy target for view components inside main window via chain references.
But I have no idea how can I set ResourceProxy target for e.g. Labels placed in view which is dynamically created via PopUpManager? I don’t have reference to such view in Application to inject that view into LocalizationMap.
(In your RegistrationDemo_i18n example you pass SignInPage reference into LocalizationMap via signInPage=”{signIn}”. But how to pass view reference into LocalizationMap if you create that view instance dynamically via PopUpManager?)
How can I do it? Did I miss something?
ThanX,
//pyso
When you do not have a reference to a specific view instance or you have multiple instances, you should look at the SmartResourceInjector tag:
<:SmartResourceInjector target=”{RegistrationForm}”>
<PropertyInjector targetKey=”lblEmailHint.text” source=”{account}” sourceKey=”email” />
</SmartResourceInjector>
What this does is “watch” for any instantiations of the RegistrationForm class. When that class is ready [determined by listening for creationComplete event internally ], the value of account.email is INJECTED into the <instance RegistrationForm>.lblEmailHit.text property. In fact, the injection occurs for ALL instances of RegistrationForm… since SmartResourceInjectors cache all instances [until they are removed from the stage]. And – even better – if you only want instances with SPECIFIC ids to be cached, then you can also use the targetID=”" property of the SmartResourceInjector.
Hi Thomas,
Thanks. This is really great.
I have just started trying it on a new project.
Thanks again.
Thanks for the support/help! Fantastic solution! As Alexander said, “nicely done!”
Great! Can we use this framework with other flex framework, such as Swiz? Do I have to bring in mate dependency?
Thanks again1
The l10nInjection.swc contains all the localization and core Mate files you will need. This library can be used with PureMVC, Swiz, Cairngorm, etc. If you use this library with the Mate framework, make sure this library is loaded FIRST, since some of the core Mate classes have been “tweaked” in the l10nInjection.swc.
Hi, Thanks so much for sharing this great work.
I can’t wait to use this in my project, we are uing mate now, btw. But I find there are many classes with the same name as mate has, such as “Binder”, I know there must have some different places, but how could I know which one was used when the app is running? Would you mind to give me any suggestions?
I find that you did many changes in those classes, so I couldn’t just use the one in mate.
The LocalizationMap uses the Mate core classes that have SOME refactoring and enhancements.
These changes should be released soon as a formal patch to the current Mate framework. Meanwhile [if you need l10n features] use the attached core Mate source for your Mate AND your localization needs.
Hi, very good work, thanks!
Localization worked fine in a new project. But i´m having some problems in my old projects that use mate. The problem is that Bind.bind (from your lib) expects 4 arguments, but mate´s expect 5. Then mate tries to pass 5 arguments to Bind.bind, but it was expecting 4. I tried to compile mate from source, replacing the source you provided, with no success. Do you have any workaround on this?
Thanks!
Pedro,
I discovered the issue. The l10nInjeciton library modified Binder.as and PropertyInjector.as with “refactoring improvements”. Unfortunately, this broke existing apps. And errors occurred: “Binder/bind() expected 4 but got 5 args”
I have fixed the Binder::bind() method to require 5 arguments (as originally defined in the original framework. I also fixed PropertyInjector to provide the “scope” as its first argument.
Attached is the revised l10nInjection source code [with the above fixes]. This should fix your issues. Also here is a link to the RegistrationDemo app with l10n code also updated. Plus here is a link to a sample app that demonstrates l10n injection with Flex 4 view states. Cool stuff!
Thank you for the feedback/issue reports.
This is fantastic! Sure would love to see a Mate example, instead of Cairngorm, but I’ll figure it out. Thanks so much for sharing this resource.
John,
Take any Mate sample application and you should be able to create a LocalizationMap without any changes to your existing source.
Simple create an instance of a LocalizationMap with custom injectors/proxies (also need the *.l10n.* packages included in the source).
Notice the com.thunderbay.locale.LocaleCommand in the RegistrationDemo’s source.
You need to dispatch an LocaleEvent.INITIALIZE event to auto detect the current OS locale, and your custom LocalizationMap will try to use that locale’s resources for injection…
all without changing your Mate view source or EventMaps.
You are a genius! nicely done!!! Pefect presentation! Thank you very much!!!