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: , , , ,


Apr 08 2010

Learning shell scripting while trying to fix Snow Leopard local network issue :)

Category: macDavide Zanotti @ 8:31 am

Mac OS X is a great system, but this doesn’t mean it’s perfect and without issues. One thing is not working as it would (in Snow Leopard 10.6.X), it’s the automatic discovering and mounting of local machines connected to the same network. This is especially true if the network includes a lot of Windows computers. Theoretically those machines should be automatically discovered and displayed in Finder under “SHARED” label, but often this is not the case. Anyway in such cases is fortunately possible to connect to a particular device from Finder (once we know its IP address) by choosing “Connect to server…” (cmd+K) and by using the Samba protocol (ie: smb://ip-address). This is however very annoying, because we have to know each ip we want to connect to and type several addresses, so I spent some time to find a way to make this process automatic (because I didn’t find a solution to the original OS X issue) and I realized a shell script which tries to do the job the Leopard should do :)
I’m really a newbie when it comes to shell scripting (and networking too :P) and my script doesn’t work perfectly, anyway it does an acceptable work the most of times and although it doesn’t really solve the problem, it could help a lot.
The script is the following:

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
#!/bin/bash

# get user name
declare currentuser=$(whoami)

# get mac name
declare macname=$(scutil --get ComputerName)

# save arp command result
declare ipstring=$(arp -a)

# split ipstring into an array by using a regex to match ip addresses
declare -a iplist=($(echo $ipstring | grep -o -E "([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})"))

# display addresses found
echo "${#iplist[*]} ip addresses found"

# switch directory to user dir
cd /Users/$currentuser

# for each ip in list try to mount it using smbfs
for ip in ${iplist[@]}
    do
        if [ ! -d link-to-$ip ]
            then
                echo "creating directory link-to-$ip under /Users/$currentuser"
                mkdir link-to-$ip
                echo "trying to mount //$macname:@$ip/Public"
                mount -t smbfs //$macname:@$ip/Public /Users/$currentuser/link-to-$ip
            else
                echo 'folder "link-to-$ip" already exists... skip'
        fi
    done

# complete
exit 0

Any comment and suggestion is really appreciated! Thanks

Tags: , , , , , ,


Apr 07 2010

Handling doubleclick on Flex components

Category: flexDavide Zanotti @ 3:34 am

Handling a common event like a double click is not so simple as we may assume when it comes to Flex programming.
In fact there are several critical aspects related to this event, first of all we have to set the attribute doubleClickEnabled to true in order to make our components double clickable, and this sounds pretty silly to me. Why should I enable an object to receive an event which is so common and “natural”? This is incoherent from a software design perspective end totally unexpected by the user. Anyway this is not a real problem, because once we know about it we have just to use a setter, but double click is somehow buggy in the framework and its broadcasting can be blocked mysteriously when using certain components. I faced this issue by trying to handle a double click on a DataGrid which makes use of custom item renderers and I tested that by double clicking on the “special cells” (those rendered by my own renderers) the MouseEvent.DOUBLE_CLICK is not dispatched/cacthed. To solve the problem I had to draw an invisible rectangle using the underlying Actionscript graphics API! This is my workaround:

1
2
3
this.graphics.beginFill(0xffffff, 0);
this.graphics.drawRect(0, 0, this.parent.width, this.parent.height);
this.graphics.endFill();

Finally, it seems that double click is completely ignored by Firefox 3.6 on Mac OS X, but fortunately this seems to been fixed in the last release (3.6.3)

Tags: ,


Feb 05 2010

Get hours, minutes and seconds from a number without maths operations

Category: actionscriptDavide Zanotti @ 8:09 am

Today I’m gonna show you a secret ninja technique to extract hours, minutes and seconds from a number (representing an amount of seconds). In my example I will show an Actionscript code, but this can be implemented in JavaScript and maybe other languages too.
So, the scenario is the following: we have a number representing seconds and we want to know how many hours this amount of seconds contains, how many minutes and how many remaining seconds. We know that a minute is composed by 60 seconds and an hour by 60 minutes and we could write a series of maths operations in order to accomplish our objective, but there is a way far simple and fast: use the Date class!
Date class already implements all the methods we need:

  • getHours()
  • getMinutes()
  • getSeconds()

So, in order to take advantage of these useful methods, all we have to do is initialize a “fake” date using the amount of seconds we want to “split” into hours, minutes and seconds. The Date class has several OPTIONAL arguments that can be specified during its initializations, these are:

  • year
  • month
  • date (day number)
  • hours
  • minutes
  • seconds
  • milliseconds

Because theme all are optional, we can create a date object by specifying only the know arguments (in our case seconds) and by assigning null or zero to the others:

1
var date:Date = new Date(null, null, null, 0, 0, 9137);

Then by calling getHours, getMinutes and getSeconds we will obtain what we expect:

1
2
3
trace("hours: ", date.getHours()); // 2 hours
trace("minutes: ", date.getMinutes()); // 32 minutes
trace("seconds: ", date.getSeconds()); // 17 seconds

Not sure about the result? Let’s test it:

60 * 60 * 2 = 7200; (seconds contained in 2 hours)
32 * 60 = 1920; (seconds contained in 32 minutes)
7200 + 1920 + 17 = 9137; (original seconds!)

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: , , ,


« Previous PageNext Page »