A no-bullshit guide for translating your iOS app.

May 19th, 2014 • 0 commentspermalink

There are 7 billion people in the world, and only 5-10% of us speak English. At some point, you’re going to want your app to reach those outside the English-speaking world. The most important way to help that process along is to translate your app into other languages.

This article will go into the process of translating, but not localizing, your app. What’s the difference? Translation is strictly changing words into other languages. It’s a part of localization, but localization is much larger than just language. Localizing is knowing that French quotation marks look like «», knowing that the British write dates “16 May 2014”, knowing that ß sorts as “ss” in German, and knowing that Americans don’t use the metric system.

I’ll be writing a follow-up article on localization soon, so stay tuned!

The process of translating an iOS app is pretty straightforward, if somewhat tedious. There are three major steps of setup, which together have you mark every displayed string in the app for translation. Then, there are three steps of work to do before every release, which extract the marked strings, give them to you for translation, and put them back as translated strings files.


Work to do for the first translation

1. Mark all display strings in code.

Mark all of your app’s literal strings (for display) with NSLocalizedString. If you don’t have a literal string, use -[NSBundle localizedStringForKey:value:table:] and add the key to a strings file.

Before:

NSString *formatString = @"My iPhone is at (%f, %f).";
label.text = [NSString stringWithFormat:formatString, latitude, longitude];
label2.text = stringFromNetwork;

After:

NSString *formatString = NSLocalizedString(@"My iPhone is at (%f, %f).",
                                           @"(<latitude>, <longitude>)");
label.text = [NSString stringWithFormat:, latitude, longitude];
label2.text = [[NSBundle mainBundle] localizedStringForKey:keyFromNetwork
                                                     value:keyFromNetwork
                                                     table:@"Filename"];

(and then in Filename.strings, add "The actual key from the network" = "The value that you want to show in English";)

2. Localize your nibs and storyboards with Base Localization.

Base Localization

Turn on Base Localization (if it’s not already on—it defaults to on in new projects). Then, add all the languages that you want to localize into.

Open a nib or storyboard. In the File Inspector (first icon) in the right sidebar of Xcode, there should be a “Localize…” button or a “Localization” section.

If there’s a “Localize…” button, push it, and localize the nib or storyboard as “Base”. Then, check all the checkboxes in the “Localization” section, and make sure all languages are localized as “Localizable Strings”.

Localize ButtonLocalization Checkboxes

3. Localize your images, strings files, and other resources.

In your app, you may have some images that need to be localized, because they have words or other language-specific symbols in them. If you’re using an Xcode Asset Catalog, you should take all of those images out of the catalog. Unfortunately, Xcode 5.1.1 doesn’t support localizing images in asset catalogs…yet.

Turn on localization for each of these files by clicking the “Localize…” button, the same way you did with the nibs. This time, however, localize the image as English (or whatever your main language is). Add more versions of the image by checking the checkboxes, and then replace the files that Xcode generates with your actual localized asset.


Work to do before every release

4. Generate strings files from your code.

