Tuesday, October 20

Pimp My Code, Part 17: Lost in Translations.

Introduction

If you’re developing commercial software, you’re going to want to sell it globally — for well-localized English-speaking software companies, for instance, I have seen between 20% and 25% of total revenue coming from non-English speaking countries. That’s, like, real money. You’ll want that.
Delicious Monster
Gross Sales by
Language of Country
English80.9%
German8.1%
French3.2%
Japanese2.6%
Unknown / Other2.2%
Dutch1.5%
Spanish0.9%
Norwegian0.8%
Italian0.5%
Danish0.5%
Swedish0.5%
Chinese0.3%
Portuguese0.3%
[Delicious Monster Oct09]
Omni Group
Upgrades by Language
English74.4%
German6.0%
French5.3%
Japanese5.0%
Spanish2.3%
Other2.3%
Italian1.9%
Dutch1.4%
Chinese0.7%
Portuguese0.4%
Norwegian0.4%
[Omni Group Oct09]

Note that the data from the above tables was gathered in different ways: Omni tracks what language a user has set in her preferences every time she upgrades any of her Omni software, whereas I tracked Delicious Monster’s total dollar sales by country, and then assigned each country a “primary” language. (For countries like Switzerland, obviously my approach is an approximation.)

Still, you can try to make some educated guesses from these two data sets, although obviously correlations aren’t necessarily causative when analyzing only two samples. But, for example, Omni has Japanese-language support and Japanese-only boxed versions of their products; their Japanese number is twice that of Delicious Monster, which doesn’t have a partnership like this (yet). On the flip side, I have a Norwegian translation in Delicious Library 2, whereas OmniGraffle does not; my Norwegian number is twice that of Omni’s.

My German number is a bit higher, which is surprising because in general Delicious Library doesn’t appear to have penetrated overseas as well as Omni has. Maybe Germans love to organize books? Maybe they love doodads, and they bought my optional Bluetooth scanner, which makes their gross sales number much higher? There’s clearly a whole post to be written about mining this data, but this isn’t that post.

Abstract

In this post I’m going to explain to you what internationalization and localization are, how Apple’s tools handle them by default, and the huge flaws in Apple’s approach. Then I’m going to provide you with the code and tools to do localization in a much, much easier way.

Then you’re going to think, “That will never work, because of blah!” and I’m going to respond, as if I can read your mind or I’ve already had this argument with a dozen developers, “It already did — I used these tools in Delicious Library and Delicious Library 2 and they've won three Apple Design Awards between them.”

And you’re going to think, “Stupid show-off… why does he keep mentioning those?” And I’m going to say, “Look, in this case I think it’s justified, because the Design Awards are given based largely on the interface of an application, and I’m merely trying to demonstrate that the compromises I had to make in interface design didn’t have a huge impact on the feel of…” and then we’ll go back and forth for a while in an imaginary argument while I try to convince you to do things my way, dammit, because I’m the daddy.

“Internationalization” vs. “Localization”

What is internationalization, and what is localization? Good question! I’m so glad you asked.

First, you should really read Apple’s documentation on it. I mean, seriously, this should ALWAYS be your first target of inquiry. Did you read it yet? How about now? Now?

