Essentials • iOS, OS X • 53:26
With users spread all around the globe, it's more important than ever to pay attention to how they expect to see information in your apps. Formatting currency and dates in the user's preferred way can make the difference between a frustrating experience and a great app. Handling different locales properly doesn't take a lot of work and can broaden the reach of your application. Come see how you can add an international flair to your app today.
Speaker: Dave DeLong
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Welcome to this session on internationalization tips and tricks. My name is Dave DeLoong. I am an engineer on the UI kit framework. And today we're going to talk about ways that you can improve internationalizing your applications. Now you're here because on Monday you heard some really impressive things. Things such as we have sold over 365 million iOS devices.
Or that by the end of the year, the app store will be available to 155 countries. Chances are most of your users may not speak your language. So we're going to talk about things that you can do to make your app work for them. Now there is something to understand.
Is that for all of these users, you should be creating one application. The iOS app store in 155 countries does not mean that you should be creating 155 versions of your application. You should be creating one version. That same version should work for everyone regardless of where they are in the world. You do this by internationalizing your app. What does that mean? Internationalization. Is the process that we as developers do to prepare our applications to receive translated content. This includes things like properly formatting dates or numbers, pulling out translated strings from our strings table, and so on.
This is in contrast to localization, which is the process of actually integrating that translated content into our application. Let's look at what a simple example might be of localizing a resource. On the left here, we have selected a strings resource file. In this case, it's the resource for our Info.plist.
And we can see that it's currently available in a single language. If we want this resource to be available in another language, we'll simply click that plus button there underneath English and choose to add a new language, such as Spanish, or we can see others like Japanese or French.
That's all we have to do. After we've done this, we'll see that our resource is now available in both English and Spanish. And there are now two versions of this file, one for English and one for Spanish. We have doubled the amount of content that you need to localize, but as is to be expected, you need different content for English and Spanish. What happens when you do this is that in your application directory, we will create a new folder called a Language Specific Project Directory, or LPROJ.
Since we have two versions of our resource in English and Spanish, we have two lproj folders, one for English, one for Spanish. And any localized resource in English will be located within the en lproj folder, and any Spanish localized resource will be inside the Spanish lproj folder. Dave DeLoong Localizing resources is done this way, no matter what kind of resource it is, whether it's an interface builder file, a strings table, an image, a plist, you can all localize them the same way. Dave DeLoong One key concept to understand when dealing with translated content is that there is a difference between the language of your app and the region in which it's running. Dave DeLoong Here on the left, we can see the list of languages that are available to us.
Dave DeLoong Here on the right, we can see the same screen running on iOS. Dave DeLoong Since the language can be different from your locale, here we can see English as our language, but Thailand as our locale, this can cause some interesting situations. Dave DeLoong For example, the text of your application might be in English, but when you're formatting dates or numbers, they will be formatted according to the title of your application. Thailand locale region formats. So the names of your months, the decimal separators used in numbers, etc., will all be formatted according to the locale of Thailand. However, your strings are translated according to the language.
The main class that we use to determine this information is called NSLocale. NSLocale contains all of the linguistic, technological, and even cultural conventions and standards for that particular region. The way that we access the locale is using the class method currentLocale. This returns a static object that does not change. However, since the user can change the locale of their device... ...at any time, you may also want to consider using the auto-updating currentLocale.
This is a locale that will correctly pick up any changes the user may make to their date format settings, to whether or not they want time to show as in 12-hour format versus 24-hour format, and so on. Regardless of which locale object you're using, you should be listening to the NSCurrentLocaleDidChange notification. This is a notification posted by the system.
The system will then send a notification to the system about when these settings change. When you receive this notification, you should update your UI to reflect the new locale settings. So, what are we going to talk about today? We're going to talk about the four main areas of localizing your content. There are text, dates, numbers, and images.
You'll notice that what we are not going to talk about is internationalizing your UI. For that, we recommend that you use AutoLayout, which is available in OS X Lion and now also in iOS 6. We've already had two sessions on AutoLayout on Tuesday and on Thursday, so I encourage you to go check out the videos for those sessions when they become available.
So, let's start off with text. Internationalizing your text will be the bulk of your work because text is also likely going to be the bulk of your content. The way that you access your translated text is with some built-in macros called NSLocalizeString. There are a couple variants that give you a little bit of flexibility in where you want your translated text to come from if you're providing a default value and so on. All of these macros are going to be available in your app. All of these macros are simply wrappers around an NSBundle method called LocalizeStringForKeyValueTable.
We'll see in a little bit how that can be useful. So where does our text come from? It comes from something called a string table. A string table is a file that lives inside your language project directory and by default it's called localizable.strings. Localizable.string simply stores your translated content in key value pairs, very much like an NSDictionary. So when you ask for a translated string, it's simply going to look through the file, find the key that matches the one you're looking for, and return the corresponding value.
So where do these tables come from? Well, if you want, you can add them to your project yourself simply by choosing to add a new file and then adding a strings file resource. However, there is another way you can do it, and that is using the GenStrings command line utility.
As the name suggests, GenStrings is a utility to generate strings tables. It is built into Mac OS X, so you have it on all of your computers. And because it's a command line utility, it is easily integrated into build scripts. It is customizable, so if you're using a custom macro, it can handle that. It can handle skipping various tables. It can handle where you want to output these generated tables to and so on. For a complete list of all the things it can do, check out its man page.
So what does GenStrings do? One way we might invoke GenStrings is like this. This is going to find in the current directory any files whose names end with the .m suffix. So we found some. It is then going to pass those files off to GenStrings. GenStrings will look through those files for any uses of the NSLocalizeString macro, build up the strings table, and then output it into your English lproj folder.
When it comes across a localized string macro inside your source code, GenStrings is going to parse the arguments and begin to build an entry in the table. The first argument is the key. So GenStrings will simply take that and copy it down and use that as the key. Since this version of the NSLocalizeString macro does not provide a default value, the same key will be reused as the value. The second argument is a comment to give some context to how this string is being used, and that is simply added to the table as the comment.
Once GenStrings has generated this table, we can then hand it off to our translators, and they can put in the correct value for us. So let's talk about some things to be careful of when dealing with internationalizing text. First off, you want to be very careful about using an NSLocalizeString macro more than once. Let's say your app puts up two alerts, one asking if you want to run a script and the other one asking how you're feeling.
If you use the same NSLocalizeString macro to provide a title for the OK button, You'll have something like this. But then after reading these alerts, you realize that the response of OK is not quite what you want for running a script. You want it to say run. And so you change your macro to use run.
After you do this, you then rerun your app and notice that it asks you how you are feeling and gives you a response, an option of run. This is obviously not what we want. So what we should be doing instead is using two different macros, one for the first alert and one for the second. This allows us to easily change the text without affecting any other parts of our UI.
You'll notice that we also updated the comments to more accurately reflect how these localized string macros were being used. This is because having little to no context in our localized string macros makes things very difficult for our translators. The comments are there for a reason. Please use them. When your translators get your strings table, this is what they do not want to see.
They don't want to see no comment provided by the engineer. And show really gives them no information on what or how this string is being used. Is show a verb, as in you want to show something to the user? Or is it a noun, asking if they want to go see a show? There's no way to know.
So please provide comments and also use descriptive key names. That makes things a lot easier for your translators. As engineers, we are trained to decompose problems into their most fundamental parts. And this is a habit of which we need to break ourselves when dealing with internationalized text. Composing strings can be very problematic. So remember that the unit of translation is not the phrase, but is the sentence.
This is because in many languages, they have concepts of number and gender. Some words may be masculine, some may be feminine. Some words may even need to be conjugated to match the rest of the sentence. As an example, let's say that your text or your application has two buttons. One says go to next page and the other says go to next chapter. And so you decompose the problem and you have go to next something. And then you will substitute in at runtime either page or chapter.
On the surface, this may not seem like a bad idea. However, when your translator tries to translate this, he's going to start with, go to next page. Well, page in many languages is a feminine noun. That means that the adjective next must also match in both number and gender. Some languages may even require the definite article the to be in there, which also must match in number and gender. So here we have a sentence with three feminine singular words.
However, when he goes to translate chapter, He runs into problems. That is because chapter in many languages is a masculine noun, which means that both the and next must also match in number and gender. They are different words, and the result is that he cannot translate this accurately. Any attempt to translate this would result in something that is incorrect.
Dave DeLoong He runs into problems. That is because chapter in many languages is a masculine noun, which means that both the and next must also match in number and gender. Any attempt to translate this would result in something that is incorrect. Any attempt to translate this would result in something that is incorrect. There are times that we need to display a list of strings to our users. Perhaps we're ordering a list of users, like an address book.
In these cases, we should not be using the compare method on NSString to order this list of strings. Instead, we should be using the NSString method localized standard compare. This is a comparison that will take into account the user's locale and it will do a case sensitive and diacritic sensitive comparison. As such, you should avoid using case and diacritic insensitivity. Let's see why. Our example is that we have these eight strings in Swedish.
And we want to see how they get sorted using these three NSString methods: compare, localized case-insensitive compare, and localized standard compare. If we sort them according to the compare method, we'll get this. For users in your Swedish locale, this is incorrect. In Swedish, A with the umlaut comes lexically after all the other characters in the alphabet. It is similar to-- it comes after the letter Z. Additionally, the lowercase letters should precede the uppercase letters. So compare is the wrong thing to use in this situation. If we tried to do a case-insensitive compare, we'll get this.
The A with the umlaut is correctly placed at the end of the list, but because we want everything to be consistent, this is still wrong. In some cases, uppercase letters come before lowercase letters, and so we should not use case-insensitive searching either, or sorting. If we use localized standard compare, we get the correct result. Lowercase letters come first, and the A with the umlaut is at the end of the list.
Similar to sorting is searching through strings. Here you may use casein diacritic insensitivity. However, You should avoid using range of string. This is because range of string does not take into account the user's locale. It uses the system locale. The system locale is the ultimate fallback locale. It is the locale to which all other locales run if they can't provide an answer themselves.
So when searching through strings, please remember to specify the locale yourself using the range of strings options range locale method passing in the current locale or the auto updating current locale. Some of our users may be running their device in Hebrew or Arabic, which are right to left languages. However, for some of them, and in some situations, they can't find the correct location.
So they can actually have mixed directionality. So their right to left text can contain left to right substrings. For example, if we have this string in Arabic, it contains parts that are right to left, a portion that is left to right, and another part that is right to left. However, when this string is stored in memory, it is stored in a single logical order.
And so if you are doing any manipulations on this string, such as needing to search through this string character by character or word by word, you need to remember about directionality. On the subject of parsing, the key thing to remember is that not every character fits inside a unichar.
What does this mean? Let's say we have the string capital E with a cute accent and we want to know how long it is. Well, we use the length method, right? So if we use the length method on this string to see how long it is, we'll get one. Let's say that we've got the string capital E with a cute accent and we want to know how long it is. So we use the length method and ask it how long it is. What do we get? Two, of course.
What's going on? Well, in the first situation, capital E with a cute accent is being stored as a single character. The second example, however, capital E with acute accent is actually being stored as two characters, the first being the capital letter E and the second being the combining acute accent mark, which means to place an acute accent over the previous character.
Extending this a little further, let's say we want to know how long Hamburger is. And so we ask, and of course the length of Hamburger is two. Or we want to know how long the US flag is and we ask, and it is four characters long. So if we were to try and iterate through these strings using length and character at index, there are times where we might get a whole character, such as the first example, or there are times where we might only get one fourth of the character, such as with the US flag. So, you should avoid, when dealing with user visible strings, you should avoid using methods such as character at index, length, and also precompose string with canonical mapping.
Dave DeLoong: They will often lead you to the wrong result. What you should use instead is the NSString method, enumerate substrings in range options using block. This is a method that allows you to iterate over a string, perhaps character by character, and that will correctly take into account combining accent marks or emoji or et cetera. Or you can iterate through a string word by word. Or sentence by sentence or paragraph by paragraph. And using this method, you can count up how many characters are in a string, what's the 42nd word, et cetera.
So let's talk about some tips to make dealing with text a little bit easier. First off, you can use multiple tables. You're not limited to just localizable dot strings. So going back to our original example that we saw in the beginning, we can add strings tables for perhaps our app delegate, the root view controller, our data view controller.
Providing multiple strings tables adds even more context to our translators. They know that anything inside the app delegate dot strings table contains text that will only be used by the application delegate. Or text inside the root view controller dot strings table will only be used by the root view controller. And so if they know what is on those screens, they get even more contextual information. This is also good for encapsulation principles.
While you're building your application, you should pseudo-localize it to help you easily find places where you have forgotten to use NSLocalizeString. What is pseudo-localization? It simply means to take your existing strings and manipulate them in such a way so that you can tell if they've been translated or not. So if we were to pseudo-localize this screen, perhaps we get something like this, where we've inserted hyphens between the strings. between the word back.
[Transcript missing]
Now what about this thing down here? Well, as it turns out, that is the name January in Thai. Why does this not have hyphens? This doesn't have hyphens because it's being provided by an NSDate formatter. For things such as dates and numbers, you do not need to translate them yourselves.
They can be translated for you by using the NSDate formatter and NSNumber formatter classes. On the subject of pseudo-localization, you can also use custom macros, as I mentioned earlier. So normally, you might use NSLocalizeString to get the title of a back button. What you can also do is to find your own function, in this case, ICT root view controller string.
You can then define your own function, ITT root view controller string that takes a key and a comment, just like NSLocalizeString. And remember how NSLocalizeString simply passes on to NSBundle to find the correct string? Well, we can do the same thing ourselves. And then perhaps if we're running in debug mode, we can pseudo-localize it. So we can encapsulate all of our pseudo-localization logic in one place without actually having to generate entirely new strings files.
This also works with the GenStrings utility. With GenStrings, you can tell it to instead of looking for NSLocalizeString, to look for ITTRootViewControllerString. And then it will also look for ITTRootViewControllerString in table, ITTRootViewControllerString with default value, and so on. Let's talk about dates. We have observed many people doing some pretty bad things with dates.
So let's talk about what a date is. An NSDate object is simply a wrapper around an NSTimeInterval. That's it. It is nothing more than that. It is nothing less. It is specifically a time interval relative to a well-defined point in time. It does not have any calendar associated with it.
It does not have any time zone associated with it. It is simply a point in time. So for example, here is now. And of course, as we know, time extends into the past and goes forward into the future. That's all an NSDate is. It's just a point in time.
We can format a date for visual consumption using an NSDate formatter. An NSDate formatter converts a date to and from human readable form. It is created automatically with the current system settings. It is locale sensitive, as we saw earlier how the name of the month was automatically translated for us into Thai.
And by far the easiest way to convert a date into a string is to use the class method, localizeStringFromDate, dateStyle, timeStyle. The dateStyle and timeStyle parameters allow you to tell the date formatter whether you want no date and simply want the time, whether you want a long style, so for example, the full spelled out name of the month and day with the year and so on. That's really going to cover about 90% of your use cases.
However, there are times where you may need to specify a format string. Well, You think you do, but you should really avoid doing that as much as possible. Let's see this example, where we want to format a date with the month, the day, and the year. So we put in the format string m/dd/yyyy.
When we localize a date using this date formatter, we'll get this. For our users in the United States, that may be the correct format. However, for your users in China, that is not how they display dates. And that is wrong. So what you should instead do is use the appropriate date style, in this case a short date style. And now your dates will be correctly localized according to the user's settings and preferences. So for your users in China, they will see dates according to their preference. And for your users in the United States, they will see dates formatted according to their preferences and format settings.
But what about for situations where the date and time styles don't meet your needs? The date styles generally show the full year-month day. But what if you just want to show the month in the day? Well for that, you might try doing something like this, just using the month and the day format string.
Well, for your users in France, they'll get the name of the month followed by the day of the month. And for your users in China, they will get the month with the appropriate month symbol followed by the day. These are both wrong. So what should you be doing instead? There is a class method on NSDateFormatter called DateFormatFromTemplateOptionsLocale.
This is a method that allows you to pass that same format string you would have used and get a localized version of that string according to the locale you pass in. So we'll take that same date format string, asking for the month and the day, and passing in our current locale, and it will return a format string. Doesn't matter what the string is, we just take it and set it into our date formatter.
Now when we localize our date, we can see that the date format string for our users in France returns the day before the name of the month. And for your users in China, you will get the month followed by the day with the appropriate symbols after each component. Occasionally, we may ask our users to type in a date for us, or we may receive a date over a network connection and we'll need to parse it into an NSDate object. We need to remember in these situations to explicitly configure the date formatter object.
So let's say we are given the string 06-15-2012 and we want to turn it into an NSDate. If we simply do this, then perhaps in many situations we'll get the correct NSDate. But if our users are, say, using the Japanese imperial calendar, where the current year is the year 24, then 6/15/2012 represents a date that is nearly 2,000 years in the future.
Or if your users are using the Hebrew calendar, the date 6/15/2012 represents a date that is 3,500 years in the past. So how do we fix this? We need to tell the date formatter what calendaring system we're using. And so we instantiate the appropriate NSCalendar object, and then we tell the date formatter, use this calendar for interpreting this date. What is NSCalendar? Optionally, there are times where you will receive an unlocalized date. Perhaps it's a timestamp given to you by a server.
In this case, you can use the C function, strptime_l, to parse that string into a time interval from which you can then construct an NSDate object. So, this is all being driven by NSCalendar. NSCalendar is an object that represents a calendaring system. We have support for the Gregorian calendar, of course, Japanese, Imperial, Persian, Islamic, Hebrew, Buddhist, many different calendars.
And this calendar object contains all of the calculations and transformations and algorithms necessary to properly convert between strings and dates in that calendaring system. It can also do things like Adding intervals to dates. Say you want to add one week to a date. It can handle that as well.
So if we have time again, today is June 15, 2012. So we have an NSDate object representing today. This is that NSDate formatted according to the Gregorian calendar. However, that same NSDate can be formatted in many different ways depending on what calendar object we are using. Remember that an NSDate is simply a wrapper around a time interval.
Since it contains no inherent calendaring information, we need to use a calendar to turn that into a human readable date. So these seven examples are all the same NSDate object, just formatted using different calendars. Another problem that we run into sometimes when trying to parse a string into a date is forgetting the time zone.
So when we are parsing a date and storing a date, we need to remember what time zone we are in. So for example, let's say we have this string representing a little bit earlier this morning, and we want to turn it into an NSDate. Well, if we simply just construct a date formatter and set the format, then for our users in San Francisco, being seven hours behind Greenwich Mean Time, they will get an NSDate for 9.42 that morning. However, your users in Beijing, being eight hours ahead of Greenwich Mean Time, will also get an NSDate representing their 9.42 in the morning. This is incorrect.
What we instead need to do is tell the date formatter, this string is actually in Greenwich Mean Time already. Then when we try to parse it, for users in San Francisco, they will get 2:42 in the morning, being seven hours behind GMT. And for your users in Beijing, they will get 5:42 in the evening, being eight hours ahead.
You do not need to worry about adding time intervals to the date yourself. Simply use the correct time zone when configuring your date formatter. So NSTimeZone. All it is is simply another wrapper around a time offset. Except this one, instead of being relative to a specific point in time, is relative to coordinated universal time.
So, if we have time again, with a date representing now, then we can format that same NSDate object in many different ways simply by changing the time zone of the date formatter. So 4:42 PM is the same date as 9:42 AM in Pacific Daylight Time and is the same NSDate as 1:42 AM in Japan.
So, some tips on dealing with dates properly. How many of you have seen this before? 86,400, or 24 times 60 times 60. 99% of the time, this is the wrong thing to use. One date is not 86,400 seconds. One day is not 24 hours. Let's say we have a date corresponding to March 10th, 2012 at 11:50 p.m. and we want to add 24 hours to it. So we add 86,400, expecting March 11th at 11:50 p.m., but we're surprised to find that we actually get March 12th.
What's going on? Well, in many places around the world, March 11th is when the daylight savings time transition happened. And so there were only 23 hours on that day. So by explicitly adding 24 hours, we have ended up with the wrong result. What should we do to fix this? Well, we can simply construct an NSDateComponents object.
And we tell it to represent one day and then ask our calendar to add one day to that date for us. And NS Calendar is smart enough to know about things like daylight savings time transitions. It will correctly handle days that are 23 hours long, 24 and 25 hours long. And so when we add one day to March 10th at 11:50 PM, we correctly get March 11th at 11:50 PM. This is done, as I mentioned, using the date components class.
NSDate components represent portions of a calendrical date. NSDate, as we remember, is a point in time. NSDate components are the various parts of a formatted human readable date. And as we saw, it can be a relative difference. So, for example, if we have this NSDate components object, we can set the month to 13, the date to 3, and the year to 2000.
Now this can be perhaps an interval of 13 months, three days, and 2,000 years. Or if we were to use a calendar to convert this back into an NSDate, then in some calendaring systems which have 13 months, that might be the third day of the 13th month of the 2000th year. If we use a calendar like the Gregorian calendar, for example, we could actually get January 3rd of the year 2001. NS calendar will handle unit rollovers for us.
There are times where we may need to know how long a unit is. Say we're building a calendaring app and we want to know how many months should we draw. Well, we should not assume there are 12, because many calendars have 13. So we can ask our calendar, how long are months? How many months in a year do you have? And we'll get back 13 in many situations, perhaps 12 in others.
We can also ask how long is this unit for a specific date? So given a date of June 15th, we can ask how many days are in that month and we will get back an NS range of 1 to 30. That means that the first day of the month is the first and there are 30 days in that month. Should not try to hard code in a leap year algorithm yourself. Simply use NSCalendar to ask how many days are in, for example, February. And it will tell you either 28 or 29, depending on the year.
We talked a little bit earlier about date templates and localizing format strings when the built-in options don't allow for what we want to do. There is a special case to this, and that is with time. How should we know whether to use AM or PM when displaying a time? Do we know whether the user prefers 1 through 12 with an AM or PM or perhaps 0 through 23 or perhaps even 1 through 24? We can use a special template called J.
When you run j through the date format from template method on NSDateFormatter, it will return a time format string specific to that locale. So in the United States, which uses an AM and PM, you will get back 1 through 12 and an AM and PM. Or for your users in France, you will get 00 through 23. And it will also correctly handle whether or not the AM and PM should come before the hour, such as in China or Korea. So you can use J for formatting an hour according to your user's settings.
The final thing to remember when dealing with dates is that 1+1 does not always equal 2. Let's say we have these three months, January, February, and March. And we want to add two months. to January 31st. So we add one month and get the last day of February, the 28th. And we add one month again and end up with March 28th. Well, March 28th isn't really two months after January 31st.
So when doing date additions like this, you should always be doing your additions relative to your starting date and not to any intermediate dates. If we start with January 31st and simply add two months directly to it, we will end up with the correct March 31st. So don't use intermediate dates for date calculations. Let's talk about numbers.
Numbers are fairly simple. Like NSDateFormatter, we have a built-in class called NSNumberFormatter, and it is similar to DateFormatter in many respects. It is initialized with your current system settings and as such is locale sensitive. And by far the easiest way to format a number for human readable display is using a class method, localized string from number with a corresponding number style.
The number styles that we provide cover a variety of options. There's general. You can format for currency, format as a percentage, format in scientific notation, and also format the number spelled out. One thing to point out here is that when you format a number as a percentage, it is actually multiplied by 100. So if you want to format a number as 50%, you should be passing in 0.5.
If you were to pass in 50 to be localized as-- or to be formatted as a percentage, you would end up with 5,000%. NSNumberFormatter is also sensitive to the user's locale. It will use the correct character set for the digits. It will use the correct decimal and grouping separator. Many places in Europe, for example, use the comma as the decimal separator.
It will also use the correct currency symbol of your user's locale and will even handle symbols like the percentage sign. There are some things to be aware of, though. When you're parsing a number or a string as a number, you should avoid using methods such as int value on NSString or functions like scanf.
The simple example is trying to parse the string ASDF. If we were to ask that string for its int value, we would get back zero because there is no number in that string. However, if we have the string zero and ask for its int value, we also get back zero. And so there is no way to disambiguate whether or not the source string was actually a number or not.
So, of course, we should use an NSNumber formatter. And in our case, we want it to be parsing numbers according to the decimal style. And now when we parse those strings, we will get back nil for an invalid number and we will get back an actual NSNumber instance for the string zero. Related to parsing is that of printing.
If we were to use methods such as string with format or printf in order to try and format a number for display, we may try doing something like this. Well, for users in the United States, that might be correct. However, for your users in Egypt, that's incorrect. First off, that's not the character set they use for their digits. So again, we should be using the decimal style here. And since NSNumber is locale sensitive, it will use the correct character set and the correct decimal separator. In Mountain Lion and iOS 6 is this method, localize string with format.
I should clarify, this method has been around for a while. However, prior to Mountain Lion and iOS 6, when you tried to use this method, it would only change the decimal separator according to the locale. This method has been updated for Mountain Lion and iOS 6 to now do the correct thing. And so the previous example works just as well using localized string with format. However, for compatibility reasons, it will only have this behavior for a while. Dave DeLoong With NSDateFormatter, we can set a specific date format to use.
We can do the same thing with an NSNumber formatter. However, when we do so, we are overriding any information the locale might provide. So if we wanted to format a number like this, With a currency symbol, perhaps some thousands, we have a thousands separator and two decimal places. Then for your users in the United States, you might be getting the correct result. However, for your users in China, they don't use the dollar sign for their currency symbol. So instead, I should be setting the number style to ask it to format our number as currency.
And now we will get the correct currency symbol being used. Some tips for dealing with numbers. The previous example showed 241.23 yuan as correct for China. Well, yes and no. It may be using the correct symbol, but it might not be the correct number. So don't forget to do unit conversion. Just because you're using, you know,
[Transcript missing]
Now, we don't provide any unit conversion for you. However, you can ask the NSLocale object what you should be doing. The Locale object will tell you whether you should be using the metric system or not. It will also tell you what currency code you should be using. And using that information, you can then perhaps call a web service to convert the currency for you. or simply do some unit conversion for distance yourself.
Also, as we saw with dates, using the strptime function, we can do the same thing with unlocalized numbers using the str2 whatever function. So str2d for integers, str2f for floats, and so on. But really, you want to use the predefined formats as much as possible, since they are the formats that will take care of all of the special edge cases for you.
Finally, let's talk about images. There is a single overriding rule to be aware of when dealing with localizing your images. And that is beware. Localizing images is fraught with peril. Let's say, for example, that you're writing a children's application and you have centered your application around this wordplay, be clever.
I think you're starting to understand how this might be problematic. You should really try to avoid any sort of linguistic puns or word play when dealing with images in your application. For example, if we were to translate "be clever" literally into Spanish, we might end up with something like "abeja inteligente," which literally means "smart honeybee." It's not what we're going for, so please avoid doing any sort of jokes with images. The rare circumstances where you will need to localize your images are when you're dealing with culture-specific content.
For example, let's say your children's application has a stop sign in it, and so you, being the internationally savvy person you are, have decided to just ship the red octagon. And then at runtime, you're going to superimpose the correct text, whether it's in Arabic, French, Portuguese, or Korean.
Well, then you start getting reports back from your users in Japan saying that their stop signs look funny. Why is that? Well, in Japan, stop signs are actually triangles. And so this is a situation where, due to the culture, you will need to localize your image to account for that specific culture. But again, you would simply just ship the red triangle image and then superimpose the correct text at runtime. This allows you to be as flexible as possible.
However, there's nothing else you need to really change because you can still use the same image named methods to retrieve the appropriate image. Image named will go through NSBundle to find the correct image, and NSBundle takes your language into account when finding the correct resource to use. There are also situations where you have text in images. This is usually a bad idea simply because it really increases the number of assets you have to ship.
But in case you do, let's say you have a question mark in an icon somewhere, you may need to ship another version that has the question mark inverted. So what have we talked about today? First off, we've learned that standards vary. Whether it's currency symbols or how we spell months or what images we should use, standards vary from region to region. And so we should be considerate of how we spell months. And so we should be considerate of how we spell months.
We've also learned that cultures vary. Images that might make sense to us in our locale might be meaningless to other users. Jokes that are funny to us may be nonsensical to them or at worst even horrendously offensive. So please remember that not everybody shares your culture. As you're building your applications, do not forget to test them, both in your own language and in other languages. Pseudolocalization can help, but it is not a panacea. So please ask your beta testers to run the application in other languages. Ideally, they may even speak that native language themselves.
And they can be the ones to let you know, hey, your stop signs actually need to be a triangle or hey, that image is actually really crude. And finally, please use the built-in APIs as much as possible. These are APIs that have been over 20 years in development, and nothing you can do will approach that. So please take advantage of our hard work.
If you have more information, you can contact our evangelist, Paul Marcos. There's also a really great guide in the documentation called "Introduction to Internationalization Programming Topics." And of course, you can go on to the dev forums to ask for specific help. There are some related sessions. We've already talked about Auto Layout, but please don't forget about accessibility. You have correctly internationalized your UI. But not all of your users can see or not all of your users can hear. Please remember to also remember accessibility when internationalizing your application. Thank you for coming.