[ios] Fix and expand accessibility feature transliteration

- Fixed pre-iOS 11 compatibility.
- Expanded transliteration to all supported languages.
This commit is contained in:
Jason Wray
2018-01-09 16:09:41 -05:00
parent c9da6e4959
commit 0806e16eb3
10 changed files with 168 additions and 17 deletions

View File

@@ -6,6 +6,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly, getter=isMapboxStreets) BOOL mapboxStreets;
+ (NS_SET_OF(NSString *) *)mapboxStreetsLanguages;
+ (NSString *)preferredMapboxStreetsLanguage;
- (NS_DICTIONARY_OF(NSString *, NSString *) *)localizedKeysByKeyForPreferredLanguage:(nullable NSString *)preferredLanguage;

View File

@@ -20,6 +20,20 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (NSString *)mgl_titleCasedStringWithLocale:(NSLocale *)locale;
/**
Returns a transliterated representation of the receiver using the specified
script. If transliteration fails, the receiver will be returned.
Only supports scripts for languages used by Mapbox Streets.
On iOS 8 or older, this will method will always return the untransliterated
receiver.
@param script The four-letter code representing the name of the script, as
specified by ISO 15924.
*/
- (NSString *)mgl_stringByTransliteratingIntoScript:(NSString *)script;
@end
@interface NSAttributedString (MGLAdditions)

View File

@@ -41,6 +41,25 @@
return string;
}
- (NSString *)mgl_stringByTransliteratingIntoScript:(NSString *)script {
if (@available(iOS 9.0, *)) {
NSMutableString *string = self.mutableCopy;
NSStringTransform transform;
if ([script isEqualToString:@"Latn"]) {
transform = NSStringTransformToLatin;
} else if ([script isEqualToString:@"Hans"]) {
// No transform available.
} else if ([script isEqualToString:@"Cyrl"]) {
transform = @"Any-Latin; Latin-Cyrillic";
} else if ([script isEqualToString:@"Arab"]) {
transform = @"Any-Latin; Latin-Arabic";
}
return transform ? [string stringByApplyingTransform:transform reverse:NO] : string;
} else {
return self;
}
}
@end
@implementation NSAttributedString (MGLAdditions)

View File

@@ -39,4 +39,38 @@
XCTAssertEqualObjects([@"Improve This iPhone" mgl_titleCasedStringWithLocale:locale], @"Improve This iPhone");
}
- (void)testTransliteratedString {
if (@available(iOS 9.0, *)) {
XCTAssertEqualObjects([@"Portland" mgl_stringByTransliteratingIntoScript:@"Latn"], @"Portland");
XCTAssertEqualObjects([@"Portland" mgl_stringByTransliteratingIntoScript:@"Hans"], @"Portland");
XCTAssertEqualObjects([@"Portland" mgl_stringByTransliteratingIntoScript:@"Cyrl"], @"Портланд");
XCTAssertEqualObjects([@"Portland" mgl_stringByTransliteratingIntoScript:@"Arab"], @"پُرتلَند");
XCTAssertEqualObjects([@"Portland" mgl_stringByTransliteratingIntoScript:@"Fake"], @"Portland");
XCTAssertEqualObjects([@"北京" mgl_stringByTransliteratingIntoScript:@"Latn"], @"běi jīng");
XCTAssertEqualObjects([@"北京" mgl_stringByTransliteratingIntoScript:@"Hans"], @"北京");
XCTAssertEqualObjects([@"北京" mgl_stringByTransliteratingIntoScript:@"Cyrl"], @"бе̌и йӣнг");
XCTAssertEqualObjects([@"北京" mgl_stringByTransliteratingIntoScript:@"Arab"], @"بِِ̌ جِينگ");
XCTAssertEqualObjects([@"北京" mgl_stringByTransliteratingIntoScript:@"Fake"], @"北京");
XCTAssertEqualObjects([@"Mосква" mgl_stringByTransliteratingIntoScript:@"Latn"], @"Moskva");
XCTAssertEqualObjects([@"Mосква" mgl_stringByTransliteratingIntoScript:@"Hans"], @"Mосква");
XCTAssertEqualObjects([@"Mосква" mgl_stringByTransliteratingIntoScript:@"Cyrl"], @"Москва");
XCTAssertEqualObjects([@"Mосква" mgl_stringByTransliteratingIntoScript:@"Arab"], @"مُسكڤَ");
XCTAssertEqualObjects([@"Mосква" mgl_stringByTransliteratingIntoScript:@"Fake"], @"Mосква");
XCTAssertEqualObjects([@"ロンドン" mgl_stringByTransliteratingIntoScript:@"Latn"], @"rondon");
XCTAssertEqualObjects([@"ロンドン" mgl_stringByTransliteratingIntoScript:@"Hans"], @"ロンドン");
XCTAssertEqualObjects([@"ロンドン" mgl_stringByTransliteratingIntoScript:@"Cyrl"], @"рондон");
XCTAssertEqualObjects([@"ロンドン" mgl_stringByTransliteratingIntoScript:@"Arab"], @"رُندُن");
XCTAssertEqualObjects([@"ロンドン" mgl_stringByTransliteratingIntoScript:@"Fake"], @"ロンドン");
} else {
XCTAssertEqualObjects([@"Made-up Place" mgl_stringByTransliteratingIntoScript:@"Latn"], @"Made-up Place");
XCTAssertEqualObjects([@"Made-up Place" mgl_stringByTransliteratingIntoScript:@"Hans"], @"Made-up Place");
XCTAssertEqualObjects([@"Made-up Place" mgl_stringByTransliteratingIntoScript:@"Cyrl"], @"Made-up Place");
XCTAssertEqualObjects([@"Made-up Place" mgl_stringByTransliteratingIntoScript:@"Arab"], @"Made-up Place");
XCTAssertEqualObjects([@"Made-up Place" mgl_stringByTransliteratingIntoScript:@"Fake"], @"Made-up Place");
}
}
@end