Ok, fine… briefly, ‘internationalization’ is getting your app ready to be localized for different languages/countries, and ‘localization’ is the process of actually adding a particular language to your app. The two are obviously are very closely related (you can't localize until you internationalize, and there's no point in internationalizing if you're not going to actually do any localizations) but it's vital to understand they are two distinct steps.

For instance, as a developer, you don't need to speak German to get a German localization — you just need to do the internationalization part and find one friendly deutschophone to do the localization, and you're golden. (I mean literally golden, as in covered in gold — don't ignore the German market! It's 8% of my total sales!)

Apple's Localization Process

Ok, you’re sold on localization. But how do you proceed? Another good question! (Again, thank you for tossing me all these softballs.)

First off (as I’ve said many-a-time), when you are writing your source code you should surround EVERY string that the user might eventually see in the NSLocalizedString() macro (or a variant). This will enable you to later automatically pull the English phrases out of your app, for translation by people who actually, like, speak other languages. Errrr… whatchacallums. “Polyglots!”

But then what? Apple’s recommended process is:
  • You run Apple’s command-line tool, ‘genstrings’, over your source files (hopefully as part of your build process in Xcode), to pull out all the English phrases you marked with NSLocalizedString() and its cousins. See, I told you that’d be useful! And here’s it’s already paid off, only a couple paragraphs later!
  • You give the “.strings” files that genstrings generates, along with any XIBs and image files that contain English words, to a localizer, who is a native speaker of the language you want.
  • The localizer creates a new directory of translated “.strings” files, plus XIBs and images with English words translated as well.
  • You take this directory, and put in into a language bundle in your project, like “ja.lproj”.
Easy? Sort of. But you can already see problems:

Problem 1: Localizers Can’t Effectively Localize Images

You really really don’t want your localizers editing your image files. I mean, your artists spent a lot of time making your images really nice (since you’re not an idiot and you didn’t try to do your artwork yourself) and they used things like layers and paths and filters and stuff, and your localizers don’t know how to use Photoshop and aren’t artists, and furthermore you probably gave your localizers the final PNG or JPEG2000 files instead of PSDs and so they don’t have all those layers and paths and filters and they’d do a really crappy job if they even could do it which they can’t.

Also, your artists are going to keep tweaking the images right up until the day you ship (and after — and then they’re going to beg you to delay) and you could spend 100% of your time just sending out updated images to localizers for re-translation, and your localizers are either going to charge you a fortune or get pissed off and quit (depending on if you pay them or not, respectively).

Luckily, there’s a simple and obvious solution to this one: DO NOT EVER PUT WORDS IN YOUR IMAGES.

buyalbum.png
buybook.png
buysong.png
this is wrong, wrong, so wrong.

Yes, I KNOW Apple does it. I think it’s some bizarre hold-over from their Carbon days, when drawing actual styled text on a 3D button using actual source code was CRAZY TALK. Plus Apple has a huge in-house art staff, so it’s easy for engineers to say, “Hey, art dudes, give me a button in three different states that says, ‘Buy Album’ in our latest iTunes-only-style-that’s-not-quite-like-Cocoa’s-standards,” and the engineer is done for the day and didn’t have to write any code and can go suck down a mojito.

Never mind that every time Apple’s (separate-but-equal) iTunes UI team changes the look of their buttons, the artists have to redo several hundred images and then re-localize them, resulting in thousands of fiddly images. That’s certainly a lot more efficient than the programmer spending a couple hours writing code to draw the button border and inside and the shadow on the text, once, and then just modifying that code in one place whenever the look changes, right? RIGHT? (* see answer at end of post.)

Luckily, you don’t even have the option to do it this stupid way, because you don’t have an art department whose time you’d like to waste. So, use icons whenever possible on buttons, and if you must use text, then make it localizable. Luckily, this is pretty easy in Cocoa, since the button edges, insides, and the various shines and shadows are all done for you if you use the standard mechanisms, and they’re easy to subclass, once, if you want a custom look.

For button titles that require text, you can either type English words directly into Interface Builder, or if you’re not on an iPhone you can bind the button’s title some method in your code and use NSLocalizedString() to make sure it’s localized. Since you’re going to have to localize most of your XIBs anyhow (because of menu items, mouseover help text, explanatory text boxes, field titles, window names, etc.), I really only recommend the binding approach if your button’s title changes in a way you can’t model in Interface Builder itself. (That is, don’t write an extra method if you don’t have to. Less code is better.)

Problem 2: Localizers Can’t Effectively Localize XIBs

Asking your localizers to modify XIBs directly (using Interface Builder) is a huge pain: First, you limit your pool of potential localizers if they have to have the developer tools installed AND understand how to use them.

inspectorforlocalization.png
hope the localizer finds this "display pattern!"

Second, XIB files aren’t flat or self-documenting, so it’s VERY hard for localizers to find EVERY last English word in your XIB. Did they forget a tooltip? What about an alternate title for a button? What about a button’s title binding’s formatter string? Maybe there’s a hidden view somewhere? You’ll have to keep testing the localizations you get for complete coverage, and sending them back to be redone, and then re-testing them again. And again. Yuck.

Sure, there’s a cool command in Interface Builder now, where you can hit control-S and see all the strings in your interface — but that’s ANOTHER thing you have to teach each localizer to do. In addition to knowing how to use Interface Builder in the first place.

Third, XIBs are like source code: they are written by programmers and contain functional parts. If your localizers happen to delete a button, or disconnect a binding, your program stops working for that language. Remember, your localizers are NOT coders — they don’t have the same innate fear of changing XIBs that you’ve learned from years of boning yourself. And how fun is it to debug a program that works differently in different languages? Not fun.

Fourth, many language are not as compact as English: the French and Germans are particularly fond of using the descriptions with the lots of the words or compoundwordstodescribeasingleconcept, respectively. You’re asking your localizers to resize your buttons and titles, and then, if those don’t fit in their containers, resize your views and widgets and panels as well.

Let me restate that last point: you are asking your localizers to design your interface. If you’re making an application that catalogs users’ books, CDs, DVDs, and other physical media, then I urge you to go ahead and do this. For the rest of you, WHAT THE HELL ARE YOU THINKING?

Problem 3: Localizers Can’t Localize from Only Your App

In the old days, the NIBs we shipped were the same as the NIBs we used to build the app (and to localize), so any polyglot user in the world could just make a new language folder in our app wrapper by copying our English resources, and then start localizing the NIBs and .strings files, and at any time she could launch our app and check her progress. (Which is good, because it’s AMAZINGLY common for localizers to screw up the punctuation in .strings files, and Cocoa’s localization machinery will just silently ignore all strings after any punctuation mistake, resulting in mystery partial localizations.)

Sure, the user had to understand Interface Builder, but at least if she did, she had everything she needed.

Nowadays, Apple doesn’t want people mucking about CHANGING their programs, so we compile XIBs down into read-only NIBs, which means we have to give localizers our original XIB files (as well as our .strings) before they can start work.

Think about the difference between some user, somewhere, just deciding to start editing a folder full of files that she already has, versus her having to write you, you having to bundle up all your XIBs and .strings and send them to her, her having to edit them all without seeing ANY results while she is doing it, then bundle them up and send them back to you, then you have to integrate them into your build system and make a test release, and either look at it yourself or send it to her again.

Lather, rinse, repeat, lather, stab, stab, stab.

Even if this weren’t a horrible, time-wasting process (it is), this points to the final and biggest problem with localization:

Problem 4: You Have to Maintain Multiple Copies of Each Localized Resource

If you have nine translations and each has a localized copy of every XIB in your application, then you’ll have to manually maintain nine different versions of each XIB. The strings in XIBs don’t change super-often, but the connections, flags, layout, and other object properties change all the time. And every time you change a flag or layout or connection in one XIB, you have to do the same thing eight more times. And do it perfectly, or you’ll have a language-specific bug.

Of course, you can wait until all your XIBs are fully finished before you localize, but, if you’re like me, you are still fiddling with your XIBs until the day you ship your 1.0 code. So you’d have wait to localize until AFTER shipping 1.0, which stinks, or put off shipping until you’ve finished the product, sent out the localizations, AND gotten them all back — which stinks. And then you find have to change some buggy XIBs for your version 1.2, and you’d still have the exact same problem of having to edit nine XIBs to make one change.

(This is obviously also true of images that have been localized, but hopefully I’ve talked you out of EVER doing that, so I’m not going to discuss that further.)

Now, Apple has provided some tools in an attempt to make this all easier — they have something called “ibtool” (“nibtool” before Leopard) which can pull all the strings from a file, and then put new ones in their place, supposedly. In fact, it’s intended to allow you to look at the geometry and language changes between two localized versions of the same XIB, so you can generate, say, a Japanese version of your Main Menu nib with the latest changes from your English Main Menu nib, except keeping changes that you specifically made in the geometry of objects in the German version (since some words are longer and/or shorter in other languages, and thus XIBs sometimes require relayout).

Apple also has a tool called AppleGlot which sets up an entire “translation environment.” It apparently dates from Carbon days, and although it’s been updated to work with some versions of Interface Builder last time I tried it, it was a lot more trouble than value to me.

Finally, there are some third-party tools that attempt to simplify some of this: for example Polyglot, but it appears to have not been updated in a while, and I’m honestly not sure what all it does. (I’m providing the link so you can research it and other products if you want.)

I disagree with the entire approach of storing a bunch of geometry diffs to your XIBs — this seems INCREDIBLY fragile to me. What happens when you move a text field from one view to another? What happens if you even move views around? All those geometry changes turn to gooblity-gook.

The fundamental problem with every tool I’ve seen to fix your XIBs, however,is that you still have to maintain all the localized versions. You have nine extra copies of each XIB in your source code, you have to make sure they are all synced up, somehow.

Now, if I told you, “Look, it’s easy to localize your Objective-C files — just maintain ten copies of each! Every time you change one, you’ll have to change all the others… but don’t worry! I’ve provided some tools that kind of help with this… until they don’t…” What would you think?

You’d spit in my eye. And I don’t like spitty eyes. So, I came up with a better way, with exactly the same solution Apple used for Objective-C files.

An Almost Ideal Solution

Let’s design, in our heads, the perfect localization system, or as close as we can get without, you know, having to do a lot of work:

• First, we simply promise ourselves we won’t put words in image files. There, that solves a lot. This is usually Apple’s convention as well, although even certain Cocoa teams (-cough-iWeb-cough-) have used fully-rendered images of words like “PUBLISH” instead of using strings — hey, THAT’S not going to be resolution-independent, is it? (I keeed! I keeeeeeed!)

• For XIBS, what we’d like (as programmers) is to have and maintain ONE single English XIB, which will polymorph to the user’s chosen language at launch time, so we don’t have to ship with ten versions of the same XIB (they’re surprisingly big) or maintain ten versions of the same XIB in our source code (with the concomitant increase in errors as our XIB localizations slowly drift out of sync).

• If we (the programmers) change a string or the whole look of a XIB, we want partial localizations to still work — it’s fine with with us if sometimes we ship with one or two English words “peeking through” a localization, since in most countries (obviously not France) they mix in English words all the time anyhow. It’s certainly better to have a partial localization (as long as it is of high-quality) than either no localization or a corrupted one. (This goes against a programmer’s natural inclination to insist that solutions be provably perfect. Get used to it.)

• To make our localizers’ jobs easier, what we’d like to do is give them ONLY .strings files, so all localization takes is any text editor – localizers don’t have to know how to use Interface Builder or any developer tools.

• We’re forgetful, so we want to generate the strings files from our XIBs and source files every time we do a build, so we never give out-of-date versions to our localizers — if they have a build of the app, then they have the latest strings files, AND the means to test their localizations themselves.

• Since our app is going to ship with the complete set of .strings files needed to localize it, ANY customer who speaks another language can copy our English.lproj to TheirLanguage.lproj, edit the .strings files, and voila create a localized version of our app after a couple hours’ work.

Here's the Code

Now, as you might guess, I've already done this, and, if I may plug myself for a second, this code was already available to clients of Golden % Braeburn, which is my other company where I give my Delicious store source code (and other miscellaneous helpful code) to Mac developers for a paltry percentage of sales. (Yes, Golden % Braeburn has launched, and yes, one of Golden % Braeburn's clients, Acacia Tree Software, is currently selling its app with our store.)

