Chapter 17. Application Localization

At the time of this writing, the iPhone is available in 84 different countries and that number will continue to increase over time. You can now buy and use an iPhone on every continent except Antarctica. If you plan on releasing applications through the iPhone App Store, your potential market is considerably larger than just people in your own country who speak your own language. Fortunately, iPhone has a robust localization architecture that lets you easily translate your application (or have it translated by others) into not only multiple languages but even into multiple dialects of the same language. Want to provide different terminology to English speakers in the United Kingdom than you do to English speakers in the United States? No problem.

That is, no problem at all if you've written your code correctly. Retrofitting an existing application to support localization is much harder than writing your application that way from the start. In this chapter, we'll show you how to write your code so it is easy to localize, and then we'll go about localizing a sample application.

Localization Architecture

When a nonlocalized application is run, all of the application's text will be presented in the developer's own language, also known as the development base language.

When developers decide to localize their application, they create a subdirectory in their application bundle for each supported language. Each language's subdirectory contains a subset of the application's resources that were translated into that language. Each subdirectory is called a localization project, also called a localization folder. Localization folder names always end with the extension .lproj.

In the Settings application, the user has the ability to set the language and region format. For example, if the user's language is English, available regions might be United States, Australia, or Hong Kong—all regions in which English is spoken.

When a localized application needs to load a resource, such as an image, property list, or nib, the application checks the user's language and region and looks for a localization folder that matches that setting. If it finds one, it will load the localized version of the resource instead of the base version.

For users who selected French as their iPhones' language and France as their region, the application will look first for a localization folder named fr_FR.lproj. The first two letters of the folder name are the ISO country code that represents the French language. The two letters following the underscore are the ISO two-digit code that represents France.

If the application cannot find a match using the two-digit code, it will look for a match using the language's three-digit ISO code. All languages have three-digit codes. Only some have two-digit codes.

Note

You can find a list of the current ISO country codes on the ISO web site. Both the two- and three-digit codes are part of the ISO 3166 standard (http://www.iso.org/iso/country_codes.htm).

In our previous example, if the application was unable to find the folder named fr_FR.lproj, it will look for a localization folder named fre_FR or fra_FR. All languages have at least one three-digit code; some have two three-digit codes, one for the English spelling of the language and one for the native spelling. When a language has both a two-digit code and a three-digit code, the two-digit code is preferred.

If the application cannot find a folder that is an exact match, it will then look for a localization folder in the application bundle that matches just the language code without the region code. So, staying with our French-speaking person from France, the application would next look for a localization project called fr.lproj. If it didn't find a language project with that name, it would try looking for fre.lproj, then fra.lproj. If none of those was found, it would look for French.lproj. The last construct exists to support legacy Mac OS X applications, and generally speaking, you should avoid it (though there is an exception to that rule that we'll look at later in this chapter).

If the application doesn't find a language project that matches either the language/region combination or just the language, it will use the resources from the development base language. If it does find an appropriate localization project, it will always look there first for any resources that it needs. If you load a UIImage using imageNamed:, for example, it will look first for an image with the specified name in the localization project. If it finds one, it will use it. If it doesn't, it will fall back to the base language resource.

If an application has more than one localization project that matches, for example, a project called fr_FR.lproj and one called fr.lproj, it will look first in the more specific match, in this case fr_FR.lproj. If it doesn't find the resource there, it will look in the fr.lproj. This gives you the ability to provide resources common to all speakers of a language in one language project, localizing only those resources that are impacted by differences in dialect or geographic region.

You only have to localize resources that are affected by language or country. If an image in your application has no words and its meaning is universal, there's no need to localize that image.

Using String Files

What do we do about string literals and string constants in your source code? Consider this source code from the previous chapter:

UIAlertView *alert = [[UIAlertView alloc]
    initWithTitle:@"Error accessing photo library"
          message:@"Device does not support a photo library"
         delegate:nil
    cancelButtonTitle:@"Drat!"
    otherButtonTitles:nil];
    [alert show];
    [alert release];

If we've gone through the effort of localizing our application for a particular audience, we certainly don't want to be presenting alerts written in the development base language.

The answer is to store these strings in special text files call string files. String files are nothing more than Unicode (UTF-16) text files that contain a list of string pairs, each identified by a comment.