View File

@@ -227,6 +227,11 @@
927FBD021F4DB05500F8BF1F /* MGLMapSnapshotter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 927FBCFE1F4DB05500F8BF1F /* MGLMapSnapshotter.mm */; };
929EFFAB1F56DCD4003A77D5 /* MGLAnnotationView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4018B1C41CDC277F00F666AF /* MGLAnnotationView.mm */; };
92F2C3ED1F0E3C3A00268EC0 /* MGLRendererFrontend.h in Headers */ = {isa = PBXBuildFile; fileRef = 92F2C3EC1F0E3C3A00268EC0 /* MGLRendererFrontend.h */; };
96036A01200565C700510F3D /* NSOrthography+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 960369FF200565C700510F3D /* NSOrthography+MGLAdditions.h */; };
96036A02200565C700510F3D /* NSOrthography+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 960369FF200565C700510F3D /* NSOrthography+MGLAdditions.h */; };
96036A03200565C700510F3D /* NSOrthography+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 96036A00200565C700510F3D /* NSOrthography+MGLAdditions.m */; };
96036A04200565C700510F3D /* NSOrthography+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 96036A00200565C700510F3D /* NSOrthography+MGLAdditions.m */; };
96036A0620059BBA00510F3D /* MGLNSOrthographyAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 96036A0520059BBA00510F3D /* MGLNSOrthographyAdditionsTests.m */; };
960D0C361ECF5AAF008E151F /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 960D0C351ECF5AAF008E151F /* Images.xcassets */; };
960D0C371ECF5AAF008E151F /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 960D0C351ECF5AAF008E151F /* Images.xcassets */; };
9620BB381E69FE1700705A1D /* MGLSDKUpdateChecker.h in Headers */ = {isa = PBXBuildFile; fileRef = 9620BB361E69FE1700705A1D /* MGLSDKUpdateChecker.h */; };
@@ -764,6 +769,9 @@
927FBCFD1F4DB05500F8BF1F /* MGLMapSnapshotter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapSnapshotter.h; sourceTree = "<group>"; };
927FBCFE1F4DB05500F8BF1F /* MGLMapSnapshotter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapSnapshotter.mm; sourceTree = "<group>"; };
92F2C3EC1F0E3C3A00268EC0 /* MGLRendererFrontend.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLRendererFrontend.h; sourceTree = "<group>"; };
960369FF200565C700510F3D /* NSOrthography+MGLAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSOrthography+MGLAdditions.h"; sourceTree = "<group>"; };
96036A00200565C700510F3D /* NSOrthography+MGLAdditions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSOrthography+MGLAdditions.m"; sourceTree = "<group>"; };
96036A0520059BBA00510F3D /* MGLNSOrthographyAdditionsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLNSOrthographyAdditionsTests.m; sourceTree = "<group>"; };
960D0C351ECF5AAF008E151F /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
9620BB361E69FE1700705A1D /* MGLSDKUpdateChecker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLSDKUpdateChecker.h; sourceTree = "<group>"; };
9620BB371E69FE1700705A1D /* MGLSDKUpdateChecker.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = MGLSDKUpdateChecker.mm; sourceTree = "<group>"; };
@@ -1231,6 +1239,8 @@
children = (
357FE2DB1E02D2B20068B753 /* NSCoder+MGLAdditions.h */,
357FE2DC1E02D2B20068B753 /* NSCoder+MGLAdditions.mm */,
960369FF200565C700510F3D /* NSOrthography+MGLAdditions.h */,
96036A00200565C700510F3D /* NSOrthography+MGLAdditions.m */,
35CE61801D4165D9004F2359 /* UIColor+MGLAdditions.h */,
35CE61811D4165D9004F2359 /* UIColor+MGLAdditions.mm */,
30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */,
@@ -1413,6 +1423,7 @@
35E208A61D24210F00EC9A46 /* MGLNSDataAdditionsTests.m */,
1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */,
DAE7DEC11E245455007505A6 /* MGLNSStringAdditionsTests.m */,
96036A0520059BBA00510F3D /* MGLNSOrthographyAdditionsTests.m */,
DA2E885D1CC0382C00F24E7B /* MGLOfflinePackTests.m */,
DA2E885E1CC0382C00F24E7B /* MGLOfflineRegionTests.m */,
55E2AD121E5B125400E8C587 /* MGLOfflineStorageTests.mm */,
@@ -1871,6 +1882,7 @@
353933F21D3FB753003F57D7 /* MGLCircleStyleLayer.h in Headers */,
DA8847F31CBAFA5100AB86E3 /* MGLMultiPoint.h in Headers */,
30E578171DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */,
96036A01200565C700510F3D /* NSOrthography+MGLAdditions.h in Headers */,
1F7454961ECD450D00021D39 /* MGLLight_Private.h in Headers */,
DAD1656C1CF41981001FF4B9 /* MGLFeature.h in Headers */,
40EDA1C01CFE0E0200D9EA68 /* MGLAnnotationContainerView.h in Headers */,
@@ -1925,6 +1937,7 @@
DABFB8641CBE99E500D62B32 /* MGLOfflineStorage.h in Headers */,
96E516E32000552A00A02306 /* MGLAccountManager_Private.h in Headers */,
96E5170420005A6B00A02306 /* SMCalloutView.h in Headers */,
96036A02200565C700510F3D /* NSOrthography+MGLAdditions.h in Headers */,
DAD165791CF4CDFF001FF4B9 /* MGLShapeCollection.h in Headers */,
4049C29E1DB6CD6C00B3F799 /* MGLPointCollection.h in Headers */,
3566C7671D4A77BA008152BC /* MGLShapeSource.h in Headers */,
@@ -2378,6 +2391,7 @@
40CFA6511D7875BB008103BD /* MGLShapeSourceTests.mm in Sources */,
DA35A2C51CCA9F8300E826B2 /* MGLClockDirectionFormatterTests.m in Sources */,
35B8E08C1D6C8B5100E768D2 /* MGLPredicateTests.mm in Sources */,
96036A0620059BBA00510F3D /* MGLNSOrthographyAdditionsTests.m in Sources */,
1F95931D1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm in Sources */,
DD58A4C61D822BD000E1F038 /* MGLExpressionTests.mm in Sources */,
3575798B1D502B0C000B822E /* MGLBackgroundStyleLayerTests.mm in Sources */,
@@ -2420,6 +2434,7 @@
DA6408DD1DA4E7D300908C90 /* MGLVectorStyleLayer.m in Sources */,
3566C7681D4A77BA008152BC /* MGLShapeSource.mm in Sources */,
400533021DB0862B0069F638 /* NSArray+MGLAdditions.mm in Sources */,
96036A03200565C700510F3D /* NSOrthography+MGLAdditions.m in Sources */,
35136D421D42274500C20EFD /* MGLRasterStyleLayer.mm in Sources */,
3538AA1F1D542239008EC33D /* MGLForegroundStyleLayer.mm in Sources */,
DA00FC901D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */,
@@ -2508,6 +2523,7 @@
3566C7691D4A77BA008152BC /* MGLShapeSource.mm in Sources */,
400533031DB086490069F638 /* NSArray+MGLAdditions.mm in Sources */,
35136D431D42274500C20EFD /* MGLRasterStyleLayer.mm in Sources */,
96036A04200565C700510F3D /* NSOrthography+MGLAdditions.m in Sources */,
3538AA201D542239008EC33D /* MGLForegroundStyleLayer.mm in Sources */,
DA00FC911D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */,
DAA4E4201CBB730400178DFB /* MGLOfflinePack.mm in Sources */,

