Skip to content


How to improve localization

Hendrick van Cleve - The Construction of the Tower of Babel

I've been meaning to write about this for a while. Even though it is not specifically about development, user experience is something that creative developers should take into account when developing websites, games or other interactive experiences. Let's look at a couple of different ways of offering the user localized content, and try to analyze whether these options fall flat on their face or offer enough flexibility to answer your client's and their customers's needs.

Clients that offer services in multiple countries require their content to be localized, and for a good reason: inhabitants of all regions of the world want to be respected and obtain content in their own language. And I agree, it's a simple matter of respect.

The usual suspects

The way localization is most often achieved is by detecting the user's country automatically. This can either be done by localizing the IP address (see an example), soon by simpler means with HTML5's geolocation (see an example) or by asking the user to select a country.

LaCie does just that.

LaCie's landing page

I think that this last option is lazy, both on the company's and on the developer's part. The user should not have to make such choices, especially when there are ways to make such a selection invisible.

What happens from there is that the website content is presented in the language relative to the country. It mostly works, but there are some situations where this logic is not sufficient. Let's take the example of some European countries, like Belgium and Greece. In Belgium, French, Dutch (Flemish) and German are the official languages. In Greece, where obviously Greek is the language, there is also need for a specific alphabet.

From adidas's country selection page, if either Greece or Belgium is chosen, the user has no choice but to visit an English website.

adidas language selection page

To adidas's credit, there is the option to change country at any time, the site even offers you a choice whether or not you want the cookie to remember your country selection.

adidas asks if the chosen country is to be remembered for next time

Some websites, such a Nike's may offer a simpler solution, only choose your language, not the country.

Nike's website offers to choose a language, not a country

All these options ask an action from the user before they can even see your products or services, and that small irritant is the first interaction they have with your website.

The mistaken

