From aebf54aee41cc892198b055a7a546743297412bd Mon Sep 17 00:00:00 2001 From: Sharon Gong Date: Thu, 20 Feb 2020 13:03:16 -0800 Subject: [PATCH] Add localized accessibility strings to React Core pod (#27995) Summary: The accessibility roles and states description strings are not able to be localized on iOS platform. Those strings are exposed to the end users so it should be localized. This PR is to add localized strings as a resource bundle to the React Core Pod so that any React Native app integrating the React Native dependencies using CocoaPods can get the localized accessibility roles and states description. ## Changelog [iOS] [Added] - Add localized accessibility strings to React Core pod Pull Request resolved: https://github.com/facebook/react-native/pull/27995 Test Plan: Verified with RNTester app. Differential Revision: D19975587 Pulled By: PeteTheHeat fbshipit-source-id: f8eb4e25194f0cd603c98a6221ec87503a2826ed --- React-Core.podspec | 1 + .../en.lproj/Localizable.strings | 26 ++++++ React/Views/RCTView.m | 89 +++++++++++-------- 3 files changed, 77 insertions(+), 39 deletions(-) create mode 100644 React/AccessibilityResources/en.lproj/Localizable.strings diff --git a/React-Core.podspec b/React-Core.podspec index bc4dfb468abdaf..b85e969e3493a7 100644 --- a/React-Core.podspec +++ b/React-Core.podspec @@ -44,6 +44,7 @@ Pod::Spec.new do |s| s.author = "Facebook, Inc. and its affiliates" s.platforms = { :ios => "10.0", :tvos => "10.0" } s.source = source + s.resource_bundle = { "AccessibilityResources" => ["React/AccessibilityResources/*.lproj"]} s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags s.header_dir = "React" s.framework = "JavaScriptCore" diff --git a/React/AccessibilityResources/en.lproj/Localizable.strings b/React/AccessibilityResources/en.lproj/Localizable.strings new file mode 100644 index 00000000000000..866bd0d883862d --- /dev/null +++ b/React/AccessibilityResources/en.lproj/Localizable.strings @@ -0,0 +1,26 @@ +/* + Localizable.strings + React +*/ +"alert"="alert"; +"checkbox"="checkbox"; +"combobox"="combo box"; +"menu"="menu"; +"menubar"="menu bar"; +"menuitem"="menu item"; +"progressbar"="progress bar"; +"radio"="radio button"; +"radiogroup"="radio group"; +"scrollbar"="scroll bar"; +"spinbutton"="spin button"; +"switch"="switch"; +"tab"="tab description"; +"tablist"="tab list"; +"timer"="timer"; +"toolbar"="tool bar"; +"checked"="checked"; +"unchecked"="not checked"; +"busy"="busy"; +"expanded"="expanded"; +"collapsed"="collapsed"; +"mixed"="mixed"; diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m index 831a1b52fc5011..6936549fa6e653 100644 --- a/React/Views/RCTView.m +++ b/React/Views/RCTView.m @@ -202,6 +202,51 @@ - (BOOL)didActivateAccessibilityCustomAction:(UIAccessibilityCustomAction *)acti - (NSString *)accessibilityValue { + static dispatch_once_t onceToken; + static NSDictionary *rolesAndStatesDescription = nil; + + dispatch_once(&onceToken, ^{ + NSString *bundlePath = [[NSBundle mainBundle]pathForResource:@"AccessibilityResources" ofType:@"bundle"]; + NSBundle *bundle = [NSBundle bundleWithPath:bundlePath]; + + if (bundle) { + NSURL *url = [bundle URLForResource:@"Localizable" withExtension:@"strings"]; + if (@available(iOS 11.0, *)) { + rolesAndStatesDescription = [NSDictionary dictionaryWithContentsOfURL:url error:nil]; + } else { + // Fallback on earlier versions + rolesAndStatesDescription = [NSDictionary dictionaryWithContentsOfURL:url]; + } + } + if (rolesAndStatesDescription == nil) { + NSLog(@"Cannot load localized accessibility strings."); + rolesAndStatesDescription = @{ + @"alert" : @"alert", + @"checkbox" : @"checkbox", + @"combobox" : @"combo box", + @"menu" : @"menu", + @"menubar" : @"menu bar", + @"menuitem" : @"menu item", + @"progressbar" : @"progress bar", + @"radio" : @"radio button", + @"radiogroup" : @"radio group", + @"scrollbar" : @"scroll bar", + @"spinbutton" : @"spin button", + @"switch" : @"switch", + @"tab" : @"tab", + @"tablist" : @"tab list", + @"timer" : @"timer", + @"toolbar" : @"tool bar", + @"checked" : @"checked", + @"unchecked" : @"not checked", + @"busy" : @"busy", + @"expanded" : @"expanded", + @"collapsed" : @"collapsed", + @"mixed": @"mixed", + }; + } + }); + if ((self.accessibilityTraits & SwitchAccessibilityTrait) == SwitchAccessibilityTrait) { for (NSString *state in self.accessibilityState) { id val = self.accessibilityState[state]; @@ -214,41 +259,7 @@ - (NSString *)accessibilityValue } } NSMutableArray *valueComponents = [NSMutableArray new]; - static NSDictionary *roleDescriptions = nil; - static dispatch_once_t onceToken1; - dispatch_once(&onceToken1, ^{ - roleDescriptions = @{ - @"alert" : @"alert", - @"checkbox" : @"checkbox", - @"combobox" : @"combo box", - @"menu" : @"menu", - @"menubar" : @"menu bar", - @"menuitem" : @"menu item", - @"progressbar" : @"progress bar", - @"radio" : @"radio button", - @"radiogroup" : @"radio group", - @"scrollbar" : @"scroll bar", - @"spinbutton" : @"spin button", - @"switch" : @"switch", - @"tab" : @"tab", - @"tablist" : @"tab list", - @"timer" : @"timer", - @"toolbar" : @"tool bar", - }; - }); - static NSDictionary *stateDescriptions = nil; - static dispatch_once_t onceToken2; - dispatch_once(&onceToken2, ^{ - stateDescriptions = @{ - @"checked" : @"checked", - @"unchecked" : @"not checked", - @"busy" : @"busy", - @"expanded" : @"expanded", - @"collapsed" : @"collapsed", - @"mixed": @"mixed", - }; - }); - NSString *roleDescription = self.accessibilityRole ? roleDescriptions[self.accessibilityRole]: nil; + NSString *roleDescription = self.accessibilityRole ? rolesAndStatesDescription[self.accessibilityRole]: nil; if (roleDescription) { [valueComponents addObject:roleDescription]; } @@ -259,16 +270,16 @@ - (NSString *)accessibilityValue } if ([state isEqualToString:@"checked"]) { if ([val isKindOfClass:[NSNumber class]]) { - [valueComponents addObject:stateDescriptions[[val boolValue] ? @"checked" : @"unchecked"]]; + [valueComponents addObject:rolesAndStatesDescription[[val boolValue] ? @"checked" : @"unchecked"]]; } else if ([val isKindOfClass:[NSString class]] && [val isEqualToString:@"mixed"]) { - [valueComponents addObject:stateDescriptions[@"mixed"]]; + [valueComponents addObject:rolesAndStatesDescription[@"mixed"]]; } } if ([state isEqualToString:@"expanded"] && [val isKindOfClass:[NSNumber class]]) { - [valueComponents addObject:stateDescriptions[[val boolValue] ? @"expanded" : @"collapsed"]]; + [valueComponents addObject:rolesAndStatesDescription[[val boolValue] ? @"expanded" : @"collapsed"]]; } if ([state isEqualToString:@"busy"] && [val isKindOfClass:[NSNumber class]] && [val boolValue]) { - [valueComponents addObject:stateDescriptions[@"busy"]]; + [valueComponents addObject:rolesAndStatesDescription[@"busy"]]; } }