View File

@@ -7,6 +7,8 @@
#import "MGLVectorSource_Private.h"
#import "NSBundle+MGLAdditions.h"
#import "NSOrthography+MGLAdditions.h"
#import "NSString+MGLAdditions.h"
@implementation MGLMapAccessibilityElement
@@ -49,24 +51,16 @@
NSString *languageCode = [MGLVectorSource preferredMapboxStreetsLanguage];
NSString *nameAttribute = [NSString stringWithFormat:@"name_%@", languageCode];
NSString *name = [feature attributeForKey:nameAttribute];
// If a feature hasn’t been translated into the preferred language, it
// may be in the local language, which may be written in another script.
// Romanize it.
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:languageCode];
NSOrthography *orthography;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
if ([NSOrthography respondsToSelector:@selector(defaultOrthographyForLanguage:)]) {
orthography = [NSOrthography defaultOrthographyForLanguage:locale.localeIdentifier];
// Attempt to transform to the script of the preferred language, keeping
// the original string if no transform exists or if transformation fails.
if (@available(iOS 9.0, *)) {
NSString *dominantScript = [NSOrthography mgl_dominantScriptForMapboxStreetsLanguage:languageCode];
name = [name mgl_stringByTransliteratingIntoScript:dominantScript];
}
#pragma clang diagnostic pop
#endif
if ([orthography.dominantScript isEqualToString:@"Latn"]) {
name = [name stringByApplyingTransform:NSStringTransformToLatin reverse:NO];
}
self.accessibilityLabel = name;
}
return self;