Apple makes this great utility called genstrings that extracts the literal strings from your code. Basically, just run genstrings path/to/all/m/files/*, and it’ll generate a Localizable.strings file (and any others, if you use NSLocalizedStringFromTable) for you to upload. There are lots of options for it, if you need to control the output directory, etc.

5. Regenerate nib and storyboard strings files

Xcode makes it easy to generate nib and storyboard strings files…once. But if you change anything in them, it won’t update the .strings files. facepalm.

Since Xcode won’t help us here, we’ll use ibtool to regenerate the strings files. ibtool has one major bug though—it generates UTF-16 .strings files with byte-order marks, and Xcode 5.1.1 generates UTF-8 files with no BOM…and refuses to compile with otherwise-empty files with a BOM. Long story short, you should actually run:

TF="tempfile.strings"
rm -f "$TF"
ibtool --export-strings-file "$TF" View.xib 
iconv -f UTF-16 -t UTF-16BE "$TF" | iconv -f UTF-16BE -t UTF-8 > View.strings
rm -f "$TF"

6. Translate your strings files, then put them back.

I can’t really help you much with this part. It totally depends on how you decide to translate the strings. Basically, the idea here is to take all of the strings in an en.lproj directory, translate them, then put them back in fr.lproj, es.lproj, etc.


And that’s it! Translating your app is the most important of international growth. There’s more to do, though—check back for an odds-and-ends of localization next week!

Objective-C: Subclassing for delegate behavior

May 10th, 2014 • 0 commentspermalink

Have you ever wanted to re-use a delegate implementation? Maybe, the fix for the UITextView return bug? Turns out, with some help from the Objective-C runtime, you totally can! That said, this is definitely a hack.

First, we’ll subclass UITextView. Then, we set ourself as super’s delegate and and create a private externalDelegate property, so that we can keep a second delegate. Then, we implement the return bug fix in textView:shouldChangeTextInRange:replacementText:, calling externalDelegate’s implementation (if it exists).

“But Phil,” you say, “What about all of the other delegate methods? How could our external delegate receive those as well?”

We’ll use the NSObject method forwardingTargetForSelector: to forward methods, and we’ll use the runtime’s protocol_getMethodDescription to tell us if a selector is in UITextViewDelegate or not. Basically, we put them together, and it scales right up!

Code for this fix is here.

Finding a Pull Request

September 25th, 2012 • 0 commentspermalink

GitHub OctocatSometimes, you’re looking back through a git blame, and you want to find the context around that commit. A commit message is nice, but a full description in a pull request is even nicer. Hence, find_pull_request. It’ll just open the pull request that merged that commit into master, full of comments and descriptions about why that particular line was added. Hooray!

Source on GitHub. Dependency on hub.

Derp 2.0!

August 28th, 2012 • 0 commentspermalink

Derp I’m happy to announce that Ricky, Mike, and I have released Derp 2.0!

Rebuilt from the ground up, Derp 2.0 has beautiful new retina images across the entire app; supports iPad, iPhone, and iPod touch; and has a slick new interface, with incredibly innovative new features like a “pause button”. Derp 2.0 also has a completely new physics engine, which provides more realistic collisions and a much more polished experience on the whole.

Once you’ve played it for a while, check out the preferences! We created a hard mode, which adds a “void” across the center of the screen that hides the balls. It means you have much less time to react, and really adds a lot of suspense and excitement to Derp.

Quick Image Hosting from OS X

December 9th, 2011 • 0 commentspermalink

On OS X, Twitter.app’s image hosting services all have a nasty habit of shrinking the image to save bandwidth. Unfortunately, reducing the resolution really ruins screenshots — often, all of the text is lost, which can defeat the point of including it in a tweet.

At first, I turned to CloudApp. Their Mac app, which I highly recommend, has a system-wide hotkey to upload any file. Once it’s done uploading, CloudApp puts a link to the file on the clipboard. It’s a really great system, but it has one flaw for this use: the URLs it generates don’t end in an image extension (e.g. .jpg), so most twitter clients don’t recognize it as an image. Because of that, anyone that reads the tweets has to open their web browser to see the image.

To prevent that, I’ve been using imgur as a host, because they’re fast and free. Plus, they have a massive 1 MB cap on image size. That’s great, but compared to CloudApp, it’s still pretty slow: I have to go to the webpage, select the image, wait for it to upload, copy the URL, and then finally return to Twitter.app. What I really wanted is a CloudApp-like experience for Imgur on my Mac.

So I made one. It’s both a command-line utility and an OS X Service, for easy uploading from both the terminal and the Finder.

Github.

Florida: Right Next to Montréal!

December 6th, 2011 • 0 commentspermalink

A map of the NHL cities, with the new conferences colored in.Today, the NHL Board of Governors voted to approve a new plan to regroup the teams into four new conferences, based on the teams’ locations. That makes sense — some divisions are really spread out. For instance, Dallas is in the same division as San Jose, which is two time zones away. The long flights and big time changes between games are bad for everyone — the players spend a long time traveling and adjusting their daily schedules, and the fans stay up late or leave work early to watch the games.

That said, the re-conferencing could have been done a little better. According to the commissioner, Gary Bettman, the discussion to regroup the conferences only lasted about an hour. Remind me why the Florida teams are in the same conference as Montreal, Toronto, Buffalo, and Boston?

Unwrapped and Over-waxed

November 30th, 2011 • 1 commentpermalink

A Bag of Unwrapped Reese's MinisI love chocolate and peanut butter, especially together. So naturally, I’m a huge fan of Reese’s cups. But the normal ones have too much peanut butter in the middle, and so in the middle of each cup, you get these bites of candy that just aren’t chocolatey enough. But the Mini cups are perfect — they’re smaller, so you eat them all at once, giving an excellent blend of chocolate to peanut butter.

The other day, I tried Reese’s Minis “Unwrapped”. They’re like a small version of the mini cups, except that they’re, well, not wrapped! They look like an awesome product — it’s still the right ratio of chocolate to peanut butter, and they’re WAY easier to eat. Sounds great, right?

Nope. They’re terrible. Hershey’s puts some kind of wax on the outside of each cup, and there’s enough of it so that all you taste is wax. It pretty much destroys the flavor of the whole thing. Until they find a better way to keep the cups fresh without a wrapper, I don’t recommend them.

Gmail’s Welcome Email from 2004

November 16th, 2011 • 0 commentspermalink

I just reread the welcome email that Gmail sent me when I signed up back in 2004. Today, they have a series of welcome pages and emails that let you know how to use the service. It’s arguably more useful now, but they seem to have lost their personality. Gmail’s not awesome anymore — now it’s just boring.

Gmail is different. Here’s what you need to know.

First off, welcome. And thanks for agreeing to help us test Gmail. By now you probably know the key ways in which Gmail differs from traditional webmail services. Searching instead of filing. A free gigabyte of storage. Messages displayed in context as conversations.

So what else is new?

Gmail has many other special features that will become apparent as you use your account. To help you get started, we encourage you to visit our Help Center, there you can browse frequently asked questions, read our Getting Started guide, or contact the Gmail User Support Team. You’ll also find information in the Help Center on such topics as:

  • Importing your contacts from Yahoo! Mail, Outlook, and others to Gmail
  • Using address auto-complete
  • Setting up filters for incoming mail
  • Using advanced search options

You may also have noticed some text ads or related links to the right of this message. They’re placed there in the same way that ads are placed alongside Google search results and, through our AdSense program, on content pages across the web. The matching of ads to content in your Gmail messages is performed entirely by computers; never by people. Because the ads and links are matched to information that is of interest to you, we hope you’ll find them relevant and useful.

We’re working hard during our limited test to improve Gmail and make it the best webmail service around. Thanks for taking the plunge with us. We hope you’ll enjoy Google’s approach to email.

Thanks,

The Gmail Team

P.S. You can sign in to your account any time by visiting http://gmail.google.com

Ruby Extensions: A Brief Tutorial

November 3rd, 2011 • 0 commentspermalink

The Ruby ruby

Ruby is slow, compared to C. But if you write the critical parts of your program in C, you can have Ruby’s rapid development AND C’s runtime speed! Profile your program, and use the steps below to port the slowest inner loops to C!

  1. Download the skeleton of your extension (or look at it on Github). Unzip it into an empty folder.
  2. Read build.sh, which is a short bash script that shows you how to build your extension.
  3. Read extconf.rb, which is the extension configuration script.
  4. The datatype ‘VALUE’ represents EVERYTHING in Ruby. You’ll need to decode your data in order to work with it in C, and re-encode everything in order to return it to Ruby.
  5. When you call a C function from Ruby, the first argument is the receiver of the method, a VALUE called self. Basically, if you want a two-argument Ruby function, you’ll need three arguments in the C version of it.
  6. Read yourextension.c. It outlines the main steps in a simple Ruby extension.
  7. If you want to know more about how extensions work under the hood, or if something isn’t abundantly clear from the file, read the ruby-docs!

Tab Rescue!

August 28th, 2011 • 0 commentspermalink

Tab Rescue!I’ve been trying to find the Mac web browser with the best combination of RAM usage, speed, and graphics card usage. After flitting through Chrome, Firefox, Safari, and even Opera, I’ve come to the conclusion that Firefox is too slow, Chrome eats too much RAM, and Opera is just too foreign. Plus Chrome and Firefox turn on my MacBook Pro’s discrete graphics card, which seriously eats into my battery life. So I’m a Safari user now, which has fringe benefits like Full Screen mode on Lion.

But one thing I’ve missed from other browsers is the ability to quickly and easily bring back tabs and windows I’ve closed. Mostly that’s because I have a nasty habit of opening a ridiculous number of tabs — on the order of 40, 50, or even higher. I think at one point, I hit 100. Anyway, after opening tons of tabs, I’ll try and clean up my browser by closing a bunch of them. But once in a while, I’ll miss and close a tab that was actually important. Currently in Safari, you can bring back the one tab you closed last with ⌘Z, but that’s not really good enough — I tend to realize that I closed the wrong tab after closing a few more. So I needed a way to reanimate dead tabs.

So…I wrote a Safari extension to do just that. It creates a hotkey at I’m calling it Tab Rescue, and you can download it below! Hopefully you like it! Feel free to comment or drop me an email if you have questions, comments, or concerns.

Download