{"id":94,"date":"2009-12-22T22:25:52","date_gmt":"2009-12-23T03:25:52","guid":{"rendered":"http:\/\/developer.casgrain.com\/?p=94"},"modified":"2009-12-23T07:15:31","modified_gmt":"2009-12-23T12:15:31","slug":"automatically-localize-your-nibs-when-building","status":"publish","type":"post","link":"http:\/\/developer.casgrain.com\/?p=94","title":{"rendered":"Automatically localize your nibs when building"},"content":{"rendered":"<p><em>This post applies to all Cocoa apps, whether on the desktop or on the iPhone.<\/em><\/p>\n<p>\nWhen you want to localize your application, you can take several routes.<\/p>\n<h3>No nibs, only .strings files<\/h3>\n<p>\nThe first one is to not use nibs (or <a href=\"http:\/\/speirs.org\/blog\/2007\/12\/5\/what-are-xib-files.html\">xibs<\/a>, in the new parlance). If you build everything programmatically, <code>+alloc<\/code> and <code>-init<\/code>-ing your controls and your views, you can store all the strings in <code>.strings<\/code> files. These files are simple key-value pairs of Unicode-encoded strings. Call the files &#8220;Localizable.strings&#8221;, drop them in the appropriate language folders (English.lproj, French.lproj, etc&#8230;). <\/p>\n<pre>\n\tEnglish.lproj\/\n\t\tLocalizable.strings\n\t\t\t[file contents]\n\t\t\t\"Dawn\" = \"Dawn\";\n\t\t\t\"Sunrise\" = \"Sunrise\";\n\t\t\t\"Sunset\" = \"Sunset\";\n\t\t\t\"Dusk\" = \"Dusk\";\n\tFrench.lproj\/\n\t\tLocalizable.strings\n\t\t\t[file contents]\n\t\t\t\"Dawn\" = \"Aube\";\n\t\t\t\"Sunrise\" = \"Lever\";\n\t\t\t\"Sunset\" = \"Coucher\";\n\t\t\t\"Dusk\" = \"Cr\u00e9puscule\";\n<\/pre>\n<p>To use, simply call call:<\/p>\n<pre>\n\tNSString *str = NSLocalizedString(@\"Dawn\", nil);\n<\/pre>\n<p>\nThis technique is important even if you have nib files, because most of your strings are probably in <code>.strings<\/code> files already. Don&#8217;t use hard-coded strings for anything that is user-visible. Only use hard-coded keys.<\/p>\n<h3>Use nibs<\/h3>\n<p>\nThis looks easy: duplicate your English nib, move it to the appropriate folder, open it in Interface Builder and translate all the strings directly in IB.<\/p>\n<p>\n<b>This is very bad<\/b>. Don&#8217;t do it.<\/p>\n<ul>\n<li>It&#8217;s unmaintainable: as soon as you change one thing in the original nib, you have to do it in one or more other nibs.<\/li>\n<li>It&#8217;s error-prone: you can easily disconnect a binding without realizing it, and Undo support in IB is spotty at best (I don&#8217;t rely on it). By the time you realize your mistake, it may be too late and you have to revert everything.<\/li>\n<li>It&#8217;s not versionable: although xib files are XML files, they are not meant to be user-editable. Think of them as binary files. You wouldn&#8217;t merge binary files now, would you?<\/li>\n<li>It&#8217;s hard to do: there are many nooks and crannies where localizable strings are found in IB (tooltips?), you&#8217;re bound to forget some of them in some language.<\/li>\n<li>It needs a developer: you would not hand off a nib file to a translator and expect him to know all of the above: there is some training involved. And even if you have the Fear of God when editing nib files (because you&#8217;ve been burnt before by a disconnected IBOutlet), chances are your translator has no such qualms.<\/li>\n<\/ul>\n<h3>Translators want strings files, not nib files<\/h3>\n<p>\nYou could use <a href=\"http:\/\/wilshipley.com\/blog\/2009\/10\/pimp-my-code-part-17-lost-in.html\">Wil Shipley&#8217;s approach of translating everything at run-time<\/a>. That approach has one great advantage: everything is driven by strings files, which only contain strings (duh!). Your translator(s) will do a great job with them, and you can easily diff the results in your own version control system when the translations come back.<\/p>\n<p>\nThere are, however, drawbacks to this approach. Since everything happens at run-time:<\/p>\n<ul>\n<li>You have to ship and execute more code than necessary (this is a minor point, but still valid for the iPhone since it is very resource-limited).<\/li>\n<li>You can only see the results by launching your app in the appropriate language, switching using the International Settings panel, which is tedious.<\/li>\n<\/ul>\n<h3>A compile-time approach<\/h3>\n<p>\nMy approach expands on <a href=\"http:\/\/www.bdunagan.com\/2009\/03\/15\/ibtool-localization-made-easy\/\">Brian Dunagan&#8217;s<\/a> use of <code>ibtools<\/code> and <code>genstrings<\/code>.<\/p>\n<p>\nXcode has excellent build scriptability due to <a href=\"http:\/\/developer.apple.com\/mac\/library\/documentation\/DeveloperTools\/Conceptual\/XcodeBuildSystem\/200-Build_Phases\/bs_build_phases.html#\/\/apple_ref\/doc\/uid\/TP40002690-CJABHEIB\">Run Script build phases<\/a>. In this example, we will use a script to generate a localized French xib from an English xib, using a strings file as a template for translation.<\/p>\n<p>\nFirst, create a French version of your xib file by adding a French localization to your English xib file.<br \/>\nAssuming it is called &#8220;MainWindow.xib&#8221;, select the xib file, choose File > Get Info and in the &#8220;General&#8221; pane click &#8220;Add Localization&#8221;. Type &#8220;French&#8221;.<\/p>\n<p>\nThis will create the &#8220;French.lproj&#8221; folder, which will contain a copy of the English MainWindow.xib.<\/p>\n<p>\nNext, add a Run Script phase to your application target:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"\/images\/run_script_build_phase.gif\" width=\"878\" height=\"302\" hspace=\"4\" vspace=\"8\" alt=\"Run Script build phase in Xcode\"><\/p>\n<p>Finally, enter this as your script:<\/p>\n<pre>\n# Extract English strings (use this to check if you added new strings to your nibs)\nibtool --generate-strings-file Resources\/English.lproj\/MainWindow.strings Resources\/English.lproj\/MainWindow.xib\n# Generate French interface\nibtool --strings-file Resources\/French.lproj\/MainWindow.strings --write Resources\/French.lproj\/MainWindow.xib Resources\/English.lproj\/MainWindow.xib\n<\/pre>\n<p>Repeat each pair of lines for each xib you need to localize, and adjust the names accordingly.<\/p>\n<p>\nThe first command extracts all the localizable strings from your English xib, and stores them in <code>English.lproj\/MainWindow.strings<\/code>. This is your reference file: add it to version control, but you do not need to add it to your application. You can add it to your project, but <i>make sure it is not included in your app bundle<\/i> (it is useless at runtime).<\/p>\n<p>\nThe second command takes a French-localized version of the same strings file (<code>French.lproj\/MainWindow.strings<\/code>) and, using the English xib as a template, generates the French xib.<\/p>\n<h4>Wait a moment&#8230;<\/h4>\n<p>\nIf you followed so far, build your app. The script should fail, because <code>French.lproj\/MainWindow.strings<\/code> does not exist yet. Just make a copy of <code>English.lproj\/MainWindow.strings<\/code> and put it in the French folder. Just like the English <code>MainWindow.strings<\/code>, you want to add this file to version control and your project, <i>but not to your app&#8217;s resources<\/i>.<\/p>\n<p>\nIf you build again, everything should go fine and your French <code>MainWindow.xib<\/code> should be created&#8230; in English.<\/p>\n<h4>Translation<\/h4>\n<p>\nOf course, you have to translate the French <code>MainWindow.strings<\/code>. Mine looks a bit like this:<\/p>\n<pre>\n\/* Class = \"IBUIBarButtonItem\"; title = \"Done\"; ObjectID = \"138\"; *\/\n\"138.title\" = \"Termin\u00e9\";\n\/* Class = \"IBUIBarButtonItem\"; title = \"Today\"; ObjectID = \"140\"; *\/\n\"140.title\" = \"Aujourd'hui\";\n\/* etc... *\/\n<\/pre>\n<p>You can send this file to your translator, and instruct her to only translate the words in quotes on the right-side of the equal (&#8220;=&#8221;) sign. <i>Don&#8217;t touch anything else<\/i>.<\/p>\n<h4>But&#8230; the Fear of God?<\/h4>\n<p>\nEverything else in this file is necessary for <code>ibtool<\/code> to do its job properly, and should not be touched. There are two safeguards against you (or your translator) accidentally touching this information:<\/p>\n<ol>\n<li>You have the original English file. When the file comes back from your translator, you can diff it against the original (which is in English, remember?) and see that she has followed your instructions (or not). It should be pretty easy to spot the differences with FileMerge.<\/li>\n<li>Every build, the script re-creates the English <code>MainWindow.strings<\/code> file. Your version control system should flag any differences immediately. For instance, if you added a label, you would see it in your new English file and you could apply the exact same change to your French file, and make a note to send it for translation again.<\/li>\n<\/ol>\n<p>I found that these two safeguards more than compensated for the fact that the generated strings file are really source code, since they contain essential information for <code>ibtool<\/code> to translate properly.<\/p>\n<h3>Summary<\/h3>\n<p>\nSince everything happens at compile-time, my solution has none of the drawbacks of Wil&#8217;s solution:<\/p>\n<ul>\n<li>No extra code needed.<\/li>\n<li>You can look at a generated xib file in Interface Builder and immediately see any layout issues you might have. In this case, change the English xib, rebuild and check again.<\/li>\n<\/ul>\n<p>Remember, these files should be added to version control, but not to your app bundle:<\/p>\n<ul>\n<li>English.lproj\/MainWindow.strings<\/li>\n<p><s><\/p>\n<li>English.lproj\/MainWindow.xib<\/li>\n<p><\/s><\/p>\n<li>French.lproj\/MainWindow.strings<\/li>\n<\/ul>\n<p>And this file should be added to your app bundle, but not to version control (since it is generated each time):<\/p>\n<ul>\n<li>French.lproj\/MainWindow.xib<\/li>\n<\/ul>\n<p>Now, every time you build, you will generate your French interface and it will be added to your app. Simple and efficient.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post applies to all Cocoa apps, whether on the desktop or on the iPhone. When you want to localize your application, you can take several routes. No nibs, only .strings files The first one is to not use nibs (or xibs, in the new parlance). If you build everything programmatically, +alloc and -init-ing your [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,5],"tags":[],"class_list":["post-94","post","type-post","status-publish","format-standard","hentry","category-development","category-macosx"],"_links":{"self":[{"href":"http:\/\/developer.casgrain.com\/index.php?rest_route=\/wp\/v2\/posts\/94","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/developer.casgrain.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/developer.casgrain.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/developer.casgrain.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/developer.casgrain.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=94"}],"version-history":[{"count":5,"href":"http:\/\/developer.casgrain.com\/index.php?rest_route=\/wp\/v2\/posts\/94\/revisions"}],"predecessor-version":[{"id":99,"href":"http:\/\/developer.casgrain.com\/index.php?rest_route=\/wp\/v2\/posts\/94\/revisions\/99"}],"wp:attachment":[{"href":"http:\/\/developer.casgrain.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=94"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/developer.casgrain.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=94"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/developer.casgrain.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=94"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}