Here is an example of what a strings file might look like in your application:

/* Used to ask the user his/her first name */
"First Name" = "First Name";

/* Used to get the user's last name */
"Last Name" = "Last Name";

/* Used to ask the user's birth date */
"Birthday" = "Birthday";

The values between the /* and the */ characters are just comments for the translator. They are not used in the application and can safely be excluded, though they're a good idea. They give context, showing how a particular string is being used in the application.

You'll notice that each line lists the same string twice. The string on the left side of the equals sign acts as a key, and it will always contain the same value regardless of language. The value on the right side of the equals sign is the one that gets translated to the local language. So, the preceding strings file, localized into French, might look like this:

/* Used to ask the user his/her first name */
"First Name " = "Prénom";

/* Used to get the user's last name */
"Last Name " = "Nom de famille";

/* Used to ask the user's birth date */
"Birthday" = "Anniversaire";

Creating the Strings File

You won't actually create the strings file by hand. Instead, you'll embed all localizable text strings in a special macro in your code. Once your source code is final and ready for localization, you'll run a command-line program, named genstrings, which will search all your code files for occurrences of the macro, pulling out all the unique strings and embedding them in a localizable strings file.

Here's how the macro works. Let's start with a traditional string declaration:

NSString *myString = @"First Name";

To make this string localizable, you'll do this instead:

NSString *myString = NSLocalizedString(@"First Name",
    @"Used to ask the user his/her first name");

The NSLocalizedString macro takes two parameters. The first is the string value in the base language. If there is no localization, the application will use this string. The second parameter will be used as a comment in the strings file.

NSLocalizedString looks in the application bundle, inside the appropriate localization project, for a strings file named localizable.strings. If it does not find the file, it returns its first parameter, and the string will appear in the development base language. Strings are typically displayed only in the base language during development, since the application will not yet be localized.

If NSLocalizedString finds the strings file, it searches the file for a line that matches the first parameter. In the preceding example, NSLocalizedString will search the strings file for the string "First Name". If it doesn't find a match in the localization project that matches the user's language settings, it will then look for a strings file in the base language and use the value there. If there is no strings file, it will just use the first parameter you passed to the NSLocalizedString macro.

Let's take a look at this process in action.

Real-World iPhone: Localizing Your Application

We're going to create a small application that displays the user's current locale. A locale (an instance of NSLocale) represents both the user's language and region. It is used by the system to determine what language to use when interacting with the user and to determine how to display dates, currency, and time information, among other things. After we create the application, we will then localize it into other languages. You'll learn how to localize nib files, string files, images, and even your application's icon. You can see what our application is going to look like in Figure 17-1. The name across the top comes from the user's locale. The words down the left side of the view are static labels that are set in the nib file. The words down the right side are set programmatically using outlets. The flag image at the bottom of the screen is a static UIImageView.

The LocalizeMe application shown with three different language/region settings

Figure 17.1. The LocalizeMe application shown with three different language/region settings

Let's hop right into it. Create a new project in Xcode using the view-based application template, and call it LocalizeMe. If you look in the 17 LocalizeMe folder, you'll see a subfolder named Resources. Inside Resources, you'll find a directory named Base Language. In that folder, you'll find two images, icon.png and flag.png. Drag both of those to the Resources folder of your project. Now, single-click LocalizeMe-Info.plist, and set the Icon file value to icon.png so that the icon image will be used as your application's icon.

We need to create outlets to a total of six labels: one for the blue label across the top of the view and five for the words down the right-hand side. Expand the Classes folder, single-click LocalizeMeViewController.h, and make the following changes:

#import <UIKit/UIKit.h>

@interface LocalizeMeViewController : UIViewController {
    UILabel *localeLabel;
    UILabel *label1;
    UILabel *label2;
    UILabel *label3;
    UILabel *label4;
    UILabel *label5;
                }
@property (nonatomic, retain) IBOutlet UILabel *localeLabel;
@property (nonatomic, retain) IBOutlet UILabel *label1;
@property (nonatomic, retain) IBOutlet UILabel *label2;
@property (nonatomic, retain) IBOutlet UILabel *label3;
@property (nonatomic, retain) IBOutlet UILabel *label4;
@property (nonatomic, retain) IBOutlet UILabel *label5;
@end

