From e7fa76964f5576e61b1c0a9a5a6005d954efa02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Mon, 12 Dec 2016 16:05:56 -0800 Subject: [PATCH] [macos] Refactored label localization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize only Mapbox Streets v6–v7, and do so more systematically than before. --- .../macos/app/MGLVectorSource+MBXAdditions.h | 15 +++++ .../macos/app/MGLVectorSource+MBXAdditions.m | 49 ++++++++++++++ platform/macos/app/MapDocument.m | 67 ++++++++++--------- .../macos/macos.xcodeproj/project.pbxproj | 6 ++ 4 files changed, 105 insertions(+), 32 deletions(-) create mode 100644 platform/macos/app/MGLVectorSource+MBXAdditions.h create mode 100644 platform/macos/app/MGLVectorSource+MBXAdditions.m diff --git a/platform/macos/app/MGLVectorSource+MBXAdditions.h b/platform/macos/app/MGLVectorSource+MBXAdditions.h new file mode 100644 index 00000000000..312081ec515 --- /dev/null +++ b/platform/macos/app/MGLVectorSource+MBXAdditions.h @@ -0,0 +1,15 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MGLVectorSource (MBXAdditions) + ++ (nullable NSString *)preferredMapboxStreetsLanguage; + +- (NS_DICTIONARY_OF(NSString *, NSString *) *)localizedKeysByKeyForPreferredLanguage:(nullable NSString *)preferredLanguage; + +@property (nonatomic, readonly, getter=isMapboxStreets) BOOL mapboxStreets; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/macos/app/MGLVectorSource+MBXAdditions.m b/platform/macos/app/MGLVectorSource+MBXAdditions.m new file mode 100644 index 00000000000..f59b72aa9fe --- /dev/null +++ b/platform/macos/app/MGLVectorSource+MBXAdditions.m @@ -0,0 +1,49 @@ +#import "MGLVectorSource+MBXAdditions.h" + +@implementation MGLVectorSource (MBXAdditions) + ++ (NS_SET_OF(NSString *) *)mapboxStreetsLanguages { + // https://www.mapbox.com/vector-tiles/mapbox-streets-v7/#overview + static dispatch_once_t onceToken; + static NS_SET_OF(NSString *) *mapboxStreetsLanguages; + dispatch_once(&onceToken, ^{ + mapboxStreetsLanguages = [NSSet setWithObjects:@"en", @"es", @"fr", @"de", @"ru", @"zh", nil]; + }); + return mapboxStreetsLanguages; +} + ++ (nullable NSString *)preferredMapboxStreetsLanguage { + for (NSString *language in [NSLocale preferredLanguages]) { + NSString *languageCode = [[NSLocale localeWithLocaleIdentifier:language] objectForKey:NSLocaleLanguageCode]; + if ([[MGLVectorSource mapboxStreetsLanguages] containsObject:languageCode]) { + return languageCode; + } + } + return nil; +} + +- (BOOL)isMapboxStreets { + NSURL *url = self.configurationURL; + if (![url.scheme isEqualToString:@"mapbox"]) { + return NO; + } + NSArray *identifiers = [url.host componentsSeparatedByString:@","]; + return [identifiers containsObject:@"mapbox.mapbox-streets-v7"] || [identifiers containsObject:@"mapbox.mapbox-streets-v6"]; +} + +- (NS_DICTIONARY_OF(NSString *, NSString *) *)localizedKeysByKeyForPreferredLanguage:(nullable NSString *)preferredLanguage { + if (!self.mapboxStreets) { + return @{}; + } + + // Replace {name} and {name_*} with the matching localized name tag. + NSString *localizedKey = preferredLanguage ? [NSString stringWithFormat:@"name_%@", preferredLanguage] : @"name"; + NSMutableDictionary *localizedKeysByKey = [NSMutableDictionary dictionaryWithObject:localizedKey forKey:@"name"]; + for (NSString *languageCode in [MGLVectorSource mapboxStreetsLanguages]) { + NSString *key = [NSString stringWithFormat:@"name_%@", languageCode]; + localizedKeysByKey[key] = localizedKey; + } + return localizedKeysByKey; +} + +@end diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 2c316107790..705ebb8a456 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -4,6 +4,8 @@ #import "LimeGreenStyleLayer.h" #import "DroppedPinAnnotation.h" +#import "MGLVectorSource+MBXAdditions.h" + #import static NSString * const MGLDroppedPinAnnotationImageIdentifier = @"dropped"; @@ -331,36 +333,50 @@ - (void)deleteStyleLayersAtArrangedObjectIndexes:(NSIndexSet *)indices { - (IBAction)setLabelLanguage:(NSMenuItem *)sender { _isLocalizingLabels = sender.tag; - [self updateLabels]; + [self reload:sender]; } - (void)updateLabels { - NSString *preferredLanguageCode = self.preferredLanguageCode; - NSString *preferredNameToken = _isLocalizingLabels ? [NSString stringWithFormat:@"{name_%@}", preferredLanguageCode] : @"{name}"; - NSRegularExpression *nameTokenExpression = [NSRegularExpression regularExpressionWithPattern:@"\\{name(?:_\\w{2})?\\}" options:0 error:NULL]; - - for (MGLSymbolStyleLayer *layer in self.mapView.style.layers) { + MGLStyle *style = self.mapView.style; + NSString *preferredLanguage = _isLocalizingLabels ? ([MGLVectorSource preferredMapboxStreetsLanguage] ?: @"en") : nil; + NSMutableDictionary *localizedKeysByKeyBySourceIdentifier = [NSMutableDictionary dictionary]; + for (MGLSymbolStyleLayer *layer in style.layers) { if (![layer isKindOfClass:[MGLSymbolStyleLayer class]]) { continue; } + MGLVectorSource *source = (MGLVectorSource *)[style sourceWithIdentifier:layer.sourceIdentifier]; + if (![source isKindOfClass:[MGLVectorSource class]] || !source.mapboxStreets) { + continue; + } + + NSDictionary *localizedKeysByKey = localizedKeysByKeyBySourceIdentifier[layer.sourceIdentifier]; + if (!localizedKeysByKey) { + localizedKeysByKey = localizedKeysByKeyBySourceIdentifier[layer.sourceIdentifier] = [source localizedKeysByKeyForPreferredLanguage:preferredLanguage]; + } + + NSString *(^stringByLocalizingString)(NSString *) = ^ NSString * (NSString *string) { + NSMutableString *localizedString = string.mutableCopy; + [localizedKeysByKey enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull localizedKey, BOOL * _Nonnull stop) { + NSAssert([key isKindOfClass:[NSString class]], @"key is not a string"); + NSAssert([localizedKey isKindOfClass:[NSString class]], @"localizedKey is not a string"); + [localizedString replaceOccurrencesOfString:[NSString stringWithFormat:@"{%@}", key] + withString:[NSString stringWithFormat:@"{%@}", localizedKey] + options:0 + range:NSMakeRange(0, localizedString.length)]; + }]; + return localizedString; + }; + if ([layer.textField isKindOfClass:[MGLStyleConstantValue class]]) { NSString *textField = [(MGLStyleConstantValue *)layer.textField rawValue]; - textField = [nameTokenExpression stringByReplacingMatchesInString:textField - options:0 - range:NSMakeRange(0, textField.length) - withTemplate:preferredNameToken]; - layer.textField = [MGLStyleValue valueWithRawValue:textField]; + layer.textField = [MGLStyleValue valueWithRawValue:stringByLocalizingString(textField)]; } else if ([layer.textField isKindOfClass:[MGLStyleFunction class]]) { MGLStyleFunction *function = (MGLStyleFunction *)layer.textField; NSMutableDictionary *stops = function.stops.mutableCopy; [stops enumerateKeysAndObjectsUsingBlock:^(NSNumber *zoomLevel, MGLStyleConstantValue *stop, BOOL *done) { NSString *textField = stop.rawValue; - textField = [nameTokenExpression stringByReplacingMatchesInString:textField - options:0 - range:NSMakeRange(0, textField.length) - withTemplate:preferredNameToken]; - stops[zoomLevel] = [MGLStyleValue valueWithRawValue:textField]; + stops[zoomLevel] = [MGLStyleValue valueWithRawValue:stringByLocalizingString(textField)]; }]; function.stops = stops; layer.textField = function; @@ -368,21 +384,6 @@ - (void)updateLabels { } } -- (NSString *)preferredLanguageCode { - // Languages supported by Mapbox Streets v10. - NSSet *supportedLanguages = [NSSet setWithObjects:@"en", @"es", @"fr", @"de", @"ru", @"zh", nil]; - NSArray *preferredLanguages = [NSLocale preferredLanguages]; - - for (NSString *language in preferredLanguages) { - NSString *languageCode = [[NSLocale localeWithLocaleIdentifier:language] objectForKey:NSLocaleLanguageCode]; - if ([supportedLanguages containsObject:languageCode]) { - return languageCode; - } - } - - return @"en"; -} - - (void)applyPendingState { if (_inheritedStyleURL) { self.mapView.styleURL = _inheritedStyleURL; @@ -836,7 +837,9 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { menuItem.state = menuItem.tag == _isLocalizingLabels ? NSOnState: NSOffState; if (menuItem.tag) { NSLocale *locale = [NSLocale localeWithLocaleIdentifier:[NSBundle mainBundle].developmentLocalization]; - menuItem.title = [locale displayNameForKey:NSLocaleIdentifier value:self.preferredLanguageCode]; + NSString *preferredLanguage = [MGLVectorSource preferredMapboxStreetsLanguage]; + menuItem.enabled = !!preferredLanguage; + menuItem.title = [locale displayNameForKey:NSLocaleIdentifier value:preferredLanguage ?: @"Preferred Language"]; } return YES; } diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index 5edb4429fb2..e68d5f1bf50 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -211,6 +211,7 @@ DAEDC4371D606291000224FF /* MGLAttributionButtonTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DAEDC4361D606291000224FF /* MGLAttributionButtonTests.m */; }; DAF0D80E1DFE0E5D00B28378 /* MGLPointCollection_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAF0D80D1DFE0E5D00B28378 /* MGLPointCollection_Private.h */; }; DAF0D8161DFE6B1800B28378 /* MGLAttributionInfo_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAF0D8151DFE6B1800B28378 /* MGLAttributionInfo_Private.h */; }; + DAF0D81C1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DAF0D81B1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m */; }; DD0902B21DB1AC6400C5BDCE /* MGLNetworkConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = DD0902AF1DB1AC6400C5BDCE /* MGLNetworkConfiguration.m */; }; DD0902B31DB1AC6400C5BDCE /* MGLNetworkConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = DD0902B01DB1AC6400C5BDCE /* MGLNetworkConfiguration.h */; }; DD58A4C91D822C6700E1F038 /* MGLExpressionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DD58A4C71D822C6200E1F038 /* MGLExpressionTests.mm */; }; @@ -475,6 +476,8 @@ DAEDC4361D606291000224FF /* MGLAttributionButtonTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLAttributionButtonTests.m; sourceTree = ""; }; DAF0D80D1DFE0E5D00B28378 /* MGLPointCollection_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLPointCollection_Private.h; sourceTree = ""; }; DAF0D8151DFE6B1800B28378 /* MGLAttributionInfo_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo_Private.h; sourceTree = ""; }; + DAF0D81A1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MGLVectorSource+MBXAdditions.h"; sourceTree = ""; }; + DAF0D81B1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MGLVectorSource+MBXAdditions.m"; sourceTree = ""; }; DD0902AF1DB1AC6400C5BDCE /* MGLNetworkConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLNetworkConfiguration.m; sourceTree = ""; }; DD0902B01DB1AC6400C5BDCE /* MGLNetworkConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLNetworkConfiguration.h; sourceTree = ""; }; DD58A4C71D822C6200E1F038 /* MGLExpressionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLExpressionTests.mm; path = ../../darwin/test/MGLExpressionTests.mm; sourceTree = ""; }; @@ -623,6 +626,8 @@ DA839E9B1CC2E3400062CAFB /* MapDocument.h */, DA839E9C1CC2E3400062CAFB /* MapDocument.m */, DA839E9E1CC2E3400062CAFB /* MapDocument.xib */, + DAF0D81A1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.h */, + DAF0D81B1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m */, DAE6C2E91CC3050F00DB3429 /* OfflinePackNameValueTransformer.h */, DAE6C2EA1CC3050F00DB3429 /* OfflinePackNameValueTransformer.m */, DAA48EFB1D6A4731006A7E36 /* StyleLayerIconTransformer.h */, @@ -1219,6 +1224,7 @@ DAE6C2F11CC3050F00DB3429 /* TimeIntervalTransformer.m in Sources */, DA839E9A1CC2E3400062CAFB /* main.m in Sources */, DA839E971CC2E3400062CAFB /* AppDelegate.m in Sources */, + DAF0D81C1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m in Sources */, DAE6C2F01CC3050F00DB3429 /* OfflinePackNameValueTransformer.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0;