May 15 2010

iPhone applications’ localization

Category: Objective-c and CocoaDavide Zanotti @ 5:15 am

Well, it’s quite a lot since my first approach to Objective C, Cocoa and iPhone development and now I’m starting to move my first concrete steps into this beautiful world. Today I would like to face an important aspect of iPhone development (and application development generally), localization! Fortunately the excellent Cocoa framework and Objective C ecosystem make internalization process easy, fast and clean, thus localize an application into several languages can be accomplished with a minimum effort by developers.
Fundamentally there are three main steps to get a multi language app in action. The first is to create a special folder with the extension .lproj under application’s root for each language we want/can support. So, if we suppose to support english, italian and spanish languages, we will create those folders: en.lproj, it.lproj, es.lproj. “en”, “it” and “es” represent the ISO 639-1 language designation (the same used in domain suffixes), it’s also possible to adopt the ISO 639-2 convention and the folders would be renamed as “eng”, “ita” and “spa”, anyway first approach is the preferred and widely adopted. If you want to know all ISO 639-1 and ISO 639-2 designators, I suggest you this page: http://www.loc.gov/standards/iso639-2/php/code_list.php.
Once we create all the necessary .lproj folders (using Finder or Terminal) we can switch to our beloved Xcode and create all the necessary resources that will keep the different localized strings. We must now select the logical “Resources” folder and add a new file by choosing “String file” under Mac OS X “Resource“, then click next and naming this file “Localizable.strings“, finally save it under previous created folders (we must repeat this operation for each folder).
Those .strings file are just simple text file, where we can specify keys and values as the following example:

// en.lproj
“hello” = “Hello”;

// it.lproj
“hello” = “Ciao”;

// sp.lproj
“hello” = “Hola”;

The basic approach is to use the english word as the key and assign it different values based on the localization file, but we can be more cryptic and use our custom conventions, for example by assigning incremental keys like: “k1″, “k2″, “k3″ and so on. As last step, we can automatically read the right localized string version at runtime (based on user’s language setting) by using macro (NSLocalizedString, NSLocalizedStringFromTable, NSLocalizedStringFromTableInBundle, NSLocalizedStringWithDefaultValue) or class methods available in Apple’s framework. The simplest way to do that is to use the function NSLocalizedString(), which accepts two arguments: an NSString representing the key we are looking for and a second optional argument which has the mere purpose of serving as note for developers by trying to describe string’s context (*), for this reason this argument will be “nil” the most of times:

1
2
// set myLabel's text using NSLocalizedString()
myLabel.text = NSLocalizedString(@"hello", nil);

Although this approach is very simple, it’s not recommendable in my opinion, because it doesn’t provide a way to handle missing keys and if a key can’t be found, it will print that key literally (and in case of a not human readable key like “k-15″, it would be very annoying for the user). Thus, a far better option is to use the NSLocalizedStringWithDefaultValue(), which accepts 5 arguments: an NSString for the key, an NSString for the table, an NSBundle reference, an NSString for a fallback string’s value, and an optional NSString for a comment (like NSLocalizedString()). The table argument refers to the name of .strings file (without extension), in fact we are not limited to “Localizable.strings” but we can create as many .strings files we need (“Menu.strings”, “InfoPanel.strings”…). “Localizable.strings” is only a conventional name used to specify a default file for localization that can be loaded without specify its name (NSLocalizedString() can read only that file). The bundle is a reference to the NSBundle where the .strings file is located (usually the main bundle).

1
2
// set myLabel's text using NSLocalizedStringWithDefaultValue()
myLabel.text = NSLocalizedStringWithDefaultValue(@"MyKey", @"MyStringFile", [NSBundle mainBundle], @"Missing key", nil);

It’s also possible to use bundle’s method localizedStringForKey:value:table, which accepts an NSString as key, a second NSString as fallback value, and a third NSString for the table (or nil if you are lazy and want to use “Localizable.strings” without specifying it) :

1
2
NSBundle *bundle = [NSBundle mainBundle];
myLabel.text = [bundle localizedStringForKey:@"MyKey" value:@"Missing key :(" table:nil];

An important point to understand is how localization process works, that is which steps are involved at runtime on the device. As far I know (by doing several experiment using iPhone simulator), the process is the following:

1. code is executed and a request for a localized string is found
2. the system try to find the .strings file of the current locale (according to user language settings). The NSString represenitng current locale can be found using:

1
[[NSLocale currentLocale] localeIdentifier];

