Random Wok Aperture Export Plugin -- Part 4
You can download the current release of Random Wok from the downloads page.
28. Better Logging
The logging I have been doing is very primitive. I call NSLog() to see what I want to see and have to manually insert and remove these calls (or mess with comments) to control what is going on. It's time for something better. Looking around, I found a handy logging class at Borkware that does much of what I want called MLog.
It implements a logging system that includes the line number and source file name with each message. This is very useful for understanding what is happening when reading the logs. The change I made to that code was to add control over the logging level.
Here are the definitions for my version of MLog.h. I implement seven levels and add the ability to revert to standard NSLog() calls, or to remove all the logging code completely:

Macros take care of inserting the correct code and extracting the file name and line number from the preprocessor. The class implements two class methods: one for actually logging messages, and one for setting the current log level. The idea of the level control is that the minimum log level can be set either by an environment variable or by code and only messages logged at that level or above will be shown.
The Implementation includes a static variable that holds the current log level:

Initialization is done in the class initializer:

It reads the environment variable MLogMinLevel and uses that to set the initial level. The logging code compares the logging level passed to the method with the current level and ignores those below the minimum:

The level setter code is very simple:

To use the logging, I add lines like this to my code:

and it provides messages that look like this that include the level, the file name and the line number:

Since I provide logging control with an environment variable, I can quite easily create build configurations that behave differently. If I go to the Project menu and select Edit Project Settings I can duplicate the current Debug and Release configurations and make two new ones:

Clicking on on the Build tab lets me set these up:

I edit the preprocessor macros to include the symbols I need to control the logging system. The Release No Logging configuration sets __BTREMOVE-LOGGING, for instance. Once set up I can change the current configuration I want to build and run from the main XCode window just by selecting it:

For this to fully work I edit the custom scripts I added to copy the executable and run Aperture, since the build names have changed and there are more of them.
It implements a logging system that includes the line number and source file name with each message. This is very useful for understanding what is happening when reading the logs. The change I made to that code was to add control over the logging level.
Here are the definitions for my version of MLog.h. I implement seven levels and add the ability to revert to standard NSLog() calls, or to remove all the logging code completely:

Macros take care of inserting the correct code and extracting the file name and line number from the preprocessor. The class implements two class methods: one for actually logging messages, and one for setting the current log level. The idea of the level control is that the minimum log level can be set either by an environment variable or by code and only messages logged at that level or above will be shown.
The Implementation includes a static variable that holds the current log level:

Initialization is done in the class initializer:

It reads the environment variable MLogMinLevel and uses that to set the initial level. The logging code compares the logging level passed to the method with the current level and ignores those below the minimum:

The level setter code is very simple:

To use the logging, I add lines like this to my code:

and it provides messages that look like this that include the level, the file name and the line number:
Since I provide logging control with an environment variable, I can quite easily create build configurations that behave differently. If I go to the Project menu and select Edit Project Settings I can duplicate the current Debug and Release configurations and make two new ones:

Clicking on on the Build tab lets me set these up:

I edit the preprocessor macros to include the symbols I need to control the logging system. The Release No Logging configuration sets __BTREMOVE-LOGGING, for instance. Once set up I can change the current configuration I want to build and run from the main XCode window just by selecting it:

For this to fully work I edit the custom scripts I added to copy the executable and run Aperture, since the build names have changed and there are more of them.
29. Implementing A Table Of Images
At this point in the project I decided two things: that I was going to release the plugin as 1.0 in a month, and that I needed to change the interface. I had already switched from a vertical arrangement with the prefix on top, to a horizontal arrangement with the prefix on the left. The trigger for this next change was the unsatisfactory display of the example random filename and the need to distinguish between what happens to the random naming when the freeze feature is used and is not.
The final version I decided to go with features a table that shows a thumbnail, plus the image version name, the image caption, and the new random name. This is the clearest way to show what is going to happen: show it happening.

By using the propertiesWithoutThumbnailForImageAtIndex: and thumbnailForImageAtIndex: methods I can get the data I need from Aperture for each table entry. So my plan was to bind the table to an array controller and bind the array controller to an array in the Random_Wok object that contained all the thumbnails and text. Updating the thumbnails and text would cause changes to propagate through the controller to the view as needed.
So I added an array controller to the nib and called it ImageTable:

I then bound it to the model like this:

and set up like this:

The model key is imageTableData and I use the keys thumb and text to access the data for the columns. So to support the needs of the array controller I implement an array called imageTableData as an ivar in Random_Wok and fill it with dictionaries with keys thumb and text, corresponding to NSImage and NSString objects I want to display.
I set up the table view like this:

Notice that I actually used a custom class for this. More on that later. The first (image) column is set up like this:

To display the image, I drag in an image cell. The properties of the image cell is accessed via the small triangle top right:

The inspector shows it as an NSImageCell, set up this way:

Now onto the bindings. The first column is bound to the array controller and uses the thumb key to get data from the model:

The second column is set up this way:

And bound to the text key through the array controller like this:

As I discovered while attempting to bind each table column to a separate array, setting up table column bindings also automatically sets up the the table bindings. This makes it impossible to bind separate columns to separate array objects: they have to all go to one object and then use the key path to get data from separate places. This pretty much means that you need an array of dictionaries to drive a table.
To provide data for the model, I loaded all the thumbnails and text into the array during the plugin initialization. Through the bindings I was able to change the dictionary contents and have the table update automatically. This all worked fine for a small selection of images.
However if I selected 500 images, the array would take a long time to fill. Worse, this was happening before anything was displayed (since the array controller was set to prepare content), making it look like the plugin has frozen. And changes to the text caused by changing the parameters for the random file names were also very slow because they too would be performed 500 times on all the items in the array.
So another approach was needed.
The final version I decided to go with features a table that shows a thumbnail, plus the image version name, the image caption, and the new random name. This is the clearest way to show what is going to happen: show it happening.

By using the propertiesWithoutThumbnailForImageAtIndex: and thumbnailForImageAtIndex: methods I can get the data I need from Aperture for each table entry. So my plan was to bind the table to an array controller and bind the array controller to an array in the Random_Wok object that contained all the thumbnails and text. Updating the thumbnails and text would cause changes to propagate through the controller to the view as needed.
So I added an array controller to the nib and called it ImageTable:

I then bound it to the model like this:

and set up like this:

The model key is imageTableData and I use the keys thumb and text to access the data for the columns. So to support the needs of the array controller I implement an array called imageTableData as an ivar in Random_Wok and fill it with dictionaries with keys thumb and text, corresponding to NSImage and NSString objects I want to display.
I set up the table view like this:

Notice that I actually used a custom class for this. More on that later. The first (image) column is set up like this:

To display the image, I drag in an image cell. The properties of the image cell is accessed via the small triangle top right:

The inspector shows it as an NSImageCell, set up this way:

Now onto the bindings. The first column is bound to the array controller and uses the thumb key to get data from the model:

The second column is set up this way:

And bound to the text key through the array controller like this:

As I discovered while attempting to bind each table column to a separate array, setting up table column bindings also automatically sets up the the table bindings. This makes it impossible to bind separate columns to separate array objects: they have to all go to one object and then use the key path to get data from separate places. This pretty much means that you need an array of dictionaries to drive a table.
To provide data for the model, I loaded all the thumbnails and text into the array during the plugin initialization. Through the bindings I was able to change the dictionary contents and have the table update automatically. This all worked fine for a small selection of images.
However if I selected 500 images, the array would take a long time to fill. Worse, this was happening before anything was displayed (since the array controller was set to prepare content), making it look like the plugin has frozen. And changes to the text caused by changing the parameters for the random file names were also very slow because they too would be performed 500 times on all the items in the array.
So another approach was needed.
30. Improving Image Table Performance
My first attempt at providing table thumbnails was to load an NSMutableArray with all the images that the user has selected when the plugin starts. Unfortunately there can be a significant delay while they are retrieved from Aperture: above a few hundred the delay becomes irritating. Once the data was in my array, access was fast and the table scrolled up and down smoothly.
To improve start-up performance I replaced my array ivar imageTable with two methods: -countOfImageTableData and -objectInImageTableDataAtIndex:. Now, through the magic of KVC the array controller instantiated in the nib will access these two methods instead of the array. In fact it was trying to do that all along, failing, and falling back to accessing the ivar directly. The idea is that by generating the data on demand, the start-up performance problem will go away and the time taken distributed across all the images as they are viewed in the table.
Here is the code for the first method:

The check for the API version exists because my code simply does not support it. There used to be a less efficient way to retrieve thumbnails that I have no intention of coding. The result of this is that the image table is blank if an old API is detected. I also log a message to that effect on initialization, so it is not a complete mystery to the user.
The second method is very simple:

The code for -textForImageAtIndex: and -thumbnailForImageAtIndex: is not simple, however. But it is straight forward. The text code gets the properties of the image from Aperture and formats it appropriately. The thumbnail code is much easier to follow:

Master images (and potentially any image) don't have thumbnails, so I have to substitute. I do that with a small TIFF image that I put into the plugin bundle that says "No Image".
How did I do? Start-up was fast, even for thousands of images, so that problem was fixed. But there were other problems. Scrolling was dog slow. Logging showed that -countOfImageTableData and -objectInImageTableDataAtIndex: were being called for practically every pixel of scrolling I was doing, retrieving the image and generating the text each time. Horrible.
So the next step was to implement caching: retaining the images and text in memory and delivering those instead of getting them from Aperture each time they are needed.
To improve start-up performance I replaced my array ivar imageTable with two methods: -countOfImageTableData and -objectInImageTableDataAtIndex:. Now, through the magic of KVC the array controller instantiated in the nib will access these two methods instead of the array. In fact it was trying to do that all along, failing, and falling back to accessing the ivar directly. The idea is that by generating the data on demand, the start-up performance problem will go away and the time taken distributed across all the images as they are viewed in the table.
Here is the code for the first method:

The check for the API version exists because my code simply does not support it. There used to be a less efficient way to retrieve thumbnails that I have no intention of coding. The result of this is that the image table is blank if an old API is detected. I also log a message to that effect on initialization, so it is not a complete mystery to the user.
The second method is very simple:

The code for -textForImageAtIndex: and -thumbnailForImageAtIndex: is not simple, however. But it is straight forward. The text code gets the properties of the image from Aperture and formats it appropriately. The thumbnail code is much easier to follow:

Master images (and potentially any image) don't have thumbnails, so I have to substitute. I do that with a small TIFF image that I put into the plugin bundle that says "No Image".
How did I do? Start-up was fast, even for thousands of images, so that problem was fixed. But there were other problems. Scrolling was dog slow. Logging showed that -countOfImageTableData and -objectInImageTableDataAtIndex: were being called for practically every pixel of scrolling I was doing, retrieving the image and generating the text each time. Horrible.
So the next step was to implement caching: retaining the images and text in memory and delivering those instead of getting them from Aperture each time they are needed.
31. Caching Table Data
Retrieving the thumbnail image and generating the text for the table is time-consuming and called very often by the array controller that is controlling the table of images. This results in very slow scrolling of my table. To solve this problem, I added caching; the idea being that repeated requests for the same data come out of memory and do so quickly.
Adding caching was relatively simple, at least to implement simply. Here is the code:

The cache needs a key to cache on. For this I turn the index number into a string and use that as the key. The cache itself is a mutable dictionary that holds dictionaries:

This stores the data I get from Aperture fine and the interface is fast again. But now I have another problem. When the random file name parameters are changed, the cached data becomes stale. So each time a change occurs, I must update the cached text:

And the cached images:

This code enumerates through the cached data replacing the cached text and thumbnail objects. Bindings take care of the table updates. -updateCachedFilenames is called whenever anything changes that could affect the file name, such as a change of alpha case:

-updateCachedImages is called when the type of image changes:

This all works well, and is how Random Wok 1.0 was released. But it still has a problem. The cache grows forever, eating memory as it goes. And as the cache grows, the time taken to update the cached data grows. What is really needed is a cache that throws away the oldest entry once it reaches a certain limit and more data is added. So that is what I implemented next.
Adding caching was relatively simple, at least to implement simply. Here is the code:

The cache needs a key to cache on. For this I turn the index number into a string and use that as the key. The cache itself is a mutable dictionary that holds dictionaries:
This stores the data I get from Aperture fine and the interface is fast again. But now I have another problem. When the random file name parameters are changed, the cached data becomes stale. So each time a change occurs, I must update the cached text:

And the cached images:

This code enumerates through the cached data replacing the cached text and thumbnail objects. Bindings take care of the table updates. -updateCachedFilenames is called whenever anything changes that could affect the file name, such as a change of alpha case:

-updateCachedImages is called when the type of image changes:

This all works well, and is how Random Wok 1.0 was released. But it still has a problem. The cache grows forever, eating memory as it goes. And as the cache grows, the time taken to update the cached data grows. What is really needed is a cache that throws away the oldest entry once it reaches a certain limit and more data is added. So that is what I implemented next.
32. Problems With Arrays and Key Presses
A problem that I encountered along the way as I implemented my cache was that any change to the data always caused the array controller to load all the elements. This was exactly what I was trying to avoid with a cache, yet it was happening.
After much hair-pulling (and posting to Apple's Cocoa mailing list) I figured that the array controller believed that my array (implemented by methods in my Random_Wok class) was immutable, and therefore any observed change must mean that the entire array had changed and so need a reload. The fix was to make the array controller believe that the array was mutable. To do this I added three more methods:

These are the mutable array methods. I didn't even have to write any code for them because they are never called. They are just there so that the array controller knows that my array is mutable and so will allow updates to individual elements.
Another problem I found that was while the Page Up and Page Down keys worked on the NSTableView, the Home and End keys did not. A little odd. To fix this I subclassed NSTableView and overrode -keyDown:.
I created a custom class called BTKeydownTableView and told Interface Builder to use it instead of NSTableView. Here is the interface:

The implementation is very simple. I read the first character from the event queue and act on it:

To scroll the window to the right place I tell the view to scroll to the first or last rows, as required.
After much hair-pulling (and posting to Apple's Cocoa mailing list) I figured that the array controller believed that my array (implemented by methods in my Random_Wok class) was immutable, and therefore any observed change must mean that the entire array had changed and so need a reload. The fix was to make the array controller believe that the array was mutable. To do this I added three more methods:

These are the mutable array methods. I didn't even have to write any code for them because they are never called. They are just there so that the array controller knows that my array is mutable and so will allow updates to individual elements.
Another problem I found that was while the Page Up and Page Down keys worked on the NSTableView, the Home and End keys did not. A little odd. To fix this I subclassed NSTableView and overrode -keyDown:.
I created a custom class called BTKeydownTableView and told Interface Builder to use it instead of NSTableView. Here is the interface:

The implementation is very simple. I read the first character from the event queue and act on it:

To scroll the window to the right place I tell the view to scroll to the first or last rows, as required.
33. Preparing For Localization
Random Wok 1.0 is currently not localized: the only language it supports is English. Before I release the 1.1 version I am going to add translations for the languages that Aperture supports: French, German, and Japanese. This requires extra files in the plugin's bundle that provide the language information.
To prepare, I replace all the messages and strings that are generated by the program and are human-readable with macros that retrieve the localized version. Code like this:

is replaced by code like this:

I use the NSLocalizedStringWithDefaultValue macro because it allows me to provide a key (exporting-images in this case) that is not the same as the string Exporting Images.... It also supports use of a specific bundle. I need that because Random Wok is a plugin and otherwise Aperture's main bundle would be used.
Once I have replaced all the strings in my code with macros, I use the terminal to run the genstrings utility on all the source files in my project:

I get this entry in the text file Localizable.strings created by genstrings for the exporting-images string above:

To provide for other languages the Localizable.strings file is duplicated and the string on the right replaced by the translation. When the plugin is run, the correct language files in the bundle are accessed and the key used in the code (exporting-images) matched with the entry in the file. Since the file is encoded UTF-16, the right hand string can contain ASCII and any unicode characters.
There are two other sources of strings that I need to worry about for localization in my plugin. First, the nib file contains all the strings used in the interface and it is currently English-only. To fix that I will need a new nib file for each language and will probably have to adjust the placement of some interface elements due to the size of the new strings. Second, I have an image that is displayed when there is no thumbnail available that says "No Image". That will have to be replaced with a new image for each language.
Apple provides a tool for helping with the translation of nibs called nibtool. Nibtool used with the -L option extracts all the strings from a nib file and sends them to stdout. The Random Wok nib file generates entries like these:

As before, a translation replaces the right hand string. Nibtool is used again to replace the strings in copies of the nib file with the translated versions.
To prepare, I replace all the messages and strings that are generated by the program and are human-readable with macros that retrieve the localized version. Code like this:

is replaced by code like this:

I use the NSLocalizedStringWithDefaultValue macro because it allows me to provide a key (exporting-images in this case) that is not the same as the string Exporting Images.... It also supports use of a specific bundle. I need that because Random Wok is a plugin and otherwise Aperture's main bundle would be used.
Once I have replaced all the strings in my code with macros, I use the terminal to run the genstrings utility on all the source files in my project:

I get this entry in the text file Localizable.strings created by genstrings for the exporting-images string above:
To provide for other languages the Localizable.strings file is duplicated and the string on the right replaced by the translation. When the plugin is run, the correct language files in the bundle are accessed and the key used in the code (exporting-images) matched with the entry in the file. Since the file is encoded UTF-16, the right hand string can contain ASCII and any unicode characters.
There are two other sources of strings that I need to worry about for localization in my plugin. First, the nib file contains all the strings used in the interface and it is currently English-only. To fix that I will need a new nib file for each language and will probably have to adjust the placement of some interface elements due to the size of the new strings. Second, I have an image that is displayed when there is no thumbnail available that says "No Image". That will have to be replaced with a new image for each language.
Apple provides a tool for helping with the translation of nibs called nibtool. Nibtool used with the -L option extracts all the strings from a nib file and sends them to stdout. The Random Wok nib file generates entries like these:

As before, a translation replaces the right hand string. Nibtool is used again to replace the strings in copies of the nib file with the translated versions.
34. Instructions To Localizers
When I sent the materials to my localizers I included a lot of information and some detailed instructions. The idea was to preempt any questions, delays, and mistakes. I put all the localizable files into a folder and ZIPped it for sending. Included in that package were the localized string files from Aperture itself. In that way the translators could see how Apple had expressed the terminology and maintain consistency.
The first item in the instructions was a series of screen shots of the plug-in. Several were needed to show the basic interface plus the pop-up menus popped up. The screen shots give the strings context and make it possible for localizers to work with the plug-in even if they cannot run it.
Here are the instructions I provided:
Enclosures
Enclosed is a ZIP file with a Localizable.strings file that was created by the genstrings utility. It contains all the strings that are localized in the application code. Also included is a file.strings file that has all the strings from the nib (user interface) and a TIFF image.
There is a folder for each language enclosed that contains a copy of the strings file used by Aperture. You can use this as a reference to see how Apple has translated things like Version Name. This will help consistency.
What To Do
You will need to use an editor that can handle UTF-16 encoding. TextWrangler can do this as can many others. Apple recommends this simple editor:
ftp://ftp.apple.com/developer/Tool_Chest/Localization_Tools/ADViewer_2.1.dmg
Edit the text in the strings files to make the second string the translation. For example if I were doing a British English translation:
/* NSMenuItem : (oid:278) */
"Period" = " Period";
becomes:
/* NSMenuItem : (oid:278) */
"Period" = " Full stop";
Do not alter the comments or the other strings in the files. Be aware of the semicolon at the end of each line.
The Localizable.strings file is a slightly different format with the comment and the first string telling you what the string is used for:
/* Continue renaming after failure */
"rename-error-continue" = "Continue";
This helps distinguish some subtle differences in meaning or tense that may not be conveyed by English.
The strings may include substitutions. These are identified by a leading %. For example:
"%@ のコピー %d";
The substitutions should be left alone and just the text translated. Notice that some strings have numbers prefixing the substituted arguments. The numbers define the order of the substitutions in the first string:
/* Message in alert dialog when something fails */
"%@ Error! %@ failed!" = "%2$@ blah blim, %1$@ bloo!";
The translated message above reverses the arguments, as may be needed in some languages.
You can enter characters directly into the second string and can use Unicode by prefixing with \U. For example \U0020 is a unicode space.
In addition translations are needed for:
"No Image"
This appears in the table (as a graphic) when no thumbnail image is available for display. I will create a graphic for each language unless you want to do that. I have enclosed the graphic I currently use.
Please do translate "Random Wok". If you can!
One part of the interface has a plural that is used if more than one image is present: "3 images selected", but "1 image selected". Please provide both the singular and plural translations for this phrase. I handle the pluralizing in code, and depending on the language will have to change the way I do this.
What To Deliver
The files you deliver (Localizable.strings, file.strings, and a file containing the translation for No Image and the two forms of Images Selected) must be encoded UTF-16. Please ZIP the files together so they don't get mangled by email systems.
For technical background information see Apple's documentation:
http://developer.apple.com/documentation/MacOSX/Conceptual/BPInternational/index.html
Specifically this page:
http://developer.apple.com/documentation/MacOSX/Conceptual/BPInternational/Articles/
NotesForLocalizers.html#//apple_ref/doc/uid/20000044
What Happens Next
I will take your strings files and put them into the project. For those that live in the nib, I duplicate the nib file and then will adjust the layout so that things fit. It is likely, English being such a terse language, that things will have to move somewhat. That is OK, but finding translations of similar size would be most helpful.
I will send you a beta of the plugin when it is ready so you can check that is still OK. Your comments on the beta as well as what I have done with your strings will be welcome.
There were actually some problems. I had mistakenly left a macro in my source files that contained strings that were nothing to do with my plug-in, so that was translated unnecessarily. Despite my efforts with the screen shots, not all of the meaning was clear and I had to answer some questions about some words. One of the ZIP files came back empty for unknown reasons. I also realized that the way I have implemented the pluralization of the number images selected -- providing a method to add the pluralising string "s" -- is unlikely to work in all languages. So I will need to change that.
The name itself, Random Wok turns out to be hard to translate. A Wok is literally a Chinese Pot in Japanese. So I opted for the more phonetic Randamu Wokku ランダム・ウオック. And the phrase No Image is seven characters that I am supposed to keep on one line inside a small button. So I had to request a different meaning for that.
The first item in the instructions was a series of screen shots of the plug-in. Several were needed to show the basic interface plus the pop-up menus popped up. The screen shots give the strings context and make it possible for localizers to work with the plug-in even if they cannot run it.
Here are the instructions I provided:
Enclosures
Enclosed is a ZIP file with a Localizable.strings file that was created by the genstrings utility. It contains all the strings that are localized in the application code. Also included is a file.strings file that has all the strings from the nib (user interface) and a TIFF image.
There is a folder for each language enclosed that contains a copy of the strings file used by Aperture. You can use this as a reference to see how Apple has translated things like Version Name. This will help consistency.
What To Do
You will need to use an editor that can handle UTF-16 encoding. TextWrangler can do this as can many others. Apple recommends this simple editor:
ftp://ftp.apple.com/developer/Tool_Chest/Localization_Tools/ADViewer_2.1.dmg
Edit the text in the strings files to make the second string the translation. For example if I were doing a British English translation:
/* NSMenuItem :
"Period" = "
becomes:
/* NSMenuItem :
"Period" = "
Do not alter the comments or the other strings in the files. Be aware of the semicolon at the end of each line.
The Localizable.strings file is a slightly different format with the comment and the first string telling you what the string is used for:
/* Continue renaming after failure */
"rename-error-continue" = "Continue";
This helps distinguish some subtle differences in meaning or tense that may not be conveyed by English.
The strings may include substitutions. These are identified by a leading %. For example:
"%@ のコピー %d";
The substitutions should be left alone and just the text translated. Notice that some strings have numbers prefixing the substituted arguments. The numbers define the order of the substitutions in the first string:
/* Message in alert dialog when something fails */
"%@ Error! %@ failed!" = "%2$@ blah blim, %1$@ bloo!";
The translated message above reverses the arguments, as may be needed in some languages.
You can enter characters directly into the second string and can use Unicode by prefixing with \U. For example \U0020 is a unicode space.
In addition translations are needed for:
"No Image"
This appears in the table (as a graphic) when no thumbnail image is available for display. I will create a graphic for each language unless you want to do that. I have enclosed the graphic I currently use.
Please do translate "Random Wok". If you can!
One part of the interface has a plural that is used if more than one image is present: "3 images selected", but "1 image selected". Please provide both the singular and plural translations for this phrase. I handle the pluralizing in code, and depending on the language will have to change the way I do this.
What To Deliver
The files you deliver (Localizable.strings, file.strings, and a file containing the translation for No Image and the two forms of Images Selected) must be encoded UTF-16. Please ZIP the files together so they don't get mangled by email systems.
For technical background information see Apple's documentation:
http://developer.apple.com/documentation/MacOSX/Conceptual/BPInternational/index.html
Specifically this page:
http://developer.apple.com/documentation/MacOSX/Conceptual/BPInternational/Articles/
NotesForLocalizers.html#//apple_ref/doc/uid/20000044
What Happens Next
I will take your strings files and put them into the project. For those that live in the nib, I duplicate the nib file and then will adjust the layout so that things fit. It is likely, English being such a terse language, that things will have to move somewhat. That is OK, but finding translations of similar size would be most helpful.
I will send you a beta of the plugin when it is ready so you can check that is still OK. Your comments on the beta as well as what I have done with your strings will be welcome.
There were actually some problems. I had mistakenly left a macro in my source files that contained strings that were nothing to do with my plug-in, so that was translated unnecessarily. Despite my efforts with the screen shots, not all of the meaning was clear and I had to answer some questions about some words. One of the ZIP files came back empty for unknown reasons. I also realized that the way I have implemented the pluralization of the number images selected -- providing a method to add the pluralising string "s" -- is unlikely to work in all languages. So I will need to change that.
The name itself, Random Wok turns out to be hard to translate. A Wok is literally a Chinese Pot in Japanese. So I opted for the more phonetic Randamu Wokku ランダム・ウオック. And the phrase No Image is seven characters that I am supposed to keep on one line inside a small button. So I had to request a different meaning for that.
35. Integrating Localized Data Part 1
Now my translators have sent back localized versions of Localizable.strings, file.strings, and images that I sent to them, I can integrate them into my project. Here is how my project is organized right now:

To localize the NoImage.tiff image, I select NoImage.tiff and get Info, then click on Make File Localizable:

This changes the image into a group and shows the targets that it is associated with:

The Resources have been rearranged like this:

Clicking on the General tab shows the languages that the image is localized for:

I'm going to add French, so I click Add Localization and select French. The French image created by Xcode is just a copy of the English image at this stage:

To get my French image in, I change its name from PasDimage.tiff to NoImage.tiff so that the code will be able to access it with the same file name and replace the current image in the French.lprog folder via the Finder. Xcode has a handy contextual menu item called Reveal File In Finder to help with this.
To Localize the strings I do the same sequence, this time putting my English Localizable.strings into the English localization as well as the French Localizable.strings file into the French localization.
I localize the nib file too, creating the localization, but just leaving it as a duplicate of the English for now. I want to see how Random Wok works in French with what I have to far. Only a few things will be French at this stage: the progress message and the missing thumbnail image will show me that things are working correctly. But how to run in French?
I go to the International preference pane and move French to the top:

Now when I run Aperture it will be French.
But when I do, I find that the NoImage image is not there. And when I run it in English it is not there either. The problem is this code:

I get the TIFF file using a path that does not take into account the localization folders (English, French). So the initWithContentsOfFile method fails and returns nil. The fix is to use -pathForResource
fType:inDirectory with a nil directory name:

And now the image shows up correctly when I run in French or English:

Next is fixing the interface strings.

To localize the NoImage.tiff image, I select NoImage.tiff and get Info, then click on Make File Localizable:

This changes the image into a group and shows the targets that it is associated with:

The Resources have been rearranged like this:

Clicking on the General tab shows the languages that the image is localized for:

I'm going to add French, so I click Add Localization and select French. The French image created by Xcode is just a copy of the English image at this stage:

To get my French image in, I change its name from PasDimage.tiff to NoImage.tiff so that the code will be able to access it with the same file name and replace the current image in the French.lprog folder via the Finder. Xcode has a handy contextual menu item called Reveal File In Finder to help with this.
To Localize the strings I do the same sequence, this time putting my English Localizable.strings into the English localization as well as the French Localizable.strings file into the French localization.
I localize the nib file too, creating the localization, but just leaving it as a duplicate of the English for now. I want to see how Random Wok works in French with what I have to far. Only a few things will be French at this stage: the progress message and the missing thumbnail image will show me that things are working correctly. But how to run in French?
I go to the International preference pane and move French to the top:

Now when I run Aperture it will be French.
But when I do, I find that the NoImage image is not there. And when I run it in English it is not there either. The problem is this code:

I get the TIFF file using a path that does not take into account the localization folders (English, French). So the initWithContentsOfFile method fails and returns nil. The fix is to use -pathForResource

And now the image shows up correctly when I run in French or English:

Next is fixing the interface strings.
36. Integrating Localized Data Part 2
Now the strings in my code and my image are localized into French as well and English, I can move on to the strings in the interface. So far the French nib is just a copy of The English nib, created when I made the French nib localization. I used nibtool to extract the strings before translation, and I use nibtool again to put the translated strings back.
To do this I fire up Terminal, cd to the French.lproj folder and use the following command line:
nibtool -w new.nib -d file.strings Random_Wok.nib
This creates a new nib file with the English strings replaced by French ones. I use the Finder to replace the old nib with the new one and I am done. Now my resources look like this:

But if I run the plug-in in French, some of the strings no longer fit:

This is unfortunately typical for English. With its huge vocabulary, English can take up as little as 50% of the space of other languages. So my nice tight interface needs adjusting.
And there is a problem with subversion. After checking all of this in I find that my repository does not contain the French files and the NoImage.tiff file is in both the localized and the main folder:

To fix this I do two subversion things: svn delete the extra TIFF and svn add the folder and its contents. Svnx could do the delete, but not the add. After a fair amount of trying things that did not consistently work, I eventually went to the command line, did a svn add of the French.lproj folder, then quit and relaunched Xcode, then finally did a commit. That worked and now everything is synchronized.
I fix the layout with IB, but don't neaten it up yet. That's because I want to run it past my translator again to make sure nothing weird has happened that I won't spot. Once the translator has OKed it, I'll peek the pixels and straighten everything.
And then there is the "Images Selected" binding. I have this set up with two display patterns, one for the number of images and one for the pluralization string. For French I have to put the second string in twice since both words gain an "s" in the plural:

When I come to add German, this will break. The German strings are "Bild ausgewählt" and "Bilder ausgewählt". There is an "er" added in the plural, not an "s". Japanese is easy: no plurals exist in the language. A better solution is to put both singular and plural strings in the strings file and do all of this in code.
To do this I fire up Terminal, cd to the French.lproj folder and use the following command line:
nibtool -w new.nib -d file.strings Random_Wok.nib
This creates a new nib file with the English strings replaced by French ones. I use the Finder to replace the old nib with the new one and I am done. Now my resources look like this:

But if I run the plug-in in French, some of the strings no longer fit:

This is unfortunately typical for English. With its huge vocabulary, English can take up as little as 50% of the space of other languages. So my nice tight interface needs adjusting.
And there is a problem with subversion. After checking all of this in I find that my repository does not contain the French files and the NoImage.tiff file is in both the localized and the main folder:

To fix this I do two subversion things: svn delete the extra TIFF and svn add the folder and its contents. Svnx could do the delete, but not the add. After a fair amount of trying things that did not consistently work, I eventually went to the command line, did a svn add of the French.lproj folder, then quit and relaunched Xcode, then finally did a commit. That worked and now everything is synchronized.
I fix the layout with IB, but don't neaten it up yet. That's because I want to run it past my translator again to make sure nothing weird has happened that I won't spot. Once the translator has OKed it, I'll peek the pixels and straighten everything.
And then there is the "Images Selected" binding. I have this set up with two display patterns, one for the number of images and one for the pluralization string. For French I have to put the second string in twice since both words gain an "s" in the plural:

When I come to add German, this will break. The German strings are "Bild ausgewählt" and "Bilder ausgewählt". There is an "er" added in the plural, not an "s". Japanese is easy: no plurals exist in the language. A better solution is to put both singular and plural strings in the strings file and do all of this in code.
The Bagelturf site welcomes Donations of any size