Now double-click the LocalizeMeViewController.xib file to open the file in Interface Builder. Once it's open, drag a Label from the library, and drop it at the top of the window. Resize it so that it takes the entire width of the view from blue guide line to blue guide line. With the label selected, make the text bold using

The LocalizeMe application shown with three different language/region settings

You can also make the font size larger if you wish. To do that, select Show Fonts from the Font menu. Make the font as large as you like. As long as Adjust to fit is selected in the attributes inspector, the text will be resized if it gets too long to fit.

With your label in place, control-drag from the File's Owner icon to this new label, and select the localeLabel outlet.

Next, drag five more Labels from the library, and put them against the left margin using the blue guide line, one above the other, as shown in Figure 17-1. Double-click the top one, and change it from Label to One. Repeat that step with the other four labels you just added so that they contain the numbers from one to five spelled out.

Drag five more Labels from the library, this time placing them against the right margin. Change the text alignment using the attributes inspector so that they are right aligned, and increase the size of the label so that it stretches from the right blue guide line to about the middle of the view. Control-drag from File's Owner to each of the five new labels, connecting each one to a different numbered label outlet. Now, double-click each one of the new labels, and delete its text. We will be setting these values programmatically.

Finally, drag an Image View from the library over to the bottom part of the view. In the attributes inspector, select flag.png for the view's Image attribute, and resize the image to stretch from blue guide line to blue guide line. Next, on the attributes inspector, change the Mode attribute from Center to Aspect Fit. Not all flags have the same aspect ratio, and we want to make sure the localized versions of the image look right. Selecting this option will cause the image view to resize any other images put in this image view so they fit, but it will maintain the correct aspect ratio (ratio of height to width). If you like, make the flag taller, until the sides of the flag touch the blue guide lines.

Save and close the nib file, and head back to Xcode. Single-click LocalizeMeViewController.m, and insert the following code at the top of the file:

#import "LocalizeMeViewController.h"

@implementation LocalizeMeViewController
@synthesize localeLabel;
@synthesize label1;
@synthesize label2;
@synthesize label3;
@synthesize label4;
@synthesize label5;

- (void)viewDidLoad {

    NSLocale *locale = [NSLocale currentLocale];
    NSString *displayNameString = [locale
        displayNameForKey:NSLocaleIdentifier
        value:[locale localeIdentifier]];
    localeLabel.text = displayNameString;

    label1.text = NSLocalizedString(@"One", @"The number 1");
    label2.text = NSLocalizedString(@"Two", @"The number 2");
    label3.text = NSLocalizedString(@"Three", @"The number 3");
    label4.text = NSLocalizedString(@"Four", @"The number 4");
    label5.text = NSLocalizedString(@"Five", @"The number 5");
    [super viewDidLoad];
}
...

Also, add the following code to the existing viewDidUnload and dealloc methods:

...
    - (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.localeLabel = nil;
    self.label1 = nil;
    self.label2 = nil;
    self.label3 = nil;
    self.label4 = nil;
    self.label5 = nil;
    [super viewDidUnload];
}

- (void)dealloc {
    [localeLabel release];
    [label1 release];
    [label2 release];
    [label3 release];
    [label4 release];
    [label5 release];
    [super dealloc];
}
@end

The only thing we need to look at in this class is the viewDidLoad method. The first thing we do there is get an NSLocale instance that represents the users' current locale, which can tell us both their language and their region preferences, as set in their iPhone's Settings application.

NSLocale *locale = [NSLocale currentLocale];

Looking at the Current Locale

The next line of code might need a little bit of explanation. NSLocale works somewhat like a dictionary. There is a whole bunch of information that it can give us about the current users' preferences, including the name of the currency they use and the date format they expect. You can find a complete list of the information that you can retrieve in the NSLocale API reference.

In this next line of code, we're retrieving the locale identifier, which is the name of the language and/or region that this locale represents. We're using a function called displayNameForKey:value:. The purpose of this method is to return the value of the item we've requested in a specific language.

The display name for the French language, for example, would be Français in French, but French in English. This method gives us the ability to retrieve data about any locale so that it can be displayed appropriately to any users. In this case, we're getting the display name for the locale in the language of that locale, which is why we pass in [locale localeIdentifier] in the second argument. The localeIdentifier is a string in the format we used earlier to create our language projects. For an American English speaker, it would be en_US and for a French speaker from France, it would be fr_FR.