3. If the file is found, it will search for the requested key and it will return it’s value if found or a nil otherwise. If .strings file can’t be found, the system will try to find others .strings file sequentially, using preferredLanguages‘s NSArray:

1
NSArray *preferredLanguages = [NSLocale preferredLanguages];

and it will stops as soon a file is found, by returning key’s value if found or nil otherwise.

So, considering the simple “hello” localization example into english, italian and spanish, our @”hello” key will be successfully translated if we use one of the three locales (en, it, sp) and it will be printed in english for extra locales (like german, french and so on).

Finally it’s also possible to generate strings file automatically from the source code by using Terminal’s command genstrings, see here for details: http://developer.apple.com/mac/library/documentation/Darwin/Reference/ManPages/man1/genstrings.1.html

* the comment parameter is also used to automatically generate a comment (/* comment */) in .strings files using genstrings

Tags: , , , ,


Jan 12 2010

Debugging PhoneGap applications using Xcode console

Category: phonegapDavide Zanotti @ 3:13 am

When I started to play whit PhoneGap, my greatest issue was: “how can I debug my code?”, I use often tools such FireBug and JavaScript debugger included in Internet Explorer 8 (which is the first good thing IE has to offers!) but write and test my code on iPhone simulator is completely different. Fortunately PhoneGap offers a way to access to Xcode console and print messages by choosing among three different levels: log, warn and error. In order to print a message, we have to use the debug object, which has scope window (it is a global object), in this way:

1
2
3
4
5
debug.log("my log message");
// or
debug.warn("my warning message");
// or
debug.error("my error message");

To open Xcode console you have to choose “Run -> Console” from the toolbar (or CMD+SHIFT+R) and after a “Build and Run” (CMD+Enter), you will see your message appear in the console.
Testing applications while developing using PhoneGap and Xcode, is an intense activity, because errors are not automatically notified (like in FireBug or similar), so it’s really important to make use of try/catch/finally blocks and logging calls:

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
   
    mycommand.execute();

} catch (e) {

    debug.error("Error using mycommand: " + e.message);

} finally {

    // do something smart here :^)

}

Finally, in order to avoid problems related to Xcode cache, I suggest to always clean the cache by running “Build -> Clean” from the toolbar (or CMD+SHIFT+K) before doing a new build and eventually remove the application folder under “/Users/{your-name}/Library/Application Support/iPhone Simulator/User/Applications/{app-number}”.

Tags: , , ,


Dec 15 2008

Detecting display’s orientation change on mobile devices with accelerometer: a platform indipendent approach (n95, i900, iPhone…) with javascript

Category: mobileDavide Zanotti @ 1:55 pm

As I wrote, I bought a Samsung i900 Omnia (which as Nokia n95, iPhone and other modern cool mobile phones, has an accelerometer), as a web developer one of the first things that I had in mind was “how can I detect the orientation change (landscape or portrait mode) with javascript or actionscript?”. The first things I thought was:

  1. If the display changes its orientation mode, then the screen width and height must will updated with the new size
  2. If the screen width is smaller than screen height, the device must be in “portrait” mode
  3. Vice versa, if the screen width is bigger than screen height, the device must be in “landscape” mode
  4. The only thing I need is to check the value of screen size and define the orientation mode according to the point 2 and 3

And, as I can see, my deductions was right! I realized a small javascript script, which by using setInterval() function and controlling screen.width and screen.heigth deduces the device orientation mode. Anyway it works actually only with Opera mobile 9.5 (tested only on Samsung Omnia), Internet Explorer Mobile seems unable to interpreting some javascript functions.

This is a piece of my code (the main logic behind):

if (screen.width != width && screen.height != height) {
    width = screen.width;
    height = screen.height;
    mode = width > height ? "landscape" : "portrait";
}

If you would like to test my script go to http://daveoncode.site90.net

Let me know, thanks ;-)

ps. as soon I will understand about flash capability on my device I will try to implement a custom Actionscript event for the orientation… something like DisplayEvent.DISPLAY_MODE_CHANGE

UPDATE: if you are unable to load  http://daveoncode.site90.net try http://andreacfm.com/dave/ (I have to thanks my friend Andrea Campolonghi for the space)

UPDATE 2: the script works even on Blackberry’s browser (as someone told me) :)

UPDATE 3: it works even on HTC devices (at this point the only problem is internet explorer mobile… I will investigate!)

Tags: , , , , , , , , ,


« Previous Page