In all online stores (let's not call these e-commerce no more, please?), there comes a moment where you want to write your shipping address to obtain your purchases. If you have offered your clients a multilingual website, it's just normal that the input fields should support special characters too, regardless of the language the client chose.

When I moved back from Amsterdam to Montréal, I had to change many addresses for many of my services, both physical and online.

Apple requires me to have a valid credit card in the country that I select, as their products availabilities are based on the locale. However, their form failed when I entered my address.

Apple's address formI get informed that the city, that I wrote "Montréal", contains illegal characters. Obviously, this turned out to be that I should not use the accent. This is ridiculous, the user should not be told there is something wrong when in fact there is not. How complicated is it really to handle accented characters? I mean, if you want to provide multilingual services, get your act together and do it everywhere.

The ignorant

There is also the peculiar case of Urban Outfitters. The american company has oftentimes been vilified for stealing designs from indie designers or for shamelessly using native names in their product names, so what I will present here should not come as a surprise.

In Québec, a law states that if a business has a brick and mortar store in the province, the business is responsible to also have a French side to it's website. As mentioned in my introduction, it's a matter of respect to your clients in the locale in which you offer your products and services.

Urban Outfitters did not, and when legally instructed to do so, they decided to simply not offer their clients in Québec with no access to their website whatsoever. Just go to the store.

Urban Outfitters's fuck you to Quebeckers

That is one stupid way to use geolocation! You disrespect a locale and it's language, and at the same time you prevent those in the locale who can speak and read English from having access to your products. Great way to prevent sales!

The road ahead

Of these different approaches I make the following conclusion and add a recommendation for multilingual website and software creators: first, indeed do geolocation to offer the most relevant content to the user. Do give options to change not only the country, but also the language. Paypal is the closest to this idea. If you change your location to Canada, you can then change the language to the available languages is that country, namely French and English.

But here is where I want to get to with this post: why stop there? Indeed, do present the most logical languages relevant to that area, but do not block the other languages from being available, especially since you have them in the database already. I understand that it may be mostly be relevant for expats and travelers, but I strongly believe that this kind of market is growing.

The iTunes Store is built in order to lock you into a language depending on your country. Websites like YesAsia are built with the idea that the content should be available to everyone, even if the clients are not from Hong Kong, Japan or Korea, where their offices are. They offer not only options for locales and languages, but a reference to the value of the currency so the customers may have a general idea of what they will pay.

YesAsia's options

In the case of Paypal, as I said before languages are chosen by country. They have many more languages, they should offer them. I can understand that there may be issues in doing so. For example, English is not legally bounding in the Netherlands, so important options are not available, not even deleting an account. How about presenting all languages, and just like YesAsia does it, add a note that explains that whatever is said in translations, the version in the official language(s) of the locale prevail?

Ultimately, it's always about how much you really care about your clients. For example, Asian countries dislike it when they are presented with English text rather than localized. Ask your markets to help you make a better localized version of your products, don't just expect your clients to swallow whatever you feed them. This is the kind of stuff metrics will not really show in numbers, this is an analysis you can make from talking with your markets.

Posted in Localization, Opinion, Strategy, Tips, UX.


Apply a texture on a cube in Away3D 4.0

Away3D cube with a texture applied

Now that your environment is set up to play with Away3D, let's create something! In my previous post, I concluded that you can now experiment, and so I did too. It turned out that there are some little intricacies about putting a simple image on a simple cube. Let's take a look.

Create a cube

This part has got to be the simplest, thanks to the nice API that the guys from Away3D built.

ActionScript 3.0:

package
{
import away3d.containers.View3D;
import away3d.materials.ColorMaterial;
import away3d.primitives.Cube;
 
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
 
[SWF(frameRate="60", backgroundColor="#ffffff", width="640", height="360")]
public class Main extends Sprite
{
	// + ----------------------------------------
	//		[ VARS ]
	// + ----------------------------------------
 
	private	var	_view3D		:View3D;
	private	var	_cube		:Cube;
 
	// + ----------------------------------------
	//		[ CONSTRUCTOR ]
	// + ----------------------------------------
 
	public function Main()
	{
		super();
		initStage();
		addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
	}
 
	// + ----------------------------------------
	//		[ PRIVATE METHODS ]
	// + ----------------------------------------
 
	private function initStage():void
	{
		stage.align = StageAlign.TOP_LEFT;
		stage.scaleMode = StageScaleMode.NO_SCALE;
	}
 
	private function init():void
	{
		// it is necessary to add a texture to the shape so it is visible
		var material:ColorMaterial = new ColorMaterial(0xffffff);
 
		// I personally prefer to set vars for repetitive values
		var cubeSize:uint = 240;
		_cube = new Cube(material, cubeSize, cubeSize, cubeSize);
 
		// add the 3D elements to the stage
		_view3D = new View3D();
		_view3D.scene.addChild(_cube);
		addChild(_view3D);
 
		// add a listener so it becomes possible to render the 3D world
		addEventListener(Event.ENTER_FRAME, enterFrameHandler);
	}
 
	// + ----------------------------------------
	//		[ PRIVATE METHODS ]
	// + ----------------------------------------
 
	private function addedToStageHandler(event:Event):void
	{
		removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
		init();
	}
 
	private function enterFrameHandler(event:Event):void
	{
		// render the 3D world
		_view3D.render();
	}
}
 
}

With this code, this is what you should get as a result:

Away3D cube with ColorMaterial applied

Not exactly exciting. For one, a ColorMaterial is really a flat color, so the best way to make it live a bit is adding light elements, which we will not see in this post (and that's also because I have not learned that yet). Also, we only see once face of the cube, quite boring.

To at least view that this object is in 3D, simply update the enterFrameHandler function.

ActionScript 3.0:

private function enterFrameHandler(event:Event):void
{
	// increment the cube's rotation values to see its different sides
	_cube.rotationX++;
	_cube.rotationY++;
	_cube.rotationZ++;
 
	// render the 3D world
	_view3D.render();
}

Apply an image texture

Most probably, you would like to add your own textures to your 3D elements. This is where it gets fun!

As suggested by a lot of users on the Away3D forums, it may be better to have a 3D artist create objects in Blender or 3ds Max (I do not know if Unity3D can be used, although I hope it can). Once those objects are created, there is a way to import/load them into Away3D.

But in this case, since I do not know 3D software and because there is no need yet to get into complicated texture mapping, we are looking at something super simple: put an image on a cube. Most of what follows is a comprehensive summary of a thread that followed my question on Away3D's forum.

First things first: let's create an image. There are specifications to follow when creating an image for a BitmapMaterial.

A texture does not need to be square, however the dimensions have to be a power of two. What this means, is a texture can be 512 x 512 pixels, or 256 x 1024 pixels, etc. I worked with 2048 x 1024 pixels in this case.

Keep in mind that if you image is not square, it will not mipmap. Not fully convinced I understand what it means, as I do now know 3D vocabulary that much, but suffice to say you simply need to set BitmapMaterial.mipmap to false to make it work.

In the case of a cube, you need to cover all six sides. In order to do so, split your image into a 3 x 2 grid, like so:

Dice texture

Do you notice that each part of the image that represents a side of the cube is not square? It does not matter really, since Away3D will map it as a square onto the cube. As long as you respect the grid in your design, it will be good.

Rather than loading the image and in order to make things quicker, I simply embedded it in the code.

ActionScript 3.0:

package
{
import away3d.containers.View3D;
import away3d.materials.BitmapMaterial;
import away3d.primitives.Cube;
 
import flash.display.Bitmap;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
 
[SWF(frameRate="60", backgroundColor="#ffffff", width="640", height="360")]
public class Main extends Sprite
{
	// + ----------------------------------------
	//		[ CONSTANTS ]
	// + ----------------------------------------
 
	[Embed(source="../assets/images/dice-texture-2048x1024.png")]
	private var ImageClass:Class;
 
	// + ----------------------------------------
	//		[ VARS ]
	// + ----------------------------------------
 
	private	var	_view3D		:View3D;
	private	var	_cube		:Cube;
 
	// + ----------------------------------------
	//		[ CONSTRUCTOR ]
	// + ----------------------------------------
 
	public function Main()
	{
		super();
		initStage();
		addEventListener(Event.ADDED_TO_STAGE, addedToStageHangler);
	}
 
	// + ----------------------------------------
	//		[ PRIVATE METHODS ]
	// + ----------------------------------------
 
	private function initStage():void
	{
		stage.align = StageAlign.TOP_LEFT;
		stage.scaleMode = StageScaleMode.NO_SCALE;
	}
 
	private function init():void
	{
		// create a Bitmap from the embedded image
		var image:Bitmap = new ImageClass() as Bitmap;
 
		// create the BitmapMaterial
		var material:BitmapMaterial = new BitmapMaterial(image.bitmapData);
		material.smooth = true;
		// little trick: you do not need to care if the image is square,
		// simply evaluate if the sides are even
		material.mipmap = (image.width == image.height);
 
		// create the cube
		var cubeSize:uint = 240;
		_cube = new Cube(material, cubeSize, cubeSize, cubeSize);
 
		// add the 3D elements to the stage
		_view3D = new View3D();
		_view3D.scene.addChild(_cube);
		addChild(_view3D);
 
		// add a listener so it becomes possible to render the 3D world
		addEventListener(Event.ENTER_FRAME, enterFrameHandler);
	}
 
	// + ----------------------------------------
	//		[ PRIVATE METHODS ]
	// + ----------------------------------------
 
	private function addedToStageHandler(event:Event):void
	{
		removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
		init();
	}
 
	private function enterFrameHandler(event:Event):void
	{
		// increment the cube's rotation values to see its different sides
		_cube.rotationX++;
		_cube.rotationY++;
		_cube.rotationZ++;
 
		// render the 3D world
		_view3D.render();
	}
}
 
}

Fix the Away3D sources

Now, I ran into an issue when I originally tested this (Flash Player 11+ needed, click and drag to move the cube):

Cube texture issue

Some sides were repeated or not applied properly. That is actually what prompted me to ask my question on the forum, as I thought I had done everything proper.

I actually did, but here also, there was a correction needed in the Away3D sources. Away3d forum member 80prozent posted a fix.

Look at the function Cube.buildUVs() into the away3d.primitives.Cube class and replace this part:

ActionScript 3.0:

for(i = 0; i <= _segmentsW; i++)
{
	outer_uv = u_tile_step + u_tile_dim * (i / _segmentsW);
 
	for(j = 0; j <= _segmentsD; j++)
	{
		uvData[uidx++] = outer_uv;
		uvData[uidx++] = 1 - v_tile_dim * (j / _segmentsD);
		uvData[uidx++] = outer_uv;
		uvData[uidx++] = v_tile_step + v_tile_dim * (j / _segmentsD);
	}
}
 
for(i = 0; i <= _segmentsH; i++)
{
	outer_uv = v_tile_step + v_tile_dim * (i / _segmentsW);
 
	for(j = 0; j <= _segmentsD; j++)
	{
		uvData[uidx++] = (u_tile_dim * ((_segmentsD - j) / _segmentsD));
		uvData[uidx++] = 1 - outer_uv;
		uvData[uidx++] = 1 - (u_tile_dim * ((_segmentsD - j) / _segmentsD));
		uvData[uidx++] = v_tile_step + v_tile_dim * ((_segmentsW - i) / _segmentsW);
	}
}
 
target.updateUVData(uvData);

with this part:

ActionScript 3.0:

for(i = 0; i <= _segmentsW; i++)
{
	outer_uv = u_tile_step + u_tile_dim * (i / _segmentsW);
 
	for(j = 0; j <= _segmentsD; j++)
	{
		uvData[uidx++] = outer_uv;
		uvData[uidx++] = 1 - v_tile_dim * (j / _segmentsD);
		uvData[uidx++] = 1 - outer_uv;
		uvData[uidx++] = 1 - (v_tile_step + v_tile_dim * (j / _segmentsD));
	}
}
 
for(i = 0; i <= _segmentsH; i++)
{
	outer_uv = v_tile_step + v_tile_dim * (i / _segmentsW);
 
	for(j = 0; j <= _segmentsD; j++)
	{
		uvData[uidx++] = (u_tile_dim * ((_segmentsD - j) / _segmentsD));
		uvData[uidx++] = 1 - outer_uv;
		uvData[uidx++] = 1 - (u_tile_dim * ((_segmentsD - j) / _segmentsD));
		uvData[uidx++] = v_tile_step + v_tile_dim * ((_segmentsW - i) / _segmentsW);
	}
}
 
target.updateUVData(uvData);

His fix corrects the way the image is applied to the cube. I guess this will be eventually fixed, but at the time of writing this post, the sources provided by Away3D still need that correction.

And there you go! From then you can have an awesomely decorated cube with the texture of your choice!

Posted in Tips, Tutorials.

Tagged with , , , , .


Setting up FDT for Away3D and Flash Player 11 (Beta)

FDT and Away3D

Recently I had been approached to develop an interface for a huge interactive wall. The company has its own engine, but the interface runs as a Flash Player piece within that environment. I am not talking about Scaleform, which allows game developers to integrate interfaces built in AS3 into their projects, but the logic is pretty much the same.

So the idea was to use some simple 3D shapes and let users interact with it via a touch screen. The project did not go through, however I had done some research on the latest Flash Player beta developments and the latest alpha release of Away3D. As expected, I had to do a lot of searches in Google, I cursed at my computer once or twice, but I did learn a lot. I think it would be nice to share my discoveries.

Setting up FDT to publish onto Flash Player 11 Beta

I have to say a lot of people wrote blog posts already on how to do that. Although their posts explained more on how to use the incubator version of Flash Player, it's pretty easy to do a parallel. I'll just list the steps here and provide the sources to my information further down.

  1. Get the latest available Flash Player debugger, Flex SDK and playerglobal.swc (by the way, since the IDE is now called Flash Builder, why is the SDK still called "Flex SDK " rather than "Flash SDK"?)
  2. Uninstall whatever version of the player you have on your computer and install the latest one you have downloaded
  3. Replace the playerglobal.swc in the SDK with the separate playerglobal.swc you have downloaded
  4. Set up the SDK in FDT
  5. When creating a new project, make sure to add the -swfversion=13 compile argument (I hope this will be dropped when the release is no longer in beta)
  6. In your HTML page, do not forget to set wmode="direct" so the rendering can be done on the GPU

Now these steps are really well explained here, although since the release of the beta version (rather than the incubator version), it is no longer necessary to remove the flex.swc.

Now, all this is a bit of work to get running, and just recently, FDT released a template that sets up a lot of that stuff already. This template includes the compiler argument and an HTML template that presets the wmode, it even includes the M2D library, which is supposed to help creating 2D elements with the Stage3D API to enjoy the benefits of GPU rendering. Thanks guys! I guess now I want to look into creating templates.

Setting up Away3D

Away3D sources and libs

I have been reading good comments here and there about Away3D, and the running examples I have seen were quite convincing, so I decided to look into it. Getting started is quite simple.

  1. Download the Away3D 4.0 alpha release
  2. Add the provided source files to your classpath
  3. Add the provided Apparat LZMA decoder library as well

Logically, this should be it and you should be able to go, but nooo! Have you ever seen a development project work properly on the first try? Especially not a project that uses both an alpha release and a beta release! After a bit of research, I found an article on DisturbMedia's wiki that basically explained the situation:

Most errors come from AGALMiniAssembler.as. I'm not sure why, but I found that if I split the multiple classes nested in the single file into multiple files that fixes most of the errors. At the end of the process you should have 3 additional classes: Sampler, Register and OpCode.

Once the split classes are added, some errors remain in the Away3D sources, however it is possible to compile an empty project at this point.

Next, you would expect to be able to use some Away3D classes already, so did I. The base requirement is to add a View3D, which contains a container that can be used as some sort of stage. Let's try:

ActionScript 3.0:

package
{
import away3d.containers.View3D;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
 
public class Main extends Sprite
{
	private	var	_view3D	:View3D;
 
public function Main()
{
	// set the stage
	stage.align = StageAlign.TOP_LEFT;
	stage.scaleMode = StageScaleMode.NO_SCALE;
 
	// create the View3D
	_view3D = new View3D();
	addChild(_view3D);
 
	//
	addEventListener(Event.ENTER_FRAME, enterFrameHandler);
}
 
private function enterFrameHandler(event:Event):void
{
	// render the View3D at every frame (or other update of your choice)
	_view3D.render();
}
 
}
 
}

Try and run that with the explanations I have given so far, you'll most probably run into the same situation I did: an error. Why? I mean, nothing seems wrong.

Here again, it took me a bit of research, but it turns out that a class needed a correction to handle a change made by the beta release of Flash Player. As I understand, this library was mostly written while the player was still an incubator release. Simply get that updated Stage3DProxy class with the one provided in the forum thread, and you're good to go.

From this point on, you can head to minute 2:30 of John Lindquist's tutorial to see how to add an awesome cube to your project, or look into the Away3D examples. Enjoy!

Posted in Tips, Tutorials, Workflow.

Tagged with , , , , .