The first step to slurp all the English strings out of your XIB files at build time, so your localizers have nice flat strings files to work with. Add two new build phases to your application target in Xcode using “Project ▶ New Build Phase ▶ New Run Script Build Phase”, set the shells to /bin/zsh, and have them look like this:

Internationalize Source Code Shell Script Build Phase

# -q silences duplicate comments with same key warning
genstrings -q -o ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/${DEVELOPMENT_LANGUAGE}.lproj ${SRCROOT}/**/*.[hm]



Internationalize XIBs Shell Script Build Phase

foreach nibFile (${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/**/*.nib)
stringsFilePath=${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/${DEVELOPMENT_LANGUAGE}.lproj/`basename ${nibFile} .nib`.strings
xibFile=`basename ${nibFile} .nib`.xib
xibFilePath=`echo ${SOURCE_ROOT}/**/${xibFile}`
if [[ -e ${xibFilePath} ]] {
ibtool --generate-stringsfile ${stringsFilePath}~ ${xibFilePath}
${BUILT_PRODUCTS_DIR}/xibLocalizationPostprocessor ${stringsFilePath}~ ${stringsFilePath}
rm ${stringsFilePath}~
}
end


Now comes a slight hitch — under Leopard and beyond, the “ibtool” command idiotically outputs a new format of .strings file that looks like this:

/* Class = "NSButtonCell"; title = "Get iPhone & iPod Touch App";
ObjectID = "1401603"; */
"1401603.title" = "Get iPhone & iPod Touch App";

/* Class = "NSTextFieldCell"; title = "Remote Libraries:";
ObjectID = "1401604"; */
"1401604.title" = "Remote Libraries:";


Instead of the standard file format (output by the older “nibtool” and the current “genstrings”):

/* Class = "NSButtonCell"; title = "Get iPhone & iPod Touch App";
ObjectID = "1401603"; */
"Get iPhone & iPod Touch App" = "Get iPhone & iPod Touch App";

/* Class = "NSTextFieldCell"; title = "Remote Libraries:";
ObjectID = "1401604"; */
"Remote Libraries:" = "Remote Libraries:";


This new format completely violates the whole .strings file paradigm and obviously makes it impossible for us to use this to localize XIBs on-the-fly, because when we load XIBs on our own, we don't have those ObjectIDs so we can't match objects up to the strings they need. Nice going, Apple.

So, I wrote a little program to post-process “ibtool”s output to make it like “nibtool” and “genstrings”, you should add this code to your Xcode project and add a new “tool” target for it and make sure your main app depends on this tool being built, and all will be well:
xibLocalizationPostprocessor.m: Post-processing ibtool output
// xibLocalizationPostprocessor.m
//
// Created by William Shipley on 4/14/08.
// Copyright © 2005-2009 Golden % Braeburn, LLC.

#import <Cocoa/Cocoa.h>


int main(int argc, const char *argv[])
{
NSAutoreleasePool *autoreleasePool = [[NSAutoreleasePool alloc] init]; {
if (argc != 3) {
fprintf(stderr, "Usage: %s inputfile outputfile\n", argv[0]);
exit (-1);
}

NSError *error = nil;
NSStringEncoding usedEncoding;
NSString *rawXIBStrings = [NSString stringWithContentsOfFile:[NSString stringWithUTF8String:argv[1]] usedEncoding:&usedEncoding error:&error];
if (error) {
fprintf(stderr, "Error reading %s: %s\n", argv[1], error.localizedDescription.UTF8String);
exit (-1);
}

NSMutableString *outputStrings = [NSMutableString string];
NSUInteger lineCount = 0;
NSString *lastComment = nil;
for (NSString *line in [rawXIBStrings componentsSeparatedByString:@"\n"]) {
lineCount++;

if ([line hasPrefix:@"/*"]) { // eg: /* Class = "NSMenuItem"; title = "Quit Library"; ObjectID = "136"; */
lastComment = line;
continue;

} else if (line.length == 0) {
lastComment = nil;
continue;

} else if ([line hasPrefix:@"\""] && [line hasSuffix:@"\";"]) { // eg: "136.title" = "Quit Library";

NSRange quoteEqualsQuoteRange = [line rangeOfString:@"\" = \""];
if (quoteEqualsQuoteRange.length && NSMaxRange(quoteEqualsQuoteRange) < line.length - 1) {
if (lastComment) {
[outputStrings appendString:lastComment];
[outputStrings appendString:@"\n"];
}
NSString *stringNeedingLocalization = [line substringFromIndex:NSMaxRange(quoteEqualsQuoteRange)]; // chop off leading: "blah" = "
stringNeedingLocalization = [stringNeedingLocalization substringToIndex:stringNeedingLocalization.length - 2]; // chop off trailing: ";
[outputStrings appendFormat:@"\"%@\" = \"%@\";\n\n", stringNeedingLocalization, stringNeedingLocalization];
continue;
}
}

NSLog(@"Warning: skipped garbage input line %d, contents: \"%@\"", lineCount, line);
}

if (outputStrings.length && ![outputStrings writeToFile:[NSString stringWithUTF8String:argv[2]] atomically:NO encoding:NSUTF8StringEncoding error:&error]) {
fprintf(stderr, "Error writing %s: %s\n", argv[2], error.localizedDescription.UTF8String);
exit (-1);
}
} [autoreleasePool release];
}


Finally, add this class to your main project. Whenever you load a XIB file now, this code will be invoked, and it’ll run through all the displayed strings in your XIB and see if there is a localization in the correspondingly-named strings file. (Eg, if you load “MainMenu.xib”, it’ll automatically be localized with strings from “MainMenu.strings”.)

DMLocalizedNibBundle.m: Run-time Localization of XIBs

// DMLocalizedNibBundle.m
//
// Created by William Jon Shipley on 2/13/05.
// Copyright © 2005-2009 Golden % Braeburn, LLC. All rights reserved except as below:
// This code is provided as-is, with no warranties or anything. You may use it in your projects as you wish, but you must leave this comment block (credits and copyright) intact. That's the only restriction -- Golden % Braeburn otherwise grants you a fully-paid, worldwide, transferrable license to use this code as you see fit, including but not limited to making derivative works.


#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>


@interface NSBundle (DMLocalizedNibBundle)
+ (BOOL)deliciousLocalizingLoadNibFile:(NSString *)fileName externalNameTable:(NSDictionary *)context withZone:(NSZone *)zone;
@end

@interface NSBundle ()
+ (void)_localizeStringsInObject:(id)object table:(NSString *)table;
+ (NSString *)_localizedStringForString:(NSString *)string table:(NSString *)table;
// localize particular attributes in objects
+ (void)_localizeTitleOfObject:(id)object table:(NSString *)table;
+ (void)_localizeAlternateTitleOfObject:(id)object table:(NSString *)table;
+ (void)_localizeStringValueOfObject:(id)object table:(NSString *)table;
+ (void)_localizePlaceholderStringOfObject:(id)object table:(NSString *)table;
+ (void)_localizeToolTipOfObject:(id)object table:(NSString *)table;
@end


@implementation NSBundle (DMLocalizedNibBundle)

#pragma mark NSObject

+ (void)load;
{
NSAutoreleasePool *autoreleasePool = [[NSAutoreleasePool alloc] init];
if (self == [NSBundle class]) {
method_exchangeImplementations(class_getClassMethod(self, @selector(loadNibFile:externalNameTable:withZone:)), class_getClassMethod(self, @selector(deliciousLocalizingLoadNibFile:externalNameTable:withZone:)));
}
[autoreleasePool release];
}


#pragma mark API

+ (BOOL)deliciousLocalizingLoadNibFile:(NSString *)fileName externalNameTable:(NSDictionary *)context withZone:(NSZone *)zone;
{
NSString *localizedStringsTableName = [[fileName lastPathComponent] stringByDeletingPathExtension];
NSString *localizedStringsTablePath = [[NSBundle mainBundle] pathForResource:localizedStringsTableName ofType:@"strings"];
if (localizedStringsTablePath && ![[[localizedStringsTablePath stringByDeletingLastPathComponent] lastPathComponent] isEqualToString:@"English.lproj"]) {

NSNib *nib = [[NSNib alloc] initWithContentsOfURL:[NSURL fileURLWithPath:fileName]];
NSMutableArray *topLevelObjectsArray = [context objectForKey:NSNibTopLevelObjects];
if (!topLevelObjectsArray) {
topLevelObjectsArray = [NSMutableArray array];
context = [NSMutableDictionary dictionaryWithDictionary:context];
[(NSMutableDictionary *)context setObject:topLevelObjectsArray forKey:NSNibTopLevelObjects];
}
BOOL success = [nib instantiateNibWithExternalNameTable:context];
[self _localizeStringsInObject:topLevelObjectsArray table:localizedStringsTableName];

[nib release];
return success;

} else {
return [self deliciousLocalizingLoadNibFile:fileName externalNameTable:context withZone:zone];
}
}



#pragma mark Private API

+ (void)_localizeStringsInObject:(id)object table:(NSString *)table;
{
if ([object isKindOfClass:[NSArray class]]) {
NSArray *array = object;

for (id nibItem in array)
[self _localizeStringsInObject:nibItem table:table];

} else if ([object isKindOfClass:[NSCell class]]) {
NSCell *cell = object;

if ([cell isKindOfClass:[NSActionCell class]]) {
NSActionCell *actionCell = (NSActionCell *)cell;

if ([actionCell isKindOfClass:[NSButtonCell class]]) {
NSButtonCell *buttonCell = (NSButtonCell *)actionCell;
if ([buttonCell imagePosition] != NSImageOnly) {
[self _localizeTitleOfObject:buttonCell table:table];
[self _localizeStringValueOfObject:buttonCell table:table];
[self _localizeAlternateTitleOfObject:buttonCell table:table];
}

} else if ([actionCell isKindOfClass:[NSTextFieldCell class]]) {
NSTextFieldCell *textFieldCell = (NSTextFieldCell *)actionCell;
// Following line is redundant with other code, localizes twice.
// [self _localizeTitleOfObject:textFieldCell table:table];
[self _localizeStringValueOfObject:textFieldCell table:table];
[self _localizePlaceholderStringOfObject:textFieldCell table:table];

} else if ([actionCell type] == NSTextCellType) {
[self _localizeTitleOfObject:actionCell table:table];
[self _localizeStringValueOfObject:actionCell table:table];
}
}

} else if ([object isKindOfClass:[NSMenu class]]) {
NSMenu *menu = object;
[self _localizeTitleOfObject:menu table:table];

[self _localizeStringsInObject:[menu itemArray] table:table];

} else if ([object isKindOfClass:[NSMenuItem class]]) {
NSMenuItem *menuItem = object;
[self _localizeTitleOfObject:menuItem table:table];

[self _localizeStringsInObject:[menuItem submenu] table:table];

} else if ([object isKindOfClass:[NSView class]]) {
NSView *view = object;
[self _localizeToolTipOfObject:view table:table];

if ([view isKindOfClass:[NSBox class]]) {
NSBox *box = (NSBox *)view;
[self _localizeTitleOfObject:box table:table];

} else if ([view isKindOfClass:[NSControl class]]) {
NSControl *control = (NSControl *)view;

if ([view isKindOfClass:[NSButton class]]) {
NSButton *button = (NSButton *)control;

if ([button isKindOfClass:[NSPopUpButton class]]) {
NSPopUpButton *popUpButton = (NSPopUpButton *)button;
NSMenu *menu = [popUpButton menu];

[self _localizeStringsInObject:[menu itemArray] table:table];
} else
[self _localizeStringsInObject:[button cell] table:table];


} else if ([view isKindOfClass:[NSMatrix class]]) {
NSMatrix *matrix = (NSMatrix *)control;

NSArray *cells = [matrix cells];
[self _localizeStringsInObject:cells table:table];

for (NSCell *cell in cells) {

NSString *localizedCellToolTip = [self _localizedStringForString:[matrix toolTipForCell:cell] table:table];
if (localizedCellToolTip)
[matrix setToolTip:localizedCellToolTip forCell:cell];
}

} else if ([view isKindOfClass:[NSSegmentedControl class]]) {
NSSegmentedControl *segmentedControl = (NSSegmentedControl *)control;

NSUInteger segmentIndex, segmentCount = [segmentedControl segmentCount];
for (segmentIndex = 0; segmentIndex < segmentCount; segmentIndex++) {
NSString *localizedSegmentLabel = [self _localizedStringForString:[segmentedControl labelForSegment:segmentIndex] table:table];
if (localizedSegmentLabel)
[segmentedControl setLabel:localizedSegmentLabel forSegment:segmentIndex];

[self _localizeStringsInObject:[segmentedControl menuForSegment:segmentIndex] table:table];
}

} else
[self _localizeStringsInObject:[control cell] table:table];

}

[self _localizeStringsInObject:[view subviews] table:table];

} else if ([object isKindOfClass:[NSWindow class]]) {
NSWindow *window = object;
[self _localizeTitleOfObject:window table:table];

[self _localizeStringsInObject:[window contentView] table:table];

}
}

+ (NSString *)_localizedStringForString:(NSString *)string table:(NSString *)table;
{
if (![string length])
return nil;

static NSString *defaultValue = @"I AM THE DEFAULT VALUE";
NSString *localizedString = [[NSBundle mainBundle] localizedStringForKey:string value:defaultValue table:table];
if (localizedString != defaultValue) {
return localizedString;
} else {
#ifdef BETA_BUILD
NSLog(@" not going to localize string %@", string);
return string; // [string uppercaseString]
#else
return string;
#endif
}
}


#define DM_DEFINE_LOCALIZE_BLAH_OF_OBJECT(blahName, capitalizedBlahName) \
+ (void)_localize ##capitalizedBlahName ##OfObject:(id)object table:(NSString *)table; \
{ \
NSString *localizedBlah = [self _localizedStringForString:[object blahName] table:table]; \
if (localizedBlah) \
[object set ##capitalizedBlahName:localizedBlah]; \
}

DM_DEFINE_LOCALIZE_BLAH_OF_OBJECT(title, Title)
DM_DEFINE_LOCALIZE_BLAH_OF_OBJECT(alternateTitle, AlternateTitle)
DM_DEFINE_LOCALIZE_BLAH_OF_OBJECT(stringValue, StringValue)
DM_DEFINE_LOCALIZE_BLAH_OF_OBJECT(placeholderString, PlaceholderString)
DM_DEFINE_LOCALIZE_BLAH_OF_OBJECT(toolTip, ToolTip)

@end

Unresolved Issues

Issue 1: Not all XIB properties are localized

I didn’t have any NSTableViews with standard column headers in Delicious Library, so there’s no code to handle NSTableViews at all in here. I also don’t even attempt to localize bindings strings, I believe. So, that’d be a good thing to do!

I haven’t touched this code in, like, four years. Which says a lot about how well it’s worked for me, but, obviously, there’s room for improving it. So, please pimp *my* code, and send it back to me, and I’ll post your changes. (We’ll all be better off for it!)

Issue 2: You can’t use the same phrase with two different meanings in a single XIB

Imagine if you have, say, a button labelled “Wind” for “what you do to a watch,” and a text field labelled “Wind” for “moving air.” You obviously can’t localize ‘em with this method. Hasn’t proven to be a big deal with me, but this is clearly what motivated Apple to completely hose string the file format for “ibtool.”

Issue 3: iPhone limitations

I never got around to porting this to work on the iPhone, since Amazon made me destroy the iPhone version of Delicious Library, but it’d be pretty easy to make this work with hierarchies of UIViews and UIControls instead of their NSCousins.

I don’t think the swap-methods-at-load technique will work on the iPhone, either, so you’d have to manually call the translation method when you loaded a XIB, but I don’t think that’s a huge deal, since on the iPhone you typically are MUCH more conscious of when you load and unload XIBs.

Also, obviously your customers couldn’t simply get the .strings from your app bundle or add their localizations themselves, so you lose those advantages. Still, this is the technique I’ll be using in my future iPhone and apps.

Issue 4: You can’t change the geometry of XIBs depending on the localization

The method requires that we, as programmers and interface designers, agree to make ALL our text fields and checkbuttons wide enough to allow for the wordiest of languages, since we’re localizing on-the-fly.

Everyone raises this as an objection, but, seriously, the alternatives are MUCH worse — either you maintain ten sets of geometries yourself, OR you let your localizers be your interface designers. Both are horrible.

And, seriously, you’d be surprised how little people notice when you have extra space. With checkboxes, just always stretch ‘em to the edge of the screen. With text fields — well, the user can’t tell HOW wide they are, can she? The only problem one is buttons, and I think those look better with extra space around them. (Update: my German localizer wrote me to tell me he disagreed with this — that it was a huge pain for him to find short enough German words. I apologized to him for not telling him he could request more width.)

I’ve even thought up two ways around this issue: one if obvious, which is you could just store some geometry changes based on the container type & string in a separate file, and merge those changes at run-time. But, that’d be hard to maintain, for reasons I’ve gone over a bunch by now.

The other would be cooler: have your widgets re-lay themselves out at run time based on their resizing properties if their localized titles don’t fit. For example, if button “a” has a button “b” 20 pixels to the right of it, and both buttons have the stretchy spring to their right, then it’s a fair guess that if you grow/shrink button “a”, you need to do so from its right edge, and you need to move “a” by that many pixels, as well. The other combinations of stretchy springs can be assigned values, and all you have to do is use them consistently in your XIBs and you’ll be golden. (You do have to watch for accidentally localizing Apple’s XIBs.)

But, seriously, I haven’t really done either of these yet, and no English speaker has ever said, “Hey, you have too much space around this button!”

And, importantly, I’m able to integrate all thirteen localizations of Delicious Library 2 by myself, and still have time to program and run my company and drink lots of mojitos.

Footnotes

*No, it isn't.

Labels: , ,

Sunday, May 24

Welcome to the iTunes App Store!

This document describes our process for reviewing applications for iPhones and iPods touch submitted to the iTunes App Store. We’ve avoided using legalese in this document so that you’ll actually read the whole thing. Please do so before starting to write your application, so that you won’t waste a bunch of time writing an application that we cannot publish.

We hate having to reject anyone’s application, so we want to make it clear that we have very compelling motivations behind our acceptance policies, so you can adhere to their spirit. (Oh, our lawyers just reminded us that we should mention that circumstances, times, and social mores change, and we reserve the right to change these policies at any time for any reason, but we’ll do our best to update this document to always reflect the current state of affairs.)


FIRST, you must do no harm. We cannot allow applications that will mess up users’ iPhones, or interfere with their normal operation in any way. The iPhone is a vital communications device, and any downtime on it is unacceptable to Apple and to our customers.

While viruses, malware, and apps that can be remotely exploited are obvious targets of this rule, there are more subtle implications to this. For instance, we cannot allow applications that send too much data through the cellular network; not only would we be breaking our agreements our cellular carriers (who have priced their data plans based on an estimated average data use of customers without including your application), but overloading the network would make it unusable for everyone, and thus violates our first rule.

We also do not allow emulators or other computer languages right now, because those kinds of applications are notorious for having subtle security holes, and we must take the cautious path to ensure the iPhone remains stable for our customers.

SECOND, if your application is what a reasonable person would consider offensive, we require it to carry an “M” rating on our app store, which carries with it certain restrictions on who can purchase and use your app.

We do this because, to a certain extent (and despite our best efforts), users do not fully distinguish between third-party authors like yourself and Apple; if something runs on an iPhone and it is ‘bad,’ that is (at least somewhat) considered to be Apple’s fault. Apple abhors censorship in any form, but it also recognizes that even within a single society there are different ideas about what is acceptable and unacceptable, and we would like to warn our customers (yours and Apple’s) who might be more sensitive.

We realize that any attempt to categorize anything into “offensive” and “inoffensive” is a fool’s errand, especially considering that your application’s audience will encompass thousands of cultures around the world. Thus, we will use our “best efforts” to determine if an app might be found offensive: for example, if it is overtly sexual, if it contains slurs or curse words, if it has violent themes — these are topics reasonable men disagree on, and our goal is to flag anything that might be controversial so that, for instance, parents can review it with their guidelines before letting their children see it.

RESUBMISSION of your app can be done if you believe that it has been rejected in error — please include in your resubmission a description of what you’ve changed in your app, or why you believe the original judgment was in error.

Because in the end our judgments are made by humans, and humans are variable and fickle creatures, our policy is to always let resubmissions be judged by a different person at Apple, to get another perspective.

REFUNDS are opt-in for developers in the iTunes App Store: you can set the number of days during which Apple will offer a full, no-questions-asked refund to your customers, which number will be displayed prominently in your application’s listing on the iTunes App Store and on the user’s list of installed apps inside iTunes.

While the number is up to you, we generally recommend that the more your application costs, the longer a refund period you offer; if you are making a $20 application and your customers are “done with it” after two days, you may not be offering a good value for money spent. As well, customers will be inclined to trust software that offers a generous refund policy.

Please note that Apple’s checks to you will be delayed by the length of the refund period you offer; we will not act as your creditor to cover refunds.


FINALLY, note that these rules actually exist only in the fevered and every-hopeful imagination of one Wil Shipley. Consult the real iTunes App Store for its actual policies, not a blog. Dur.

Labels: , , ,

Monday, September 22

iPhone App Store: Let the Market Decide

Call me a proponent of free markets, but I think Apple needs to have a clearly-documented policy for approving submissions to the iPhone App Store, and it should be:

Publish all software submitted to Apple, as long as the software isn't actively harmful to users, illegal, and does not violate Apple's agreements with cell phone vendors.

Period.

--

The iPhone app store is, at heart, incredible. Every software developer's dream store looks a lot like this:
  • 100% of the devices that can run my software have a link directly to this store.
  • And only this store.
  • And the user can't remove the link to this store from the device.
  • There's no other way to buy software, so users are never confused as to whether they should go to some website or physical store or the online store to find software for their devices.
  • Users never wonder if there's some other, better software out there - if it's not on the store, it doesn't exist.
  • Users can buy with a click.
  • Software is instantly installed and enabled for users.
  • "Good enough" copy-protection is handled invisibly for all developers.
  • Apple hosts the software makes the pretty, professional website.
  • Credit card transactions are handled automatically.
  • Apple's percentage is MUCH lower than traditional distribution.
  • The store actually pays out the money it owes you, unlike the vast majority of physical distributors.
  • There's already a market of something around ten million users for iPhone apps.
  • The market is increasing by the day.

That's a LOT of plusses. A LOT. And it's working. Developers are reporting making thousands to hundreds of thousands of dollars every MONTH and the store is only a couple months old.

Some of this is likely because of the novelty of the device and the store itself — there's a mini-gold rush effect happening, and already I suspect that if you weren't one of the guys to get rich selling a flashlight app or sudoku, well, you probably shouldn't start writing one now.

So, yay Apple, and yay developers who are rolling in fat, filthy lucre. (I'm not bitter that I didn't get in on that first round. No, no.)

As with any pioneering effort that succeeds (c.f. Twitter's constant whale-fails when it took off), Apple is encountering problems it never anticipated, and having to make up solutions on-the-fly.

Which is fine, and good, except, well... maybe we developers need to give Apple a loving nudge, so the problems are solved in a conscious way that helps everyone, instead of being solved ad-hoc and turning into policies which punish us all.

--

Problem: Most Software is Crap

There's a LOT of crap out there for the iPhone. A LOT. And a bunch of neat apps. How does the the user tell them apart? Should Apple's model be like Nintendo, where Apple only allows software through that meets their rigorous standards for being fun and cool and stable? It sounds nice, except (a) it requires a ton of effort on Apple's part, and Apple's success or failure is determined entirely by the tastes of the people doing the vetting, and (b) it stifles innovation. (Look at the number of titles available for the Nintendo Wii, which has been out for years, vs. the number for the iPhone, whose SDK has been available for months.)

The other problem with Apple vetting apps for quality is that Apple gets blamed if crappy apps slip through the process. Once you appoint yourself censor, you've taken responsibility. If an App Store app crashes, it'll be blamed on Apple. If an App Store app has a bug, it'll be blamed on Apple. If an... well, you see where I'm going.

--

Recently Apple decided to go ice-skating on the slippery slope of censorship by removing the "I am Rich" application from its store. Briefly: some prankster priced an app at $999 that did nothing but show some text and a picture, congratulating the purchaser for being rich and stupid. Apple pulled the app after a few days, citing "not enough functionality" or some such.

Now, this application did point real problems in the system, but not in the app. The problems are in the App Store, and they are: it's not really clear how to get refunds, and it's a little TOO easy to click on something that says "$999" without realizing that, seriously, this is a grand you're blowing.

Let's solve the real problems so that we don't need to censor apps, and so that developers don't need to guess if their apps are "functional" enough to pass muster with whichever App Store censor they happen to get:

• Apple needs to have a clearly posted refund policy that applies across-the-board. They may already have a policy, but, honestly, I've bought 15 or so apps and I've never seen it, and I'm going to say that if users don't see the policy, you might as well not have it.

I'd suggest something like, "You can get a full refund any time in the first two weeks of ownership of any app." This would solve many problems: if the app turns out to be buggy, or have limited functionality, or insult your mom, or whatever... well, it's not Apple's problem any more. They refund your money and everyone's happy.

• For apps over some threshold ($30? $100?), Apple needs to add a click to the purchase process. Something like, "Note: this is A HUNDRED REAL LIVE SMACKERS, here, so MAKE SURE you really want this, OK?"

--

After that first rejection, there have been two more reports of rejections. I can't verify them myself, of course, but I also have no reason to doubt the reports. One of these applications had 'podcasting' as part of its functionality, and one had fetching mail from Google as part of its functionality.

Both were censored because of a new criterion Apple has invented, which is "duplicates existing functionality." Let me make my position on this perfectly clear: it in unethical and antithetical to the whole IDEA of an App Store for Apple to be censoring applications based on criteria they have never given to developers, and only told developers after the developers put in all the work of writing an app.

Even TV network censors produce a "standards and practices" document, so writers can tell if they are pushing the envelope. Apple's censors have acted capriciously and against the interests of all of its developers, its customers, and itself.

This situation is worsened because it's obvious that Apple is only worried about applications duplicating the functionality of Apple's iPhone applications — there are twenty "sudoku" apps and a dozen "flashlights" and a bunch of pokers and, heck, there's more than one racing game.

But it was only when a developer added functionality that Apple considered sacrosanct to Apple itself that she was censored. Apple wasn't worried about customer confusion, Apple was worried about getting some competition.

I have to be clear: it simply will not stand for Apple to prevent applications on the iPhone from competing with Apple's own applications. Besides chasing away all decent developers, besides hurting their customers by stifling competition and innovation, besides it simply being evil, it will, shortly, be illegal. This kind of behavior is illegal when you hit a certain point in market saturation for your product; Microsoft was slapped for it constantly in the late '80s. If the iPhone is the success Apple thinks it will be, they will find themselves the target of a huge class-action lawsuit.

--

I can see how the iPhone App Store could be some short-sighted Apple marketing dude's dream: "Hey, we can nip all competing applications in the bud and completely own any market we choose! Imagine how well Final Cut Pro would do without Premiere! Imagine iPhoto without Lightroom! We own it all, baby!"

Those of us who actually write software know that, in fact, killing your competition is a sword that's not just double-edged, but in fact has a blade as its handle, as well. Without competition there is no innovation. Apple needs competing apps. As they add features or speed or UI innovations, Apple can copy them and make Apple's apps better.

Competition is how nature has made strong organisms since literally the beginning of time. You simply won't get stronger if you don't have adversity. It is demonstrated in any system you can think of, from virus resistance in operating systems to the relative strength of the huns versus the northern Chinese.

There's a simple proof of why competing apps should exist: (1) If customers use the third-party app, it clearly provides some functionality Apple's version does not, and customers benefit and the platform is stronger. (2) If customer do not use the third-party app, that app withers and dies and nobody is hurt.

But, ignoring how Premiere actually helps Final Cut, let's imagine a world in which Apple DID censor Premiere and Lightroom for "duplication [Apple's] existing functionality." What do you think Adobe would do with Photoshop? Flash? InDesign?

If you voted, "Make those suckers Windows-only," give yourself a gold star. Now think about how not having those applications would have affected where the Mac market is today. (Remember the lag in selling Intel machines until Adobe made Photoshop "Universal?" Imagine if it didn't run at all.)

Now imagine the next revolutionary application for phones, and what platform it's going to be on if Apple doesn't cut this crap out. (Hint: rhymes with "manbloid.")

--

"What about all the crap-ware? Aren't decent applications getting buried be all the stuff that's just being dumped out there in hopes of a few pity clicks?"

This is actually surprisingly easy to solve. Eventually, there are going to be tens and tens of thousands of apps on the App Store. Just simply paging randomly through applications to find one is already far too onerous to be practical.

The App Store needs to think of itself as two different parts - it already implements these parts, but the people who run the store need to understand that these two parts are fundamentally separate:

• Part one is a giant warehouse, where every piece of software that is not actively harmful is kept in case someone wants to buy it (remember, users can always get a refund). This warehouse can be searched with titles and keywords or an item can be directly linked.

• Part two is like a traditional storefront, with limited real estate, so only the best or coolest applications are highlighted. It's a recommendation engine, that highlights popular, highly-rated, or innovative applications.

Everyone can get into the warehouse. Only the select few can get into the storefront.

Customers win because they can choose whatever software they like, regardless of whether Apple "approves" of their choice or not. Apple wins because developers aren't alienated and don't all go develop for Android, and so Apple has the device where all the innovation is happening. And developers win because the obviously cool apps will be featured by Apple and get tons of his, but even if their app isn't "blessed" by Apple, if it's a neat enough idea it'll become popular on its own, through word-of-mouth.

--

It's a huge mistake for Apple to appoint themselves arbitrator of what's cool, or to even appear to do so. It's an equally huge mistake for Apple to decide that all innovation must come from Apple.

Let's list a handful of cool Apple apps: Safari. iTunes. Preview. Mail. iSync.

Did Apple invent the ideas or protocols behind any of these? Nope. Did Apple write the first implementations? Nope. Did Apple even write the original code they are using for their versions? Nope. (They licensed them all from third parties, except for Mail.)

When the next cool app comes out, and the next one after that, is it going to be on the iPhone, or on Android? It's really Apple's call.

--

UPDATE 9/23: Apple's response is reportedly to put the rejection letters under nondisclosure, as well. That's, uh, not a solution, guys. In fact, it's the opposite. It makes you look more draconian. And it's a useless gesture.

Do you REALLY think developers are not going to talk among ourselves, or leak info to the press, after we've worked for months on an application and then had it capriciously rejected by Apple? All the press has to say is, "we've heard of several developers who have been rejected" and there's nothing you can do; you can't subpoena people who aren't under NDA, and you won't know who among your NDA'd rejectees talked.

Seriously, Android is open and free. The tighter you try to clench your fists, the more developers you are going to drive away. Yes, you have the nicest frameworks and the prettiest hardware. But that's only the first part of what you need.

Labels: ,

Tuesday, July 29

“The Mojave Experiment:” Bad Science, Bad Marketing

I guess I should first admit I hate the show Punk’d. I mean, here’s a guy who is famous for lying about his age so he seems hipper, telling us that his show’s purpose it to deflate the big egos on other stars, and show them what truly matters in life. So he sets up situations where anyone would get upset, and then laughs when he upsets people. I call *cough*bullshit*cough*. (Also *cough*jerkface*cough*.)

So I have to admit I’m not predisposed to like The Mojave Experiment, where Microsoft took a bunch of “regular folks” XP users who were afraid of Vista, and told them Microsoft was going to show them a secret new operating system — which was actually Vista.

UNSURPRISINGLY, these people mostly said they liked Vista.

Now, if you read this blog, you know I pretty much hate Microsoft, because of their incredibly shady business practices (moreso in the early 1990s) and their shoddy products, most especially their operating systems, whose crappy user experience and programmer interfaces hold back the advance of technology. However, I’m not going to rail on Vista here. Seriously, I’m not.

What I am going to rail on is this “experiment.” (I use that word advisedly.)

--

I hate bad science. Hate it. Hate. So let’s look at not one, not two, but FOUR, yes FOUR (ah-ah-ah!) key flaws in this experiment, any single one of which would render its results meaningless:

The Placebo Effect: Every time I do a software release, no matter how minor, even if I just change one word, in French, to another French word, someone will send me mail or post on a forum, “Thanks, this release seems a lot faster!” Do I make fun of them? Or videotape them and put it on a blog? No. Because it’s just human nature. If we are told something is new-and-improved, we prime ourselves to believe it (c.f. Blink by Malcolm Gladwell, which I’ll refer to again in a bit) and make it so in our minds.

This is why we have, for example, blind taste tests: because humans are proven to not be able make dispassionate judgments about subjects they already know about. So, if you say to someone, “Hey, I’m giving you a top-secret peek at a new operating system from Microsoft, you’re incredibly lucky and special, and I really value your opinion!” of COURSE they are going to like it. They almost can’t not like it.

The Pepsi Challenge Effect: “The Pepsi Challenge” was a blind taste test that Pepsi overwhelmingly won (again, from Blink). Yet, most people still drink Coke. Why? Gladwell’s thesis is that a single sip of a soft drink is very different from drinking a whole can, which is the smallest unit most people imbibe. Pepsi usually wins the challenge because it's a sweeter drink, and initially people respond to this extra sweetness. But after drinking a can, Pepsi becomes cloying.

So, here I am, sat down in front of Mojave-err-Vista, and all I've ever used is XP. Well, look, nobody is doubting the graphics are prettier in Vista. It looks nice compare to XP (it should — they hired the guy who designed Aqua for Mac OS X).

I play with Mojave, and, yes, some system tasks are easier. Again, nobody doubts there are things that work much better. When I plug my iSight camera into Vista it shows up as a device and offers to let me take pictures in the Vista Explorer thingy. That’s kind of cool! Hey, I kind of like Mojave-nee-Vista!

Except, those glossy features aren’t why people downgrade from Vista to XP. Those are not the reason people hate on Vista!

Now, again, look — I don’t use Vista or XP for anything but games. I liked using Vista better, until the new UFO (X-Com) game that I had played great on XP, and wouldn’t launch at all on Vista. Then I bailed. That’s my story. There are apparently hundreds of others.

You, personally, may never have encountered a piece of hardware or an app that didn’t work on Vista, and you might be perfectly happy with it. I’m not going to try to argue you out of that happiness. My point is that the problems that Vista has become famous for are not the kinds of problems you encounter in a few minutes of playing with it in a controlled environment.

Vista is known for people initially liking it, then after a while discovering it’s not working for them, and “downgrading” to XP. This study has told us exactly what we already knew: that, initially, people like Vista. (Initially, people like having sex without condoms, too... it’s simply not a very good criterion all by itself.)

The Perfectly Controlled Environment Effect: Microsoft set up the hardware. Microsoft brought the accessories. Microsoft picked the software. Microsoft sat people down with Vista experts driving the mouse, and walked people through Vista. What an INCREDIBLE SHOCKER that in this INCREDIBLY TIGHTLY CONTROLLED ENVIRONMENT Vista performed OK!

Microsoft had set up an environment with a philosophy similar to Apple’s: “Look, we work well with this hardware and software, and too bad if you want something different.” Unfortunately, that’s NOT why people choose Windows. They hack together their own machines, and they want their software to still run.

Did any of these customers bring in their favorite games and try to play them? Did they bring in their graphics tablets and discover they fail?

Did any of the test machines ever say, “Oh, I’m sorry, Windows Genuine Advantage has determined that you may be running an invalid copy of Windows, so please jump through these hoops or we’ll disable some of your hardware”? I’m going to guess no. But I’ve seen this message a lot. And I own three valid licenses to Windows.

The Personal Tutor Effect: If you sit anyone down with an expert in a particular program, and the expert walks them through the features and answers their every question, chances are good that person is going to report that she had a good experience with the program. Very good, indeed.

Personal training is so important to customer experience that Apple thinks of it as a key asset of its Apple Stores. But Microsoft doesn’t have Apple Stores in real life. Or any analog. It’s you and a box with a holographic sticker on it. Good luck!

--

Microsoft has managed to prove that if you have a friendly expert on a controlled machine (with Vista pre-installed) showing a carefully selected subset of Vista features to an ignorant XP user for a few minutes, the XP user will often say he finds Vista acceptable. Wow.

This so-called experiment of Microsoft’s is an insult to science, and to our intelligence. And I am dying to see the out-takes from their shoot. I mean, how many people do you suppose like being told, “Hey, this giant, unpopular monopolistic software company just made an ass out of you! Ha ha! Our leading scienticians just PROVED that you LOVE VISTA and WANT TO MARRY IT. You are TOTALLY GAY for Vista! Haaaaaaa HAAAAAAA!”

Vista may or may not be an upgrade in user experience for most Windows customers. I personally prefer the feel of Vista over XP when the former works as well as the latter, but Vista has failed me on several occasions, and I also don’t enjoy running games MORE slowly than XP.

I've got to imagine that the Microsoft customers who took all the damn time to upgrade their machines to Vista, determined it was unworkable, and then had to take all the time to go BACK to XP, probably did so for a reason, possibly even a valid reason, and not because they had been swayed by bad word-of-mouth. I further imagine that these customers are completely livid at having Microsoft not say, “Oh, sorry, we’ll get right on those bugs,” but, instead, “You’re just stupidly following the crowd, and if you’d just free your mind up, you’ll discover you actually love Vista... hater.”

Is “Our Customers Are Stupid and Have No Idea What They Really Want” really Microsoft’s new mantra?

Again, wow.

Labels: , ,