| // |
| // 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 |