Localize expressions more thoroughly (#11651)
* [ios] Removed changelog entry for Terrarium The broader feature is new to v4.0.0 as well. * [ios, macos] Localize expressions more thoroughly Replaced the MGLStyle.localizesLabels property with a -localizeLabelsIntoLocale: method that allows the caller to specify the locale to localize into. Also exposed a per-expression localization method for developers who want to vary behavior from layer to layer. * [macos] Offer English labels if preferred language is unsupported * [ios, macos] Removed dead code * [ios] Use new localization method in iosapp * [ios, macos] Fixed local name labels * [ios, macos] Convert tokens to key path expressions in stop dictionaries * [ios, macos] Streamlined token upgrading Separated token upgrading into a separate process that only happens as part of the MGLSymbolStyleLayer.text and MGLSymbolStyleLayer.iconImageName properties’ getters, so that it’s easy to remove later when mbgl changes obviate this workaround. Removed the replacesTokens parameter to the expression localization methods. * [ios, macos] Preserve whitespace between tokens * [ios, macos] Moved token replacement to a consistent category Fixed a build warning. * [ios, macos] Replace tokens in all string-typed getters For consistency, replace tokens with key paths in all string-typed style paint and layout properties. * [ios, macos] Test token replacement Added tests for replacement of tokens with key paths in expressions. Fixed token replacement for raw strings in stop dictionaries. Avoid sticking a single string inside an mgl_join: call. * [ios, macos] Test token replacement, localization Added unit tests of token replacement and localization of expressions. Only NSExpression is responsible for resolving the preferred language now, since NSLocale tends to tack a region code onto the locale identifier and the NSExpression method can be called independently anyways. Added a private variation of +[MGLVectorTileSource preferredMapboxStreetsLanguage] that takes an array of preferred languages. Fixed localization of non-expressions in stop dictionaries. * [ios, macos] Dictionary keys aren’t necessarily zoom levels
This commit is contained in:
@@ -116,7 +116,8 @@
|
||||
if (propertyValue.isUndefined()) {
|
||||
propertyValue = self.rawLayer->getDefaultBackgroundPattern();
|
||||
}
|
||||
return MGLStyleValueTransformer<std::string, NSString *>().toExpression(propertyValue);
|
||||
NSExpression *expression = MGLStyleValueTransformer<std::string, NSString *>().toExpression(propertyValue);
|
||||
return expression.mgl_expressionByReplacingTokensWithKeyPaths;
|
||||
}
|
||||
|
||||
- (void)setBackgroundPatternTransition:(MGLTransition )transition {
|
||||
|
||||
@@ -231,7 +231,8 @@ namespace mbgl {
|
||||
if (propertyValue.isUndefined()) {
|
||||
propertyValue = self.rawLayer->getDefaultFillExtrusionPattern();
|
||||
}
|
||||
return MGLStyleValueTransformer<std::string, NSString *>().toExpression(propertyValue);
|
||||
NSExpression *expression = MGLStyleValueTransformer<std::string, NSString *>().toExpression(propertyValue);
|
||||
return expression.mgl_expressionByReplacingTokensWithKeyPaths;
|
||||
}
|
||||
|
||||
- (void)setFillExtrusionPatternTransition:(MGLTransition )transition {
|
||||
|
||||
@@ -220,7 +220,8 @@ namespace mbgl {
|
||||
if (propertyValue.isUndefined()) {
|
||||
propertyValue = self.rawLayer->getDefaultFillPattern();
|
||||
}
|
||||
return MGLStyleValueTransformer<std::string, NSString *>().toExpression(propertyValue);
|
||||
NSExpression *expression = MGLStyleValueTransformer<std::string, NSString *>().toExpression(propertyValue);
|
||||
return expression.mgl_expressionByReplacingTokensWithKeyPaths;
|
||||
}
|
||||
|
||||
- (void)setFillPatternTransition:(MGLTransition )transition {
|
||||
|
||||
@@ -390,7 +390,8 @@ namespace mbgl {
|
||||
if (propertyValue.isUndefined()) {
|
||||
propertyValue = self.rawLayer->getDefaultLinePattern();
|
||||
}
|
||||
return MGLStyleValueTransformer<std::string, NSString *>().toExpression(propertyValue);
|
||||
NSExpression *expression = MGLStyleValueTransformer<std::string, NSString *>().toExpression(propertyValue);
|
||||
return expression.mgl_expressionByReplacingTokensWithKeyPaths;
|
||||
}
|
||||
|
||||
- (void)setLinePatternTransition:(MGLTransition )transition {
|
||||
|
||||
@@ -493,17 +493,20 @@ MGL_EXPORT
|
||||
#pragma mark Localizing Map Content
|
||||
|
||||
/**
|
||||
A Boolean value that determines whether the style attempts to localize labels in
|
||||
the style into the system’s preferred language.
|
||||
Attempts to localize labels in the style into the given locale.
|
||||
|
||||
When this property is enabled, the style automatically modifies the text property
|
||||
of any symbol style layer whose source is the
|
||||
<a href="https://www.mapbox.com/vector-tiles/mapbox-streets-v7/#overview">Mapbox
|
||||
Streets source</a>. On iOS, the user can set the system’s preferred language in
|
||||
Settings, General Settings, Language & Region. On macOS, the user can set the
|
||||
system’s preferred language in the Language & Region pane of System Preferences.
|
||||
This method automatically modifies the text property of any symbol style layer
|
||||
in the style whose source is the
|
||||
<a href="https://www.mapbox.com/vector-tiles/mapbox-streets-v7/#overview">Mapbox Streets source</a>.
|
||||
On iOS, the user can set the system’s preferred language in Settings, General
|
||||
Settings, Language & Region. On macOS, the user can set the system’s preferred
|
||||
language in the Language & Region pane of System Preferences.
|
||||
|
||||
@param locale The locale into which labels should be localized. To use the
|
||||
system’s preferred language, if supported, specify `nil`. To use the local
|
||||
language, specify a locale with the identifier `mul`.
|
||||
*/
|
||||
@property (nonatomic) BOOL localizesLabels;
|
||||
- (void)localizeLabelsIntoLocale:(nullable NSLocale *)locale;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -570,73 +570,27 @@ static_assert(6 == mbgl::util::default_styles::numOrderedStyles,
|
||||
|
||||
#pragma mark Mapbox Streets source introspection
|
||||
|
||||
- (void)setLocalizesLabels:(BOOL)localizesLabels
|
||||
{
|
||||
if (_localizesLabels != localizesLabels) {
|
||||
_localizesLabels = localizesLabels;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
- (void)localizeLabelsIntoLocale:(nullable NSLocale *)locale {
|
||||
NSSet<MGLVectorTileSource *> *streetsSources =
|
||||
[self.sources filteredSetUsingPredicate:
|
||||
[NSPredicate predicateWithBlock:^BOOL(MGLVectorTileSource * _Nullable source, NSDictionary<NSString *, id> * _Nullable bindings) {
|
||||
return [source isKindOfClass:[MGLVectorTileSource class]] && [source isMapboxStreets];
|
||||
}]];
|
||||
NSSet<NSString *> *streetsSourceIdentifiers = [streetsSources valueForKey:@"identifier"];
|
||||
|
||||
if (_localizesLabels) {
|
||||
NSString *preferredLanguage = [MGLVectorTileSource preferredMapboxStreetsLanguage];
|
||||
NSMutableDictionary *localizedKeysByKeyBySourceIdentifier = [NSMutableDictionary dictionary];
|
||||
for (MGLSymbolStyleLayer *layer in self.layers) {
|
||||
if (![layer isKindOfClass:[MGLSymbolStyleLayer class]]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MGLVectorTileSource *source = (MGLVectorTileSource *)[self sourceWithIdentifier:layer.sourceIdentifier];
|
||||
if (![source isKindOfClass:[MGLVectorTileSource 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.text.expressionType == NSConstantValueExpressionType) {
|
||||
NSString *textField = layer.text.constantValue;
|
||||
NSString *localizingString = stringByLocalizingString(textField);
|
||||
if (![textField isEqualToString:localizingString]) {
|
||||
MGLTextLanguage *textLanguage = [[MGLTextLanguage alloc] initWithTextLanguage:textField
|
||||
updatedTextField:localizingString];
|
||||
[self.localizedLayersByIdentifier setObject:@{ textField : textLanguage } forKey:layer.identifier];
|
||||
layer.text = [NSExpression expressionForConstantValue:localizingString];
|
||||
}
|
||||
}
|
||||
for (MGLSymbolStyleLayer *layer in self.layers) {
|
||||
if (![layer isKindOfClass:[MGLSymbolStyleLayer class]]) {
|
||||
continue;
|
||||
}
|
||||
if (![streetsSourceIdentifiers containsObject:layer.sourceIdentifier]) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
|
||||
[self.localizedLayersByIdentifier enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, NSDictionary<NSObject *, MGLTextLanguage *> *textFields, BOOL *done) {
|
||||
MGLSymbolStyleLayer *layer = (MGLSymbolStyleLayer *)[self.mapView.style layerWithIdentifier:identifier];
|
||||
|
||||
if (layer.text.expressionType == NSConstantValueExpressionType) {
|
||||
NSString *textField = layer.text.constantValue;
|
||||
[textFields enumerateKeysAndObjectsUsingBlock:^(NSObject *originalLanguage, MGLTextLanguage *textLanguage, BOOL *done) {
|
||||
if ([textLanguage.updatedTextField isEqualToString:textField]) {
|
||||
layer.text = [NSExpression expressionForConstantValue:textLanguage.originalTextField];
|
||||
}
|
||||
}];
|
||||
|
||||
}
|
||||
}];
|
||||
|
||||
self.localizedLayersByIdentifier = [NSMutableDictionary dictionary];
|
||||
NSExpression *text = layer.text;
|
||||
NSExpression *localizedText = [text mgl_expressionLocalizedIntoLocale:locale];
|
||||
if (![localizedText isEqual:text]) {
|
||||
layer.text = localizedText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,12 @@ namespace mbgl {
|
||||
if (propertyValue.isUndefined()) {
|
||||
propertyValue = self.rawLayer->getDefault<%- camelize(originalPropertyName(property)) %>();
|
||||
}
|
||||
<% if (property.type === 'string') { -%>
|
||||
NSExpression *expression = MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toExpression(propertyValue);
|
||||
return expression.mgl_expressionByReplacingTokensWithKeyPaths;
|
||||
<% } else { -%>
|
||||
return MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toExpression(propertyValue);
|
||||
<% } -%>
|
||||
}
|
||||
|
||||
<% if (property.original) { -%>
|
||||
@@ -174,7 +179,12 @@ namespace mbgl {
|
||||
if (propertyValue.isUndefined()) {
|
||||
propertyValue = self.rawLayer->getDefault<%- camelize(originalPropertyName(property)) %>();
|
||||
}
|
||||
<% if (property.type === 'string') { -%>
|
||||
NSExpression *expression = MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toExpression(propertyValue);
|
||||
return expression.mgl_expressionByReplacingTokensWithKeyPaths;
|
||||
<% } else { -%>
|
||||
return MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toExpression(propertyValue);
|
||||
<% } -%>
|
||||
}
|
||||
<% if (property["transition"]) { -%>
|
||||
|
||||
|
||||
@@ -233,7 +233,8 @@ namespace mbgl {
|
||||
if (propertyValue.isUndefined()) {
|
||||
propertyValue = self.rawLayer->getDefaultIconImage();
|
||||
}
|
||||
return MGLStyleValueTransformer<std::string, NSString *>().toExpression(propertyValue);
|
||||
NSExpression *expression = MGLStyleValueTransformer<std::string, NSString *>().toExpression(propertyValue);
|
||||
return expression.mgl_expressionByReplacingTokensWithKeyPaths;
|
||||
}
|
||||
|
||||
- (void)setIconImage:(NSExpression *)iconImage {
|
||||
@@ -578,7 +579,8 @@ namespace mbgl {
|
||||
if (propertyValue.isUndefined()) {
|
||||
propertyValue = self.rawLayer->getDefaultTextField();
|
||||
}
|
||||
return MGLStyleValueTransformer<std::string, NSString *>().toExpression(propertyValue);
|
||||
NSExpression *expression = MGLStyleValueTransformer<std::string, NSString *>().toExpression(propertyValue);
|
||||
return expression.mgl_expressionByReplacingTokensWithKeyPaths;
|
||||
}
|
||||
|
||||
- (void)setTextField:(NSExpression *)textField {
|
||||
|
||||
@@ -75,28 +75,55 @@
|
||||
|
||||
@implementation MGLVectorTileSource (Private)
|
||||
|
||||
/**
|
||||
An array of locale codes with dedicated name fields in the Mapbox Streets
|
||||
source.
|
||||
|
||||
https://www.mapbox.com/vector-tiles/mapbox-streets-v7/#overview
|
||||
*/
|
||||
static NSArray * const MGLMapboxStreetsLanguages = @[
|
||||
@"ar", @"de", @"en", @"es", @"fr", @"pt", @"ru", @"zh", @"zh-Hans",
|
||||
];
|
||||
|
||||
/**
|
||||
Like `MGLMapboxStreetsLanguages`, but deanglicized for use with
|
||||
`+[NSBundle preferredLocalizationsFromArray:forPreferences:]`.
|
||||
*/
|
||||
static NSArray * const MGLMapboxStreetsAlternativeLanguages = @[
|
||||
@"mul", @"ar", @"de", @"es", @"fr", @"pt", @"ru", @"zh", @"zh-Hans",
|
||||
];
|
||||
|
||||
+ (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, ^{
|
||||
// https://www.mapbox.com/vector-tiles/mapbox-streets-v7/#overview
|
||||
mapboxStreetsLanguages = [NSSet setWithObjects:@"ar", @"de", @"en", @"es", @"fr", @"pt", @"ru", @"zh", @"zh-Hans", nil];
|
||||
mapboxStreetsLanguages = [NSSet setWithArray:MGLMapboxStreetsLanguages];
|
||||
});
|
||||
return mapboxStreetsLanguages;
|
||||
}
|
||||
|
||||
+ (NSString *)preferredMapboxStreetsLanguage {
|
||||
NSArray<NSString *> *supportedLanguages = [MGLVectorTileSource mapboxStreetsLanguages].allObjects;
|
||||
NSArray<NSString *> *preferredLanguages = [NSBundle preferredLocalizationsFromArray:supportedLanguages
|
||||
forPreferences:[NSLocale preferredLanguages]];
|
||||
return [self preferredMapboxStreetsLanguageForPreferences:[NSLocale preferredLanguages]];
|
||||
}
|
||||
|
||||
+ (NSString *)preferredMapboxStreetsLanguageForPreferences:(NSArray<NSString *> *)preferencesArray {
|
||||
BOOL acceptsEnglish = [preferencesArray filteredArrayUsingPredicate:
|
||||
[NSPredicate predicateWithBlock:^BOOL(NSString * _Nullable language, NSDictionary<NSString *,id> * _Nullable bindings) {
|
||||
return [[NSLocale localeWithLocaleIdentifier:language].languageCode isEqualToString:@"en"];
|
||||
}]].count;
|
||||
|
||||
NSArray<NSString *> *preferredLanguages = [NSBundle preferredLocalizationsFromArray:MGLMapboxStreetsAlternativeLanguages
|
||||
forPreferences:preferencesArray];
|
||||
NSString *mostSpecificLanguage;
|
||||
for (NSString *language in preferredLanguages) {
|
||||
if (language.length > mostSpecificLanguage.length) {
|
||||
mostSpecificLanguage = language;
|
||||
}
|
||||
}
|
||||
return mostSpecificLanguage ?: @"en";
|
||||
if ([mostSpecificLanguage isEqualToString:@"mul"]) {
|
||||
return acceptsEnglish ? @"en" : nil;
|
||||
}
|
||||
return mostSpecificLanguage;
|
||||
}
|
||||
|
||||
- (BOOL)isMapboxStreets {
|
||||
|
||||
@@ -8,7 +8,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
+ (NS_SET_OF(NSString *) *)mapboxStreetsLanguages;
|
||||
|
||||
+ (NSString *)preferredMapboxStreetsLanguage;
|
||||
+ (nullable NSString *)preferredMapboxStreetsLanguage;
|
||||
+ (nullable NSString *)preferredMapboxStreetsLanguageForPreferences:(NSArray<NSString *> *)preferencesArray;
|
||||
|
||||
- (NS_DICTIONARY_OF(NSString *, NSString *) *)localizedKeysByKeyForPreferredLanguage:(nullable NSString *)preferredLanguage;
|
||||
|
||||
|
||||
@@ -44,6 +44,22 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@property (nonatomic, readonly) id mgl_jsonExpressionObject;
|
||||
|
||||
/**
|
||||
Returns a copy of the receiver localized into the given locale.
|
||||
|
||||
This method assumes the receiver refers to the feature attributes that are
|
||||
available in vector tiles supplied by the
|
||||
<a href="https://www.mapbox.com/vector-tiles/mapbox-streets-v7/#overview">Mapbox Streets source</a>.
|
||||
On iOS, the user can set the system’s preferred language in Settings, General
|
||||
Settings, Language & Region. On macOS, the user can set the system’s preferred
|
||||
language in the Language & Region pane of System Preferences.
|
||||
|
||||
@param locale The locale into which labels should be localized. To use the
|
||||
system’s preferred language, if supported, specify `nil`. To use the local
|
||||
language, specify a locale with the identifier `mul`.
|
||||
*/
|
||||
- (NSExpression *)mgl_expressionLocalizedIntoLocale:(nullable NSLocale *)locale;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endif
|
||||
#import "NSPredicate+MGLAdditions.h"
|
||||
#import "NSValue+MGLStyleAttributeAdditions.h"
|
||||
#import "MGLVectorTileSource_Private.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@@ -328,6 +329,112 @@
|
||||
return {};
|
||||
}
|
||||
|
||||
// Selectors of functions that can contain tokens in arguments.
|
||||
static NSArray * const MGLTokenizedFunctions = @[
|
||||
@"mgl_interpolateWithCurveType:parameters:stops:",
|
||||
@"mgl_interpolate:withCurveType:parameters:stops:",
|
||||
@"mgl_stepWithMinimum:stops:",
|
||||
@"mgl_step:from:stops:",
|
||||
];
|
||||
|
||||
/**
|
||||
Returns a copy of the given collection with tokens replaced by key path
|
||||
expressions.
|
||||
|
||||
If no replacements take place, this method returns the original collection.
|
||||
*/
|
||||
NS_ARRAY_OF(NSExpression *) *MGLCollectionByReplacingTokensWithKeyPaths(NS_ARRAY_OF(NSExpression *) *collection) {
|
||||
__block NSMutableArray *upgradedCollection;
|
||||
[collection enumerateObjectsUsingBlock:^(NSExpression * _Nonnull item, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
NSExpression *upgradedItem = item.mgl_expressionByReplacingTokensWithKeyPaths;
|
||||
if (upgradedItem != item) {
|
||||
if (!upgradedCollection) {
|
||||
upgradedCollection = [collection mutableCopy];
|
||||
}
|
||||
upgradedCollection[idx] = upgradedItem;
|
||||
}
|
||||
}];
|
||||
return upgradedCollection ?: collection;
|
||||
};
|
||||
|
||||
/**
|
||||
Returns a copy of the given stop dictionary with tokens replaced by key path
|
||||
expressions.
|
||||
|
||||
If no replacements take place, this method returns the original stop
|
||||
dictionary.
|
||||
*/
|
||||
NS_DICTIONARY_OF(NSNumber *, NSExpression *) *MGLStopDictionaryByReplacingTokensWithKeyPaths(NS_DICTIONARY_OF(NSNumber *, NSExpression *) *stops) {
|
||||
__block NSMutableDictionary *upgradedStops;
|
||||
[stops enumerateKeysAndObjectsUsingBlock:^(id _Nonnull zoomLevel, NSExpression * _Nonnull value, BOOL * _Nonnull stop) {
|
||||
if (![value isKindOfClass:[NSExpression class]]) {
|
||||
value = [NSExpression expressionForConstantValue:value];
|
||||
}
|
||||
NSExpression *upgradedValue = value.mgl_expressionByReplacingTokensWithKeyPaths;
|
||||
if (upgradedValue != value) {
|
||||
if (!upgradedStops) {
|
||||
upgradedStops = [stops mutableCopy];
|
||||
}
|
||||
upgradedStops[zoomLevel] = upgradedValue;
|
||||
}
|
||||
}];
|
||||
return upgradedStops ?: stops;
|
||||
};
|
||||
|
||||
- (NSExpression *)mgl_expressionByReplacingTokensWithKeyPaths {
|
||||
switch (self.expressionType) {
|
||||
case NSConstantValueExpressionType: {
|
||||
NSString *constantValue = self.constantValue;
|
||||
if ([constantValue isKindOfClass:[NSString class]] &&
|
||||
[constantValue containsString:@"{"] && [constantValue containsString:@"}"]) {
|
||||
NSMutableArray *components = [NSMutableArray array];
|
||||
NSScanner *scanner = [NSScanner scannerWithString:constantValue];
|
||||
scanner.charactersToBeSkipped = nil;
|
||||
while (!scanner.isAtEnd) {
|
||||
NSString *string;
|
||||
if ([scanner scanUpToString:@"{" intoString:&string]) {
|
||||
[components addObject:[NSExpression expressionForConstantValue:string]];
|
||||
}
|
||||
|
||||
NSString *token;
|
||||
if ([scanner scanString:@"{" intoString:NULL]
|
||||
&& [scanner scanUpToString:@"}" intoString:&token]
|
||||
&& [scanner scanString:@"}" intoString:NULL]) {
|
||||
[components addObject:[NSExpression expressionForKeyPath:token]];
|
||||
}
|
||||
}
|
||||
if (components.count == 1) {
|
||||
return components.firstObject;
|
||||
}
|
||||
return [NSExpression expressionForFunction:@"mgl_join:"
|
||||
arguments:@[[NSExpression expressionForAggregate:components]]];
|
||||
}
|
||||
NSDictionary *stops = self.constantValue;
|
||||
if ([stops isKindOfClass:[NSDictionary class]]) {
|
||||
NSDictionary *localizedStops = MGLStopDictionaryByReplacingTokensWithKeyPaths(stops);
|
||||
if (localizedStops != stops) {
|
||||
return [NSExpression expressionForConstantValue:localizedStops];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
case NSFunctionExpressionType: {
|
||||
if ([MGLTokenizedFunctions containsObject:self.function]) {
|
||||
NSArray *arguments = self.arguments;
|
||||
NSArray *localizedArguments = MGLCollectionByReplacingTokensWithKeyPaths(arguments);
|
||||
if (localizedArguments != arguments) {
|
||||
return [NSExpression expressionForFunction:self.operand selectorName:self.function arguments:localizedArguments];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
default:
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSObject (MGLExpressionAdditions)
|
||||
@@ -1108,4 +1215,126 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) {
|
||||
return expressionObject;
|
||||
}
|
||||
|
||||
#pragma mark Localization
|
||||
|
||||
/**
|
||||
Returns a localized copy of the given collection.
|
||||
|
||||
If no localization takes place, this method returns the original collection.
|
||||
*/
|
||||
NS_ARRAY_OF(NSExpression *) *MGLLocalizedCollection(NS_ARRAY_OF(NSExpression *) *collection, NSLocale * _Nullable locale) {
|
||||
__block NSMutableArray *localizedCollection;
|
||||
[collection enumerateObjectsUsingBlock:^(NSExpression * _Nonnull item, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
NSExpression *localizedItem = [item mgl_expressionLocalizedIntoLocale:locale];
|
||||
if (localizedItem != item) {
|
||||
if (!localizedCollection) {
|
||||
localizedCollection = [collection mutableCopy];
|
||||
}
|
||||
localizedCollection[idx] = localizedItem;
|
||||
}
|
||||
}];
|
||||
return localizedCollection ?: collection;
|
||||
};
|
||||
|
||||
/**
|
||||
Returns a localized copy of the given stop dictionary.
|
||||
|
||||
If no localization takes place, this method returns the original stop
|
||||
dictionary.
|
||||
*/
|
||||
NS_DICTIONARY_OF(NSNumber *, NSExpression *) *MGLLocalizedStopDictionary(NS_DICTIONARY_OF(NSNumber *, NSExpression *) *stops, NSLocale * _Nullable locale) {
|
||||
__block NSMutableDictionary *localizedStops;
|
||||
[stops enumerateKeysAndObjectsUsingBlock:^(id _Nonnull zoomLevel, NSExpression * _Nonnull value, BOOL * _Nonnull stop) {
|
||||
if (![value isKindOfClass:[NSExpression class]]) {
|
||||
value = [NSExpression expressionForConstantValue:value];
|
||||
}
|
||||
NSExpression *localizedValue = [value mgl_expressionLocalizedIntoLocale:locale];
|
||||
if (localizedValue != value) {
|
||||
if (!localizedStops) {
|
||||
localizedStops = [stops mutableCopy];
|
||||
}
|
||||
localizedStops[zoomLevel] = localizedValue;
|
||||
}
|
||||
}];
|
||||
return localizedStops ?: stops;
|
||||
};
|
||||
|
||||
- (NSExpression *)mgl_expressionLocalizedIntoLocale:(nullable NSLocale *)locale {
|
||||
switch (self.expressionType) {
|
||||
case NSConstantValueExpressionType: {
|
||||
NSDictionary *stops = self.constantValue;
|
||||
if ([stops isKindOfClass:[NSDictionary class]]) {
|
||||
NSDictionary *localizedStops = MGLLocalizedStopDictionary(stops, locale);
|
||||
if (localizedStops != stops) {
|
||||
return [NSExpression expressionForConstantValue:localizedStops];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
case NSKeyPathExpressionType: {
|
||||
if ([self.keyPath isEqualToString:@"name"] || [self.keyPath hasPrefix:@"name_"]) {
|
||||
NSString *localizedKeyPath = @"name";
|
||||
if (![locale.localeIdentifier isEqualToString:@"mul"]) {
|
||||
NSArray *preferences = locale ? @[locale.localeIdentifier] : [NSLocale preferredLanguages];
|
||||
NSString *preferredLanguage = [MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences];
|
||||
if (preferredLanguage) {
|
||||
localizedKeyPath = [NSString stringWithFormat:@"name_%@", preferredLanguage];
|
||||
}
|
||||
}
|
||||
return [NSExpression expressionForKeyPath:localizedKeyPath];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
case NSFunctionExpressionType: {
|
||||
NSExpression *operand = self.operand;
|
||||
NSExpression *localizedOperand = [operand mgl_expressionLocalizedIntoLocale:locale];
|
||||
|
||||
NSArray *arguments = self.arguments;
|
||||
NSArray *localizedArguments = MGLLocalizedCollection(arguments, locale);
|
||||
if (localizedArguments != arguments) {
|
||||
return [NSExpression expressionForFunction:localizedOperand
|
||||
selectorName:self.function
|
||||
arguments:localizedArguments];
|
||||
}
|
||||
if (localizedOperand != operand) {
|
||||
return [NSExpression expressionForFunction:localizedOperand
|
||||
selectorName:self.function
|
||||
arguments:self.arguments];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
case NSConditionalExpressionType: {
|
||||
if (@available(iOS 9.0, *)) {
|
||||
NSExpression *trueExpression = self.trueExpression;
|
||||
NSExpression *localizedTrueExpression = [trueExpression mgl_expressionLocalizedIntoLocale:locale];
|
||||
NSExpression *falseExpression = self.falseExpression;
|
||||
NSExpression *localizedFalseExpression = [falseExpression mgl_expressionLocalizedIntoLocale:locale];
|
||||
if (localizedTrueExpression != trueExpression || localizedFalseExpression != falseExpression) {
|
||||
return [NSExpression expressionForConditional:self.predicate
|
||||
trueExpression:localizedTrueExpression
|
||||
falseExpression:localizedFalseExpression];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
case NSAggregateExpressionType: {
|
||||
NSArray *collection = self.collection;
|
||||
if ([collection isKindOfClass:[NSArray class]]) {
|
||||
NSArray *localizedCollection = MGLLocalizedCollection(collection, locale);
|
||||
if (localizedCollection != collection) {
|
||||
return [NSExpression expressionForAggregate:localizedCollection];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
default:
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,6 +27,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic, readonly) mbgl::FeatureIdentifier mgl_featureIdentifier;
|
||||
@property (nonatomic, readonly) std::vector<mbgl::FeatureIdentifier> mgl_aggregateFeatureIdentifier;
|
||||
|
||||
/**
|
||||
Returns a copy of the receiver with tokens replaced by key path expressions.
|
||||
*/
|
||||
- (NSExpression *)mgl_expressionByReplacingTokensWithKeyPaths;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSNull (MGLExpressionAdditions)
|
||||
@@ -74,6 +79,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (NSExpression *)mgl_expressionWithContext:(NSDictionary<NSString *, NSExpression *> *)context;
|
||||
|
||||
|
||||
- (id)mgl_has:(id)element;
|
||||
|
||||
@end
|
||||
|
||||
@@ -866,4 +866,144 @@ using namespace std::string_literals;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Localization tests
|
||||
|
||||
- (void)testTokenReplacement {
|
||||
{
|
||||
NSExpression *original = MGLConstantExpression(@"");
|
||||
NSExpression *expected = original;
|
||||
XCTAssertEqualObjects(original.mgl_expressionByReplacingTokensWithKeyPaths, expected);
|
||||
}
|
||||
{
|
||||
NSExpression *original = MGLConstantExpression(@"{");
|
||||
NSExpression *expected = original;
|
||||
XCTAssertEqualObjects(original.mgl_expressionByReplacingTokensWithKeyPaths, expected);
|
||||
}
|
||||
{
|
||||
NSExpression *original = MGLConstantExpression(@"{token");
|
||||
NSExpression *expected = original;
|
||||
XCTAssertEqualObjects(original.mgl_expressionByReplacingTokensWithKeyPaths, expected);
|
||||
}
|
||||
{
|
||||
NSExpression *original = MGLConstantExpression(@"{token}");
|
||||
NSExpression *expected = [NSExpression expressionForKeyPath:@"token"];
|
||||
XCTAssertEqualObjects(original.mgl_expressionByReplacingTokensWithKeyPaths, expected);
|
||||
}
|
||||
{
|
||||
NSExpression *original = MGLConstantExpression(@"{token} {token}");
|
||||
NSExpression *expected = [NSExpression expressionWithFormat:@"mgl_join({token, ' ', token})"];
|
||||
XCTAssertEqualObjects(original.mgl_expressionByReplacingTokensWithKeyPaths, expected);
|
||||
}
|
||||
{
|
||||
NSExpression *original = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, '{short}', %@)", @{
|
||||
@1: MGLConstantExpression(@"{short}"),
|
||||
@2: @"…",
|
||||
@3: @"{long}",
|
||||
}];
|
||||
NSExpression *expected = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, short, %@)", @{
|
||||
@1: [NSExpression expressionForKeyPath:@"short"],
|
||||
@2: @"…",
|
||||
@3: [NSExpression expressionForKeyPath:@"long"],
|
||||
}];
|
||||
XCTAssertEqualObjects(original.mgl_expressionByReplacingTokensWithKeyPaths, expected);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testLocalization {
|
||||
{
|
||||
NSExpression *original = MGLConstantExpression(@"");
|
||||
NSExpression *expected = original;
|
||||
XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected);
|
||||
}
|
||||
{
|
||||
NSExpression *original = MGLConstantExpression(@"Old MacDonald");
|
||||
NSExpression *expected = original;
|
||||
XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected);
|
||||
}
|
||||
{
|
||||
NSExpression *original = MGLConstantExpression(@"{name_en}");
|
||||
NSExpression *expected = original;
|
||||
XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected);
|
||||
}
|
||||
{
|
||||
NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"];
|
||||
NSExpression *expected = original;
|
||||
XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected);
|
||||
}
|
||||
{
|
||||
NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"];
|
||||
NSExpression *expected = [NSExpression expressionForKeyPath:@"name"];
|
||||
XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"mul"]], expected);
|
||||
}
|
||||
{
|
||||
NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"];
|
||||
NSExpression *expected = [NSExpression expressionForKeyPath:@"name_fr"];
|
||||
XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"fr-CA"]], expected);
|
||||
}
|
||||
{
|
||||
NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"];
|
||||
NSExpression *expected = [NSExpression expressionForKeyPath:@"name_zh-Hans"];
|
||||
XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"zh-Hans"]], expected);
|
||||
}
|
||||
{
|
||||
NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"];
|
||||
NSExpression *expected = [NSExpression expressionForKeyPath:@"name"];
|
||||
XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"tlh"]], expected);
|
||||
}
|
||||
{
|
||||
NSExpression *original = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, short, %@)", @{
|
||||
@1: [NSExpression expressionForKeyPath:@"abbr"],
|
||||
@2: @"…",
|
||||
@3: [NSExpression expressionForKeyPath:@"name_fr"],
|
||||
}];
|
||||
NSExpression *expected = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, short, %@)", @{
|
||||
@1: [NSExpression expressionForKeyPath:@"abbr"],
|
||||
@2: @"…",
|
||||
@3: [NSExpression expressionForKeyPath:@"name_es"],
|
||||
}];
|
||||
XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"es-PR"]], expected);
|
||||
}
|
||||
{
|
||||
NSArray *jsonExpression = @[
|
||||
@"step",
|
||||
@[@"zoom"],
|
||||
@[
|
||||
@"case",
|
||||
@[
|
||||
@"<",
|
||||
@[
|
||||
@"to-number",
|
||||
@[@"get", @"area"]
|
||||
],
|
||||
@80000
|
||||
],
|
||||
@[@"get", @"abbr"],
|
||||
@[@"get", @"name_en"]
|
||||
],
|
||||
@5, @[@"get", @"name_en"]
|
||||
];
|
||||
NSArray *localizedJSONExpression = @[
|
||||
@"step",
|
||||
@[@"zoom"],
|
||||
@[
|
||||
@"case",
|
||||
@[
|
||||
@"<",
|
||||
@[
|
||||
@"to-number",
|
||||
@[@"get", @"area"]
|
||||
],
|
||||
@80000
|
||||
],
|
||||
@[@"get", @"abbr"],
|
||||
@[@"get", @"name"]
|
||||
],
|
||||
@5, @[@"get", @"name"]
|
||||
];
|
||||
NSExpression *expression = [NSExpression expressionWithMGLJSONObject:jsonExpression];
|
||||
NSExpression *localizedExpression = [expression mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"mul"]];
|
||||
XCTAssertEqualObjects(localizedExpression.mgl_jsonExpressionObject, localizedJSONExpression);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#import <Mapbox/Mapbox.h>
|
||||
|
||||
#import "NSBundle+MGLAdditions.h"
|
||||
#import "MGLVectorTileSource_Private.h"
|
||||
|
||||
#import <mbgl/util/default_styles.hpp>
|
||||
|
||||
@@ -419,4 +420,45 @@
|
||||
XCTAssertEqualObjects(layers[startIndex++].identifier, layer4.identifier);
|
||||
}
|
||||
|
||||
#pragma mark Localization tests
|
||||
|
||||
- (void)testLanguageMatching {
|
||||
{
|
||||
NSArray *preferences = @[@"en"];
|
||||
XCTAssertEqualObjects([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
|
||||
}
|
||||
{
|
||||
NSArray *preferences = @[@"en-US"];
|
||||
XCTAssertEqualObjects([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
|
||||
}
|
||||
{
|
||||
NSArray *preferences = @[@"fr"];
|
||||
XCTAssertEqualObjects([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"fr");
|
||||
}
|
||||
{
|
||||
NSArray *preferences = @[@"zh-Hans"];
|
||||
XCTAssertEqualObjects([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans");
|
||||
}
|
||||
{
|
||||
NSArray *preferences = @[@"zh-Hans", @"en"];
|
||||
XCTAssertEqualObjects([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans");
|
||||
}
|
||||
{
|
||||
NSArray *preferences = @[@"zh-Hant"];
|
||||
XCTAssertNil([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]);
|
||||
}
|
||||
{
|
||||
NSArray *preferences = @[@"tlh"];
|
||||
XCTAssertNil([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]);
|
||||
}
|
||||
{
|
||||
NSArray *preferences = @[@"tlh", @"en"];
|
||||
XCTAssertEqualObjects([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
|
||||
}
|
||||
{
|
||||
NSArray *preferences = @[@"mul"];
|
||||
XCTAssertNil([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,16 +12,16 @@ The 4.0._x_ series of releases will be the last to support iOS 8. The minimum iO
|
||||
* Added Danish and Hebrew localizations. ([#10967](https://github.com/mapbox/mapbox-gl-native/pull/10967), [#11136](https://github.com/mapbox/mapbox-gl-native/pull/11134))
|
||||
* Removed methods, properties, and constants that had been deprecated as of v3.7.4. ([#11205](https://github.com/mapbox/mapbox-gl-native/pull/11205))
|
||||
|
||||
### Styles and rendering
|
||||
### Styles
|
||||
|
||||
* Added support for a new layer type: `MGLHeatmapStyleLayer`, a powerful way to visualize point data distributions using heatmaps, fully customizable through runtime styling. [#11046](https://github.com/mapbox/mapbox-gl-native/pull/11046)
|
||||
* The layout and paint properties on subclasses of `MGLStyleLayer` are now of type `NSExpression` instead of `MGLStyleValue`. A new “Predicates and Expressions” guide provides an overview of the supported operators. ([#10726](https://github.com/mapbox/mapbox-gl-native/pull/10726))
|
||||
* Renamed `MGLRasterSource` to `MGLRasterTileSource` and `MGLVectorSource` to `MGLVectorTileSource`. ([#11568](https://github.com/mapbox/mapbox-gl-native/pull/11568))
|
||||
* Added an `MGLComputedShapeSource` class that allows applications to supply vector data to a style layer on a per-tile basis. ([#9983](https://github.com/mapbox/mapbox-gl-native/pull/9983))
|
||||
* A style can now display smooth hillshading and customize its appearance at runtime using the `MGLHillshadeStyleLayer` class. Hillshading is based on a rasterized digital elevation model supplied by the `MGLRasterDEMSource` class. ([#10642](https://github.com/mapbox/mapbox-gl-native/pull/10642))
|
||||
* Replaced the `MGLStyle.localizesLabels` property with an `-[MGLStyle localizeLabelsIntoLocale:]` method that allows you to specify the language to localize into. Also added an `-[NSExpression(MGLAdditions) mgl_expressionLocalizedIntoLocale:]` method for localizing an individual value used with `MGLSymbolStyleLayer.text`. ([#11651](https://github.com/mapbox/mapbox-gl-native/pull/11651))
|
||||
* The `MGLSymbolStyleLayer.textFontNames` property can now depend on a feature’s attributes. ([#10850](https://github.com/mapbox/mapbox-gl-native/pull/10850))
|
||||
* Added `MGLShapeSourceOptionWrapsCoordinates`, to specify whether the shape of an `MGLComputedShapeSource` should be wrapped to accommodate coordinates with longitudes beyond −180 and 180; and `MGLShapeSourceOptionClipsCoordinates`, to specify whether the shape of an `MGLComputedShapeSource` should be clipped at the edge of each tile. ([#11041](https://github.com/mapbox/mapbox-gl-native/pull/11041))
|
||||
* Added support for Mapzen Terrarium DEM encoding. ([#11274](https://github.com/mapbox/mapbox-gl-native/pull/11274))
|
||||
* The layer filtering predicate's format strings now can contain arithmetic and calls to built-in `NSExpression` functions. ([#11587](https://github.com/mapbox/mapbox-gl-native/pull/11587))
|
||||
* The layer filtering predicate's key paths now may need to be cast to `NSString` or `NSNumber`. ([#11587](https://github.com/mapbox/mapbox-gl-native/pull/11587))
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
MBXSettingsMiscellaneousShowZoomLevel,
|
||||
MBXSettingsMiscellaneousScrollView,
|
||||
MBXSettingsMiscellaneousToggleTwoMaps,
|
||||
MBXSettingsMiscellaneousCountryLabels,
|
||||
MBXSettingsMiscellaneousLocalizeLabels,
|
||||
MBXSettingsMiscellaneousShowSnapshots,
|
||||
MBXSettingsMiscellaneousShouldLimitCameraChanges,
|
||||
MBXSettingsMiscellaneousPrintLogFile,
|
||||
@@ -130,7 +130,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
@property (nonatomic) NSInteger styleIndex;
|
||||
@property (nonatomic) BOOL debugLoggingEnabled;
|
||||
@property (nonatomic) BOOL customUserLocationAnnnotationEnabled;
|
||||
@property (nonatomic) BOOL usingLocaleBasedCountryLabels;
|
||||
@property (nonatomic, getter=isLocalizingLabels) BOOL localizingLabels;
|
||||
@property (nonatomic) BOOL reuseQueueStatsEnabled;
|
||||
@property (nonatomic) BOOL showZoomLevelEnabled;
|
||||
@property (nonatomic) BOOL shouldLimitCameraChanges;
|
||||
@@ -139,7 +139,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
|
||||
@interface MGLMapView (MBXViewController)
|
||||
|
||||
@property (nonatomic) BOOL usingLocaleBasedCountryLabels;
|
||||
@property (nonatomic) NSDictionary *annotationViewReuseQueueByIdentifier;
|
||||
|
||||
@end
|
||||
@@ -383,7 +382,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
[NSString stringWithFormat:@"%@ Zoom/Pitch/Direction Label", (_showZoomLevelEnabled ? @"Hide" :@"Show")],
|
||||
@"Embedded Map View",
|
||||
[NSString stringWithFormat:@"%@ Second Map", ([self.view viewWithTag:2] == nil ? @"Show" : @"Hide")],
|
||||
[NSString stringWithFormat:@"Show Labels in %@", (_usingLocaleBasedCountryLabels ? @"Default Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])],
|
||||
[NSString stringWithFormat:@"Show Labels in %@", (_localizingLabels ? @"Default Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])],
|
||||
@"Show Snapshots",
|
||||
[NSString stringWithFormat:@"%@ Camera Changes", (_shouldLimitCameraChanges ? @"Unlimit" : @"Limit")],
|
||||
]];
|
||||
@@ -574,7 +573,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
case MBXSettingsMiscellaneous:
|
||||
switch (indexPath.row)
|
||||
{
|
||||
case MBXSettingsMiscellaneousCountryLabels:
|
||||
case MBXSettingsMiscellaneousLocalizeLabels:
|
||||
[self styleCountryLabelsLanguage];
|
||||
break;
|
||||
case MBXSettingsMiscellaneousWorldTour:
|
||||
@@ -1400,8 +1399,8 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
|
||||
-(void)styleCountryLabelsLanguage
|
||||
{
|
||||
_usingLocaleBasedCountryLabels = !_usingLocaleBasedCountryLabels;
|
||||
self.mapView.style.localizesLabels = _usingLocaleBasedCountryLabels;
|
||||
_localizingLabels = !_localizingLabels;
|
||||
[self.mapView.style localizeLabelsIntoLocale:_localizingLabels ? [NSLocale localeWithLocaleIdentifier:@"mul"] : nil];
|
||||
}
|
||||
|
||||
- (void)styleRouteLine
|
||||
@@ -1984,7 +1983,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
// Default Mapbox styles use {name_en} as their label language, which means
|
||||
// that a device with an English-language locale is already effectively
|
||||
// using locale-based country labels.
|
||||
_usingLocaleBasedCountryLabels = [[self bestLanguageForUser] isEqualToString:@"en"];
|
||||
_localizingLabels = [[self bestLanguageForUser] isEqualToString:@"en"];
|
||||
}
|
||||
|
||||
- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
* Renamed `MGLRasterSource` to `MGLRasterTileSource` and `MGLVectorSource` to `MGLVectorTileSource`. ([#11568](https://github.com/mapbox/mapbox-gl-native/pull/11568))
|
||||
* Added an `MGLComputedShapeSource` class that allows applications to supply vector data to a style layer on a per-tile basis. ([#9983](https://github.com/mapbox/mapbox-gl-native/pull/9983))
|
||||
* A style can now display smooth hillshading and customize its appearance at runtime using the `MGLHillshadeStyleLayer` class. Hillshading is based on a rasterized digital elevation model supplied by the `MGLRasterDEMSource` class. ([#10642](https://github.com/mapbox/mapbox-gl-native/pull/10642))
|
||||
* Replaced the `MGLStyle.localizesLabels` property with an `-[MGLStyle localizeLabelsIntoLocale:]` method that allows you to specify the language to localize into. Note that this method does not automatically update the style when the system’s preferred language changes. Also added an `-[NSExpression(MGLAdditions) mgl_expressionLocalizedIntoLocale:]` method for localizing an individual value used with `MGLSymbolStyleLayer.text`. ([#11651](https://github.com/mapbox/mapbox-gl-native/pull/11651))
|
||||
* Fixed incorrect color calibration on macOS 10.13 High Sierra when using color-related methods of `MGLStyleLayer` subclasses, as well as when displaying an `MGLAttributionInfo`. It is no longer necessary to explicitly convert an `NSColor` to the sRGB color space before using these classes on High Sierra. ([#11391](https://github.com/mapbox/mapbox-gl-native/pull/11391))
|
||||
* The `MGLSymbolStyleLayer.textFontNames` property can now depend on a feature’s attributes. ([#10850](https://github.com/mapbox/mapbox-gl-native/pull/10850))
|
||||
* Properties such as `MGLSymbolStyleLayer.iconAllowsOverlap` and `MGLSymbolStyleLayer.iconIgnoresPlacement` now account for symbols in other sources. ([#10436](https://github.com/mapbox/mapbox-gl-native/pull/10436))
|
||||
|
||||
@@ -421,7 +421,7 @@ NS_ARRAY_OF(id <MGLAnnotation>) *MBXFlattenedShapes(NS_ARRAY_OF(id <MGLAnnotatio
|
||||
}
|
||||
|
||||
- (void)updateLabels {
|
||||
self.mapView.style.localizesLabels = _isLocalizingLabels;
|
||||
[self.mapView.style localizeLabelsIntoLocale:_isLocalizingLabels ? nil : [NSLocale localeWithLocaleIdentifier:@"mul"]];
|
||||
}
|
||||
|
||||
- (void)applyPendingState {
|
||||
@@ -1067,7 +1067,7 @@ NS_ARRAY_OF(id <MGLAnnotation>) *MBXFlattenedShapes(NS_ARRAY_OF(id <MGLAnnotatio
|
||||
menuItem.state = menuItem.tag == _isLocalizingLabels ? NSOnState: NSOffState;
|
||||
if (menuItem.tag) {
|
||||
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:[NSBundle mainBundle].developmentLocalization];
|
||||
NSString *preferredLanguage = [MGLVectorTileSource preferredMapboxStreetsLanguage];
|
||||
NSString *preferredLanguage = [MGLVectorTileSource preferredMapboxStreetsLanguage] ?: @"en";
|
||||
menuItem.title = [locale displayNameForKey:NSLocaleIdentifier value:preferredLanguage];
|
||||
}
|
||||
return YES;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
language = "en"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
@@ -56,7 +56,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
Reference in New Issue
Block a user