View File

@@ -0,0 +1,18 @@
#import <Foundation/Foundation.h>
@interface NSOrthography (NSOrthography_MGLAdditions)
/**
Returns a four-letter ISO 15924 code representing the name of the dominant
script for a given language.
On iOS 11 or newer, this method wraps
`+[NSOrthography defaultOrthographyForLanguage:]` and supports any language.
On iOS 10 and older, this method only returns values for Mapbox
Streets-supported languages.
@param language The ISO-639 code representing a language.
*/
+ (NSString *)mgl_dominantScriptForMapboxStreetsLanguage:(NSString *)language;
@end

View File

@@ -0,0 +1,31 @@
#import "NSOrthography+MGLAdditions.h"
@implementation NSOrthography (MGLAdditions)
+ (NSString *)mgl_dominantScriptForMapboxStreetsLanguage:(NSString *)language {
if (@available(iOS 11.0, *)) {
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:language];
NSOrthography *orthography = [NSOrthography defaultOrthographyForLanguage:locale.localeIdentifier];
return orthography.dominantScript;
}
// Manually map Mapbox Streets languages to ISO 15924 script codes.
NSSet *latinLanguages = [NSSet setWithObjects:@"de", @"en", @"es", @"fr", @"pt", nil];
NSSet *hansLanguages = [NSSet setWithObjects:@"zh", @"zh-Hans", nil];
if ([latinLanguages containsObject:language]) {
return @"Latn";
} else if ([hansLanguages containsObject:language]) {
return @"Hans";
} else if ([language isEqualToString:@"ru"]) {
return @"Cyrl";
} else if ([language isEqualToString:@"ar"]) {
return @"Arab";
} else {
// Code for undetermined script
return @"Zyyy";
}
}
@end

View File

@@ -19,13 +19,17 @@
};
MGLFeatureAccessibilityElement *element = [[MGLFeatureAccessibilityElement alloc] initWithAccessibilityContainer:self feature:feature];
XCTAssertEqualObjects(element.accessibilityLabel, @"English", @"Accessibility label should be localized.");
feature.attributes = @{
@"name": @"Цинциннати",
@"name_en": @"Цинциннати",
};
element = [[MGLFeatureAccessibilityElement alloc] initWithAccessibilityContainer:self feature:feature];
XCTAssertEqualObjects(element.accessibilityLabel, @"Cincinnati", @"Accessibility label should be romanized.");
if (@available(iOS 9.0, *)) {
XCTAssertEqualObjects(element.accessibilityLabel, @"Cincinnati", @"Accessibility label should be romanized.");
} else {
XCTAssertEqualObjects(element.accessibilityLabel, @"Цинциннати", @"Accessibility label should not be romanized.");
}
}
- (void)testPlaceFeatureValues {

View File

@@ -0,0 +1,19 @@
#import <XCTest/XCTest.h>
#import "NSOrthography+MGLAdditions.h"
#import "MGLVectorSource_Private.h"
@interface MGLNSOrthographyAdditionsTests : XCTestCase
@end
@implementation MGLNSOrthographyAdditionsTests
- (void)testStreetsLanguages {
for (NSString *language in [MGLVectorSource mapboxStreetsLanguages]) {
NSString *dominantScript = [NSOrthography mgl_dominantScriptForMapboxStreetsLanguage:language];
XCTAssertNotEqualObjects(dominantScript, @"Zyyy", @"Mapbox Streets languages should have dominant script");
}
}
@end