NSString *displayNameString = [locale
          displayNameForKey:NSLocaleIdentifier
          value:[locale localeIdentifier]];

Once we have the display name, we use it to set the top label in the view:

localeLabel.text = displayNameString;

Next, we set the five other labels to the numbers one through five spelled out in our development base language. We also provide a comment telling what each word is. You can just pass an empty string if the words are obvious, as they are here, but any string you pass in the second argument will be turned into a comment in the strings file, so you can use this comment to communicate with the person doing your translations.

label1.text = NSLocalizedString(@"One", @"The number 1");
label2.text = NSLocalizedString(@"Two", @"The number 2");
label3.text = NSLocalizedString(@"Three", @"The number 3");
label4.text = NSLocalizedString(@"Four", @"The number 4");
label5.text = NSLocalizedString(@"Five", @"The number 5");

Trying Out LocalizeMe

Let's run our application now. You can use either the simulator or a device to test this. The simulator does seem to cache some language and region settings, so you may want to do this on the device if you have that option. Once the application launches, it should look like Figure 17-2.

By using the NSLocalizedString macros instead of static strings, we are all ready for localization. But we are not localized yet. If you use the Settings application on the simulator or on your iPhone to change to another language or region, the results would look essentially the same, except for the label at the top of the view (see Figure 17-3).

The language running under the authors' base language. Our application is set up for localization but is not yet localized.

Figure 17.2. The language running under the authors' base language. Our application is set up for localization but is not yet localized.

The nonlocalized application run on an iPhone set to use the French language

Figure 17.3. The nonlocalized application run on an iPhone set to use the French language

Localizing the Nib

Now, let's localize the nib file. The basic process for localizing any file is the same. In Xcode, single-click LocalizeMeViewController.xib, and then press

Localizing the Nib
The LocalizeMeViewController.xib Info window

Figure 17.4. The LocalizeMeViewController.xib Info window

When you click the Make File Localizable button, the window will switch to the Targets tab. Close the Info window, and look at the Groups & Files pane in Xcode. Notice that the LocalizeMeViewController.xib file now has a disclosure triangle next to it, as if it were a group or folder. Expand it, and take a look (see Figure 17-5).

Localizable files have a disclosure triangle and a child value for each language or region you add.

Figure 17.5. Localizable files have a disclosure triangle and a child value for each language or region you add.

Looking at the Localized Project Structure

In our project, LocalizeMeViewController.xib has one child, English. This one was created automatically, and it represents your development base language. Go to the Finder, and open your LocalizeMe project folder. You should see a new folder named English.lproj (see Figure 17-6).

By making a file localizable, Xcode created a language project folder for our base language.

Figure 17.6. By making a file localizable, Xcode created a language project folder for our base language.

At the time of this writing, Xcode is still using the legacy project name for the development base language project, English.lproj, rather than following Apple's localization convention of using the ISO two-letter language code, which would have resulted in a folder called en.lproj instead. This is listed in the Xcode 3.1 release notes as a known issue. You don't need to change this folder name, as it will work just fine, but use the ISO codes for any new localizations you add.

Single-click LocalizeMeViewController.xib in the Groups & Files pane again, and press

By making a file localizable, Xcode created a language project folder for our base language.

Tip

When dealing with locales, language codes are lowercase, but country codes are uppercase. So, the correct name for the French language project is fr.lproj, but the project for Parisian French (French as spoken by people in France) is fr_FR.lproj, not fr_fr.lproj or FR_fr.lproj. The iPhone's file system is case sensitive, so it is important to match case correctly.

After you press return, Xcode will create a new localization project in your project folder called fr.lproj and copy LocalizeMeViewController.xib there. In the Groups & Files pane, LocalizeMeViewController.xib should now have two children, English and fr. Double-click fr to open the nib file that will be shown to French speakers.

Warning

Xcode will allow you to localize pretty much any file in the Groups & Files pane. Just because you can, doesn't mean you should. Do not ever localize a source code file. Doing so will cause compile errors, as multiple object files with the same name will be created.

