blob: 07f11e4766b8f8f8545933c8148a661cf3a2d27e [file] [log] [blame]
//
// GTMUILocalizer.m
//
// Copyright 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
//
#import "GTMDefines.h"
#import "GTMUILocalizer.h"
@interface GTMUILocalizer (GTMUILocalizerPrivate)
- (void)localizeAccessibility:(id)object;
- (void)localizeBindings:(id)object;
// Never recursively call any of these methods. Always call
// -[self localizeObject:recursively:] otherwise bindings will not be
// localized properly.
- (void)localizeWindow:(NSWindow *)window recursively:(BOOL)recursive;
- (void)localizeToolbar:(NSToolbar *)toolbar;
- (void)localizeView:(NSView *)view recursively:(BOOL)recursive;
- (void)localizeMenu:(NSMenu *)menu recursively:(BOOL)recursive;
- (void)localizeCell:(NSCell *)cell recursively:(BOOL)recursive;
@end
@implementation GTMUILocalizer
- (id)initWithBundle:(NSBundle *)bundle {
if ((self = [super init])) {
bundle_ = [bundle retain];
}
return self;
}
- (void)dealloc {
[bundle_ release];
[super dealloc];
}
- (void)awakeFromNib {
if (owner_) {
NSBundle *newBundle = [[self class] bundleForOwner:owner_];
bundle_ = [newBundle retain];
[self localizeObject:owner_ recursively:YES];
[self localizeObject:otherObjectToLocalize_ recursively:YES];
[self localizeObject:yetAnotherObjectToLocalize_ recursively:YES];
} else {
_GTMDevLog(@"Expected an owner_ set for %@", self);
}
// Won't need these again, clear them.
owner_ = nil;
otherObjectToLocalize_ = nil;
yetAnotherObjectToLocalize_ = nil;
}
+ (NSBundle *)bundleForOwner:(id)owner {
NSBundle *newBundle = nil;
if (owner) {
Class class = [NSWindowController class];
if ([owner isKindOfClass:class] && ![owner isMemberOfClass:class]) {
newBundle = [NSBundle bundleForClass:[owner class]];
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
} else if ([owner isKindOfClass:[NSViewController class]]) {
newBundle = [(NSViewController *)owner nibBundle];
#endif
}
if (!newBundle) {
newBundle = [NSBundle mainBundle];
}
}
return newBundle;
}
- (NSString *)localizedStringForString:(NSString *)string {
NSString *localized = nil;
if (bundle_ && [string hasPrefix:@"^"]) {
NSString *notFoundValue = @"__GTM_NOT_FOUND__";
NSString *key = [string substringFromIndex:1];
localized = [bundle_ localizedStringForKey:key
value:notFoundValue
table:nil];
if ([localized isEqualToString:notFoundValue]) {
localized = nil;
}
}
return localized;
}
- (void)localizeObject:(id)object recursively:(BOOL)recursive {
if (object) {
if ([object isKindOfClass:[NSWindowController class]]) {
NSWindow *window = [object window];
[self localizeWindow:window recursively:recursive];
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
} else if ([object isKindOfClass:[NSViewController class]]) {
NSView *view = [object view];
[self localizeView:view recursively:recursive];
#endif
} else if ([object isKindOfClass:[NSMenu class]]) {
[self localizeMenu:(NSMenu *)object recursively:recursive];
} else if ([object isKindOfClass:[NSWindow class]]) {
[self localizeWindow:(NSWindow *)object recursively:recursive];
} else if ([object isKindOfClass:[NSView class]]) {
[self localizeView:(NSView *)object recursively:recursive];
} else if ([object isKindOfClass:[NSApplication class]]) {
// Do the main menu
NSMenu *menu = [object mainMenu];
[self localizeMenu:menu recursively:recursive];
} else if ([object isKindOfClass:[NSCell class]]) {
[self localizeCell:(NSCell *)object recursively:recursive];
} else if ([object isKindOfClass:[NSToolbar class]]) {
[self localizeToolbar:(NSToolbar*)object];
}
[self localizeBindings:object];
}
}
- (void)localizeWindow:(NSWindow *)window recursively:(BOOL)recursive {
NSString *title = [window title];
NSString *localizedTitle = [self localizedStringForString:title];
if (localizedTitle) {
[window setTitle:localizedTitle];
}
if (recursive) {
[self localizeObject:[window contentView] recursively:recursive];
[self localizeObject:[window toolbar] recursively:recursive];
}
}
- (void)localizeToolbar:(NSToolbar *)toolbar {
// NOTE: Like the header says, -items only gives us what is in the toolbar
// which is usually the default items, if the toolbar supports customization
// there is no way to fetch those possible items to tweak their contents.
NSToolbarItem *item;
for (item in [toolbar items]) {
NSString *label = [item label];
if (label) {
label = [self localizedStringForString:label];
if (label) {
[item setLabel:label];
}
}
NSString *paletteLabel = [item paletteLabel];
if (paletteLabel) {
paletteLabel = [self localizedStringForString:paletteLabel];
if (paletteLabel) {
[item setPaletteLabel:paletteLabel];
}
}
NSString *toolTip = [item toolTip];
if (toolTip) {
toolTip = [self localizedStringForString:toolTip];
if (toolTip) {
[item setToolTip:toolTip];
}
}
}
}
- (void)localizeView:(NSView *)view recursively:(BOOL)recursive {
if (view) {
// First do tooltips
NSString *toolTip = [view toolTip];
if (toolTip) {
NSString *localizedToolTip = [self localizedStringForString:toolTip];
if (localizedToolTip) {
[view setToolTip:localizedToolTip];
}
}
// Then do accessibility both on views directly...
[self localizeAccessibility:view];
// ...and on control cells (which implement accessibility for the controls
// that contain them)
if ([view isKindOfClass:[NSControl class]]) {
[self localizeAccessibility:[(NSControl *)view cell]];
}
// Must do the menu before the titles, or else this will screw up
// popup menus on us.
[self localizeObject:[view menu] recursively:recursive];
if (recursive) {
NSArray *subviews = [view subviews];
NSView *subview = nil;
for (subview in subviews) {
[self localizeObject:subview recursively:recursive];
}
}
// Then do titles
if ([view isKindOfClass:[NSTextField class]]) {
NSString *title = [(NSTextField *)view stringValue];
NSString *localizedTitle = [self localizedStringForString:title];
if (localizedTitle) {
[(NSTextField *)view setStringValue:localizedTitle];
}
} else if ([view respondsToSelector:@selector(title)]
&& [view respondsToSelector:@selector(setTitle:)]) {
NSString *title = [view performSelector:@selector(title)];
if (title) {
NSString *localizedTitle = [self localizedStringForString:title];
if (localizedTitle) {
[view performSelector:@selector(setTitle:) withObject:localizedTitle];
}
}
if ([view respondsToSelector:@selector(alternateTitle)]
&& [view respondsToSelector:@selector(setAlternateTitle:)]) {
title = [view performSelector:@selector(alternateTitle)];
if (title) {
NSString *localizedTitle = [self localizedStringForString:title];
if (localizedTitle) {
[view performSelector:@selector(setAlternateTitle:)
withObject:localizedTitle];
}
}
}
} else if ([view respondsToSelector:@selector(tabViewItems)]) {
NSArray *items = [view performSelector:@selector(tabViewItems)];
NSEnumerator *itemEnum = [items objectEnumerator];
NSTabViewItem *item = nil;
while ((item = [itemEnum nextObject])) {
NSString *label = [item label];
NSString *localizedLabel = [self localizedStringForString:label];
if (localizedLabel) {
[item setLabel:localizedLabel];
}
if (recursive) {
[self localizeObject:[item view] recursively:recursive];
}
}
}
}
// Do NSTextField placeholders
if ([view isKindOfClass:[NSTextField class]]) {
NSString *placeholder = [[(NSTextField *)view cell] placeholderString];
NSString *localizedPlaceholer = [self localizedStringForString:placeholder];
if (localizedPlaceholer) {
[[(NSTextField *)view cell] setPlaceholderString:localizedPlaceholer];
}
}
// Do any NSMatrix placeholders
if ([view isKindOfClass:[NSMatrix class]]) {
NSMatrix *matrix = (NSMatrix *)view;
// Process the prototype
id cell = [matrix prototype];
[self localizeObject:cell recursively:recursive];
// Process the cells
for (cell in [matrix cells]) {
[self localizeObject:cell recursively:recursive];
// The tooltip isn't on a cell, so we do it via the matrix.
NSString *toolTip = [matrix toolTipForCell:cell];
NSString *localizedToolTip = [self localizedStringForString:toolTip];
if (localizedToolTip) {
[matrix setToolTip:localizedToolTip forCell:cell];
}
}
}
// Do NSTableView column headers.
if ([view isKindOfClass:[NSTableView class]]) {
NSTableView *tableView = (NSTableView *)view;
NSArray *columns = [tableView tableColumns];
NSTableColumn *column = nil;
for (column in columns) {
[self localizeObject:[column headerCell] recursively:recursive];
}
}
// Do NSSegmentedControl segments.
if ([view isKindOfClass:[NSSegmentedControl class]]) {
NSSegmentedControl *segmentedControl = (NSSegmentedControl *)view;
for (NSInteger i = 0; i < [segmentedControl segmentCount]; ++i) {
NSString *label = [segmentedControl labelForSegment:i];
NSString *localizedLabel = [self localizedStringForString:label];
if (localizedLabel) {
[segmentedControl setLabel:localizedLabel forSegment:i];
}
}
}
// Do NSComboBox items.
if ([view isKindOfClass:[NSComboBox class]]) {
NSComboBox *combobox = (NSComboBox*)view;
// Make sure it doesn't use a DataSource.
if (![combobox usesDataSource]) {
NSMutableArray *localizedValues = [NSMutableArray array];
BOOL replaceValues = NO;
NSString *value;
for (value in [combobox objectValues]) {
NSString *localizedValue = nil;
if ([value isKindOfClass:[NSString class]]) {
localizedValue = [self localizedStringForString:value];
}
if (localizedValue) {
replaceValues = YES;
[localizedValues addObject:localizedValue];
} else {
[localizedValues addObject:value];
}
}
if (replaceValues) {
[combobox removeAllItems];
[combobox addItemsWithObjectValues:localizedValues];
}
}
}
}
- (void)localizeMenu:(NSMenu *)menu recursively:(BOOL)recursive {
if (menu) {
NSString *title = [menu title];
NSString *localizedTitle = [self localizedStringForString:title];
if (localizedTitle) {
[menu setTitle:localizedTitle];
}
NSArray *menuItems = [menu itemArray];
NSMenuItem *menuItem = nil;
for (menuItem in menuItems) {
title = [menuItem title];
localizedTitle = [self localizedStringForString:title];
if (localizedTitle) {
[menuItem setTitle:localizedTitle];
}
if (recursive) {
[self localizeObject:[menuItem submenu] recursively:recursive];
}
}
}
}
- (void)localizeCell:(NSCell *)cell recursively:(BOOL)recursive {
if (cell) {
NSString *title = [cell title];
NSString *localizedTitle = [self localizedStringForString:title];
if (localizedTitle) {
[cell setTitle:localizedTitle];
}
[self localizeObject:[cell menu] recursively:recursive];
id obj = [cell representedObject];
[self localizeObject:obj recursively:recursive];
}
}
- (void)localizeBindings:(id)object {
NSArray *exposedBindings = [object exposedBindings];
if (exposedBindings) {
NSString *optionsToLocalize[] = {
NSDisplayNameBindingOption,
NSDisplayPatternBindingOption,
NSMultipleValuesPlaceholderBindingOption,
NSNoSelectionPlaceholderBindingOption,
NSNotApplicablePlaceholderBindingOption,
NSNullPlaceholderBindingOption,
};
Class stringClass = [NSString class];
NSString *exposedBinding;
for (exposedBinding in exposedBindings) {
NSDictionary *bindingInfo = [object infoForBinding:exposedBinding];
if (bindingInfo) {
id observedObject = [bindingInfo objectForKey:NSObservedObjectKey];
NSString *path = [bindingInfo objectForKey:NSObservedKeyPathKey];
NSDictionary *options = [bindingInfo objectForKey:NSOptionsKey];
if (observedObject && path && options) {
NSMutableDictionary *newOptions
= [NSMutableDictionary dictionaryWithDictionary:options];
BOOL valueChanged = NO;
for (size_t i = 0;
i < sizeof(optionsToLocalize) / sizeof(optionsToLocalize[0]);
++i) {
NSString *key = optionsToLocalize[i];
NSString *value = [newOptions objectForKey:key];
if ([value isKindOfClass:stringClass]) {
NSString *localizedValue = [self localizedStringForString:value];
if (localizedValue) {
valueChanged = YES;
[newOptions setObject:localizedValue forKey:key];
}
}
}
if (valueChanged) {
// Only unbind and rebind if there is a change.
[object unbind:exposedBinding];
[object bind:exposedBinding
toObject:observedObject
withKeyPath:path
options:newOptions];
}
}
}
}
}
}
- (void)localizeAccessibility:(id)object {
NSArray *supportedAttrs = [object accessibilityAttributeNames];
if ([supportedAttrs containsObject:NSAccessibilityHelpAttribute]) {
NSString *accessibilityHelp
= [object accessibilityAttributeValue:NSAccessibilityHelpAttribute];
if (accessibilityHelp) {
NSString *localizedAccessibilityHelp
= [self localizedStringForString:accessibilityHelp];
if (localizedAccessibilityHelp) {
[object accessibilitySetValue:localizedAccessibilityHelp
forAttribute:NSAccessibilityHelpAttribute];
}
}
}
// We cannot do the same thing with NSAccessibilityDescriptionAttribute; see
// the links in the header file for more details.
}
@end