The nib file that opens in Interface Builder will look exactly like the one you built earlier, because the nib file you just created is a copy of the earlier one. Any changes you make to this one will be shown to people who speak French, so double-click each of the labels on the left side and change them from One, Two, Three, Four, Five to Un, Deux, Trois, Quatre, Cinq. Once you have changed them, save the nib, and go back to Xcode. Your nib is now localized in to French. Compile and run the program. After it launches, tap the home button.

iPhone caches settings, so if we were to just go and run the application now, there's a very good chance we would see exactly what we saw before. In order to make sure we see the correct thing, we need to go reset the simulator and do a clean build of our application.

On the simulator, there's a menu option under the iPhone Simulator menu to Reset Content and Settings.... Select that now. After you select it, you should be presented with the home screen. From there, go to the Settings application, and select the General row and then the row labeled International. From here, you'll be able to change your language and region preferences (see Figure 17-7).

Changing the language and region, the two settings that affect the user's locale

Figure 17.7. Changing the language and region, the two settings that affect the user's locale

Change the Region Format from United States to France (which is under the row for French), and then change Language from English to Français. Now click the Done button. And finally, quit the simulator, and go to Xcode. You want to change the Region Format first, because once you change the language, your iPhone will reset and go back to the home screen. Now, your phone is set to use French.

Back in Xcode, and select Clean from the Build menu. Make sure all the checkboxes on the presented sheet are checked, and then click the Clean button. This will remove all traces of the previous build. Once the clean operation is done, build and run LocalizeMe again. This time, the words down the left-hand side should show up in French (see Figure 17-8).

The problem is that the flag and right column of text are still wrong. We'll take care of the flag first. Now, we could have changed the flag image right in the nib by just selecting a different image for the image view in the French localized nib file. Instead of doing that, we'll actually localize the flag image itself. When an image or other resource used by a nib is localized, the nib will automatically show the correct version for the language, though not for the dialect, at the time of this writing. If we localize the flag.png file itself with a French version, the nib will automatically show the correct flag when appropriate.

The application is partially translated into French now.

Figure 17.8. The application is partially translated into French now.

Localizing an Image

Let's localize the flag image now. Single-click flag.png in Xcode's Groups & Files pane. Next, press

Localizing an Image

That's it. You're done. If you are running in the simulator, reset the simulator and do a clean build, as you did earlier before rerunning the program. Once you rerun, you'll need to reset the region and language to get the French flag to appear.

If you're running on the device, your iPhone has probably cached the American flag from the last time you ran the application, let's remove the old application from your iPhone using the Organizer window in Xcode.

Select Organizer from the Window menu, or press ^

Localizing an Image
The Xcode Organizer window lets you manually remove applications.

Figure 17.9. The Xcode Organizer window lets you manually remove applications.

On the Summary tab, you'll see three sections. The bottommost section is labeled Applications. In the list of applications, look for LocalizeMe; select it, and click the minus button to remove the old version of that application and the caches associated with it.

Now, select Clean from the Build menu, and build and run the application again. Once the application launches, you'll need to reset the region then the language and the French flag should now come up in addition to the French words down the left-hand side (see Figure 17-10).

The image and nib are both localized now.

Figure 17.10. The image and nib are both localized now.

Localizing the Application Icon

You can localize the application's icon image in exactly the same way that you localized flag.png. Single-click icon.png in the Groups & Files pane's Resources group. Bring up the Info window, and switch to the General tab if you're not already there. Click the Make File Localizable button, and switch back to the General tab. Click the Add Localization button, and when prompted for the language, type fr.

In the fr folder in the Resources folder of 17 LocalizeMe, where you just copied the flag.png file, you'll also find a localized version of icon.png. Copy that into your fr.lproj folder using the Finder, overwriting the version that's there. Now, the iPhone will automatically detect and show this icon to users who speak French, though you'll probably need to delete the application from your phone again, or reset the simulator to get the change to show up.

Generating and Localizing a Strings File

If you look at Figure 17-10, you'll see that the words on the right-hand side of the view are still in English. In order to translate those, we need to generate our base language strings file and then localize that. In order to accomplish this, we'll need to leave the comfy confines of Xcode for a few minutes.

We localized our application icon!

Figure 17.11. We localized our application icon!

Launch Terminal.app, which is in /Applications/Utilities/. When the terminal window opens, type cd followed by a space. Don't press return.

Now, go to the Finder, and drag your LocalizeMe project folder to the terminal window. As soon as you drop the folder onto the terminal window, the path to the project folder should appear on the command line. Now, press return.

The cd command is Unix-speak for "change directory," so what you've just done is steer your terminal session from its default directory over to your project directory.

Our next step is to run the program genstrings and tell it to find all the occurrences of NSLocalizedString in our .m files in the Classes folder. To do this, type the following command, and then press return:

genstrings ./Classes/*.m

When the command is done executing (it just takes a second on a project this small) you'll be returned to the command line. In the Finder, look in the project folder for a new file called Localizable.strings. Drag that to the Resources folder in Xcode's Groups & Files pane, but when it prompts you, don't click the Add button just yet.

Tip

You can rerun genstrings at any time to re-create your base language file, but once you have had your strings file localized into another language, it's important that you don't change the text used in any of the NSLocalizedString() macros. That base-language version of the string is used as a key to retrieve the translations, so if you change them, the translated version will no longer be found, and you will either have to update the localized strings file or have it retranslated.

Localizable.strings files are encoded in UTF-16, which is a two-byte version of Unicode. Most of us are probably using UTF-8 or a language-local encoding scheme as our default encoding in Xcode. When we import the Localizable.strings file into our project, we need to take that into account. First, uncheck the box that says Copy items into destination group's folder (if needed), because the file is already in our project folder. More importantly, change the text encoding to Unicode (UTF-16) (see Figure 17-12). If you don't do that, the file will look like gibberish when you try to edit it in Xcode.

Importing the Localizable.strings file

Figure 17.12. Importing the Localizable.strings file

Now, go ahead and click the Add button. Once the file is imported, single-click Localizable.strings in Resources, and take a look at it. It should contain five entries, because we use NSLocalizableString five times with five distinct values. The values that we passed in as the second argument have become the comments for each of the strings.

The strings were generated in alphabetical order, which is a nice feature. In this case, since we're dealing with numbers, alphabetical order is not the most intuitive way to present them, but in most cases, having them in alphabetical order will be helpful.

/* The number 5 */
"Five" = "Five";

/* The number 4 */
"Four" = "Four";

/* The number 1 */
"One" = "One";

/* The number 3 */
"Three" = "Three";

/* The number 2 */
"Two" = "Two";

Let's localize this sucker.

Single-click Localizable.strings, and press

Importing the Localizable.strings file

Switch back to the General tab, and click Add Localization. When prompted for a language, type fr to indicate that we are localizing for all dialects of the French language. Back in the Groups & Files pane of Xcode, click the disclosure triangle next to Localizable.strings. Single-click fr, and in the editor pane of Xcode, make the following changes:

/* The number 5 */
"Five" = "Cinq";

/* The number 4 */
"Four" = "Quatre";

/* The number 1 */
"One" = "Un";

/* The number 3 */
"Three" = "Trois";
/* The number 2 */
"Two" = "Deux";

In real life (unless you're multilingual), you would ordinarily send this file out to a translation service to translate the values on the right of the equals signs. In this simple example, armed with knowledge that came from years of watching Sesame Street, we can do the translation ourselves.

Now save, compile, and run—our application is now fully localized for the French language. We've provided you with the information in the Resources subfolder of 17 LocalizeMe to do the German and Canadian French localizations if you want some more practice. You'll find two more copies of the icon.png, flag.png, and Localizable.strings file if you want to try adding support for additional languages.

Auf Wiedersehen

If you want to maximize sales of your iPhone application, localize it as much as possible. Fortunately, iPhone's localization architecture makes easy work of supporting multiple languages, and even multiple dialects of the same language, within your application. As you saw in this chapter, nearly any type of file that you add to your application can be localized, as needed.

Even if you don't plan on localizing your application, get in the habit of using NSLocalizedString instead of just using static strings in your code. With Xcode's Code Sense feature, the difference in typing time is negligible, and should you ever want to translate your application, your life will be much, much easier.

At this point, our journey is nearly done. We're almost to the end of our travels together. After the next chapter, we'll be saying sayonara, au revoir, auf wiedersehen, avρío, arrivederci, and adiís. You now have a solid foundation you can use to build your own cool iPhone applications. Stick around for the going-away party though, as we've still got a few helpful bits of information for you.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.25.144