From 66d633fdba592af6c1b139f38ec4a89991df6885 Mon Sep 17 00:00:00 2001
From: Diego Pasquali <5963683+dgopsq@users.noreply.github.com>
Date: Mon, 7 Mar 2022 09:43:30 -0800
Subject: [PATCH] Integrated iOS-only `accessibilityLanguage` prop (#33090)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Summary:
This PR fixes https://github.com/facebook/react-native/issues/30891
This PR is going to add an `accessibilityLanguage` prop to all the available components. This props is currently working only on iOS and should follow the [guidelines of the relative configuration](https://developer.apple.com/documentation/objectivec/nsobject/1615192-accessibilitylanguage).
I'm in no way an expert on native programming (especially Objective-C) so I'm open to changes / improvements 🙂
[iOS] [Added] - Integrated the `accessibilityLanguage` prop to all the available components. The prop is available for any platform but it will work only on iOS.
Pull Request resolved: https://github.com/facebook/react-native/pull/33090
Test Plan:
This has been tested using both the Simulator, checking for the `Language` attribute, and using a physical device with the Voice Over enabled.
Reviewed By: philIip
Differential Revision: D34523608
Pulled By: rh389
fbshipit-source-id: b5d77fc0b3d76ea8ed8f30c8385459ba98122ff6
---
Libraries/Components/Button.js | 3 +++
Libraries/Components/Pressable/Pressable.js | 1 +
Libraries/Components/Touchable/TouchableBounce.js | 1 +
Libraries/Components/Touchable/TouchableHighlight.js | 1 +
.../Components/Touchable/TouchableNativeFeedback.js | 1 +
Libraries/Components/Touchable/TouchableOpacity.js | 1 +
.../Components/Touchable/TouchableWithoutFeedback.js | 2 ++
.../Components/View/ReactNativeViewAttributes.js | 1 +
Libraries/Components/View/ViewPropTypes.js | 9 +++++++++
Libraries/NativeComponent/PlatformBaseViewConfig.js | 2 ++
Libraries/Text/TextProps.js | 1 +
.../RCTParagraphComponentAccessibilityProvider.mm | 1 +
.../ComponentViews/View/RCTViewComponentView.mm | 6 ++++++
React/Views/RCTViewManager.m | 3 +++
React/Views/UIView+React.h | 1 +
React/Views/UIView+React.m | 11 +++++++++++
.../renderer/components/view/AccessibilityProps.cpp | 6 ++++++
.../renderer/components/view/AccessibilityProps.h | 1 +
.../examples/Accessibility/AccessibilityIOSExample.js | 3 +++
19 files changed, 55 insertions(+)
diff --git a/Libraries/Components/Button.js b/Libraries/Components/Button.js
index 416f4e7bb5f6e4..c51dad84ac5a3f 100644
--- a/Libraries/Components/Button.js
+++ b/Libraries/Components/Button.js
@@ -200,6 +200,7 @@ type ButtonProps = $ReadOnly<{|
onAccessibilityAction?: ?(event: AccessibilityActionEvent) => mixed,
accessibilityState?: ?AccessibilityState,
accessibilityHint?: ?string,
+ accessibilityLanguage?: ?Stringish,
|}>;
/**
@@ -338,6 +339,7 @@ class Button extends React.Component {
accessible,
accessibilityActions,
accessibilityHint,
+ accessibilityLanguage,
accessibilityRole, // [macOS]
onAccessibilityAction,
} = this.props;
@@ -382,6 +384,7 @@ class Button extends React.Component {
onAccessibilityAction={onAccessibilityAction}
accessibilityLabel={accessibilityLabel}
accessibilityHint={accessibilityHint}
+ accessibilityLanguage={accessibilityLanguage}
accessibilityRole={accessibilityRole || 'button'} // [macOS]
accessibilityState={accessibilityState}
hasTVPreferredFocus={hasTVPreferredFocus}
diff --git a/Libraries/Components/Pressable/Pressable.js b/Libraries/Components/Pressable/Pressable.js
index 3887684a6d985c..53e3bd0c60c850 100644
--- a/Libraries/Components/Pressable/Pressable.js
+++ b/Libraries/Components/Pressable/Pressable.js
@@ -49,6 +49,7 @@ type Props = $ReadOnly<{|
accessibilityActions?: ?$ReadOnlyArray,
accessibilityElementsHidden?: ?boolean,
accessibilityHint?: ?Stringish,
+ accessibilityLanguage?: ?Stringish,
accessibilityIgnoresInvertColors?: ?boolean,
accessibilityLabel?: ?Stringish,
accessibilityLiveRegion?: ?('none' | 'polite' | 'assertive'),
diff --git a/Libraries/Components/Touchable/TouchableBounce.js b/Libraries/Components/Touchable/TouchableBounce.js
index 5fd06a8d80ea58..e4bbbd2004c28d 100644
--- a/Libraries/Components/Touchable/TouchableBounce.js
+++ b/Libraries/Components/Touchable/TouchableBounce.js
@@ -142,6 +142,7 @@ class TouchableBounce extends React.Component {
accessible={this.props.accessible !== false}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityHint={this.props.accessibilityHint}
+ accessibilityLanguage={this.props.accessibilityLanguage}
accessibilityRole={this.props.accessibilityRole}
accessibilityState={this.props.accessibilityState}
accessibilityActions={this.props.accessibilityActions}
diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js
index e6a1ef25f9e5c6..26cac7919164d8 100644
--- a/Libraries/Components/Touchable/TouchableHighlight.js
+++ b/Libraries/Components/Touchable/TouchableHighlight.js
@@ -301,6 +301,7 @@ class TouchableHighlight extends React.Component {
accessible={this.props.accessible !== false}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityHint={this.props.accessibilityHint}
+ accessibilityLanguage={this.props.accessibilityLanguage}
accessibilityRole={this.props.accessibilityRole}
accessibilityState={accessibilityState}
accessibilityValue={this.props.accessibilityValue}
diff --git a/Libraries/Components/Touchable/TouchableNativeFeedback.js b/Libraries/Components/Touchable/TouchableNativeFeedback.js
index 013eca42b9fb08..fd13a586177f3f 100644
--- a/Libraries/Components/Touchable/TouchableNativeFeedback.js
+++ b/Libraries/Components/Touchable/TouchableNativeFeedback.js
@@ -285,6 +285,7 @@ class TouchableNativeFeedback extends React.Component {
),
accessible: this.props.accessible !== false,
accessibilityHint: this.props.accessibilityHint,
+ accessibilityLanguage: this.props.accessibilityLanguage,
accessibilityLabel: this.props.accessibilityLabel,
accessibilityRole: this.props.accessibilityRole,
accessibilityState: accessibilityState,
diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js
index b7cb8cb2210174..a31d4bedad4379 100644
--- a/Libraries/Components/Touchable/TouchableOpacity.js
+++ b/Libraries/Components/Touchable/TouchableOpacity.js
@@ -256,6 +256,7 @@ class TouchableOpacity extends React.Component {
accessible={this.props.accessible !== false}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityHint={this.props.accessibilityHint}
+ accessibilityLanguage={this.props.accessibilityLanguage}
accessibilityRole={this.props.accessibilityRole}
accessibilityState={accessibilityState}
accessibilityActions={this.props.accessibilityActions}
diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js
index dd294b519cd1bb..b232deccdb0b85 100755
--- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js
+++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js
@@ -39,6 +39,7 @@ type Props = $ReadOnly<{|
accessibilityActions?: ?$ReadOnlyArray,
accessibilityElementsHidden?: ?boolean,
accessibilityHint?: ?Stringish,
+ accessibilityLanguage?: ?Stringish,
accessibilityIgnoresInvertColors?: ?boolean,
accessibilityLabel?: ?Stringish,
accessibilityLiveRegion?: ?('none' | 'polite' | 'assertive'),
@@ -93,6 +94,7 @@ const PASSTHROUGH_PROPS = [
'accessibilityActions',
'accessibilityElementsHidden',
'accessibilityHint',
+ 'accessibilityLanguage',
'accessibilityIgnoresInvertColors',
'accessibilityLabel',
'accessibilityLiveRegion',
diff --git a/Libraries/Components/View/ReactNativeViewAttributes.js b/Libraries/Components/View/ReactNativeViewAttributes.js
index 7dee20801a5a3e..d3e67af4f736ae 100644
--- a/Libraries/Components/View/ReactNativeViewAttributes.js
+++ b/Libraries/Components/View/ReactNativeViewAttributes.js
@@ -21,6 +21,7 @@ const UIView = {
accessibilityState: true,
accessibilityValue: true,
accessibilityHint: true,
+ accessibilityLanguage: true,
importantForAccessibility: true,
nativeID: true,
testID: true,
diff --git a/Libraries/Components/View/ViewPropTypes.js b/Libraries/Components/View/ViewPropTypes.js
index 626106488558ca..e84f0746267508 100644
--- a/Libraries/Components/View/ViewPropTypes.js
+++ b/Libraries/Components/View/ViewPropTypes.js
@@ -528,6 +528,15 @@ export type ViewProps = $ReadOnly<{|
*/
accessibilityHint?: ?Stringish,
+ /**
+ * Indicates to the accessibility services that the UI component is in
+ * a specific language. The provided string should be formatted following
+ * the BCP 47 specification (https://www.rfc-editor.org/info/bcp47).
+ *
+ * @platform ios
+ */
+ accessibilityLanguage?: ?Stringish,
+
/**
* Indicates to accessibility services to treat UI component like a specific role.
*/
diff --git a/Libraries/NativeComponent/PlatformBaseViewConfig.js b/Libraries/NativeComponent/PlatformBaseViewConfig.js
index 07c84a86793288..f3c0e81489c4c4 100644
--- a/Libraries/NativeComponent/PlatformBaseViewConfig.js
+++ b/Libraries/NativeComponent/PlatformBaseViewConfig.js
@@ -147,6 +147,7 @@ const PlatformBaseViewConfig: PartialViewConfigWithoutName =
accessibilityLabelledBy: true,
accessibilityLabel: true,
accessibilityHint: true,
+ accessibilityLanguage: true,
accessibilityRole: true,
accessibilityState: true,
accessibilityActions: true,
@@ -383,6 +384,7 @@ const PlatformBaseViewConfig: PartialViewConfigWithoutName =
accessibilityActions: true,
accessibilityLabel: true,
accessibilityHint: true,
+ accessibilityLanguage: true,
accessibilityValue: true,
accessibilityViewIsModal: true,
accessibilityElementsHidden: true,
diff --git a/Libraries/Text/TextProps.js b/Libraries/Text/TextProps.js
index 262f4ba5476c8d..28e638a214679d 100644
--- a/Libraries/Text/TextProps.js
+++ b/Libraries/Text/TextProps.js
@@ -44,6 +44,7 @@ export type TextProps = $ReadOnly<{|
accessibilityActions?: ?$ReadOnlyArray,
onAccessibilityAction?: ?(event: AccessibilityActionEvent) => mixed,
accessibilityHint?: ?Stringish,
+ accessibilityLanguage?: ?Stringish,
accessibilityLabel?: ?Stringish,
accessibilityRole?: ?AccessibilityRole,
accessibilityState?: ?AccessibilityState,
diff --git a/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.mm b/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.mm
index 55d5f9893b155b..4602c6f46eb84e 100644
--- a/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.mm
+++ b/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.mm
@@ -70,6 +70,7 @@ - (instancetype)initWithString:(facebook::react::AttributedString)attributedStri
firstElement.isAccessibilityElement = YES;
firstElement.accessibilityTraits = _view.accessibilityTraits;
firstElement.accessibilityLabel = accessibilityLabel;
+ firstElement.accessibilityLanguage = _view.accessibilityLanguage;
firstElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(_view.bounds, _view);
[firstElement setAccessibilityActivationPoint:CGPointMake(
firstElement.accessibilityFrame.origin.x + 1.0,
diff --git a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm
index 8d9b4fe9637c4d..e736a791ea8a42 100644
--- a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm
+++ b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm
@@ -304,6 +304,12 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
self.accessibilityElement.accessibilityLabel = RCTNSStringFromStringNilIfEmpty(newViewProps.accessibilityLabel);
}
+ // `accessibilityLanguage`
+ if (oldViewProps.accessibilityLanguage != newViewProps.accessibilityLanguage) {
+ self.accessibilityElement.accessibilityLanguage =
+ RCTNSStringFromStringNilIfEmpty(newViewProps.accessibilityLanguage);
+ }
+
// `accessibilityHint`
if (oldViewProps.accessibilityHint != newViewProps.accessibilityHint) {
self.accessibilityElement.accessibilityHint = RCTNSStringFromStringNilIfEmpty(newViewProps.accessibilityHint);
diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m
index b5018eadc772f2..a905e8f92e9dca 100644
--- a/React/Views/RCTViewManager.m
+++ b/React/Views/RCTViewManager.m
@@ -164,6 +164,9 @@ - (RCTShadowView *)shadowView
#else // [macOS
RCT_REMAP_VIEW_PROPERTY(accessibilityHint, reactAccessibilityElement.accessibilityHelp, NSString)
#endif // macOS]
+#if !TARGET_OS_OSX // [macOS]
+RCT_REMAP_VIEW_PROPERTY(accessibilityLanguage, reactAccessibilityElement.accessibilityLanguage, NSString)
+#endif // macOS]
RCT_REMAP_VIEW_PROPERTY(accessibilityValue, reactAccessibilityElement.accessibilityValueInternal, NSDictionary)
#if !TARGET_OS_OSX // [macOS]
RCT_REMAP_VIEW_PROPERTY(accessibilityViewIsModal, reactAccessibilityElement.accessibilityViewIsModal, BOOL)
diff --git a/React/Views/UIView+React.h b/React/Views/UIView+React.h
index 41b048a5d27637..407a0d46cfd81f 100644
--- a/React/Views/UIView+React.h
+++ b/React/Views/UIView+React.h
@@ -124,6 +124,7 @@
@property (nonatomic, copy) NSDictionary *accessibilityState;
@property (nonatomic, copy) NSArray *accessibilityActions;
@property (nonatomic, copy) NSDictionary *accessibilityValueInternal;
+@property (nonatomic, copy) NSString *accessibilityLanguage;
/**
* Used in debugging to get a description of the view hierarchy rooted at
diff --git a/React/Views/UIView+React.m b/React/Views/UIView+React.m
index f1c5f52c1d7386..915ddcef3fda18 100644
--- a/React/Views/UIView+React.m
+++ b/React/Views/UIView+React.m
@@ -372,6 +372,17 @@ - (void)setAccessibilityActions:(NSArray *)accessibilityActions
self, @selector(accessibilityActions), accessibilityActions, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+- (NSString *)accessibilityLanguage
+{
+ return objc_getAssociatedObject(self, _cmd);
+}
+
+- (void)setAccessibilityLanguage:(NSString *)accessibilityLanguage
+{
+ objc_setAssociatedObject(
+ self, @selector(accessibilityLanguage), accessibilityLanguage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
- (NSString *)accessibilityRoleInternal // [macOS] renamed so it doesn't conflict with -[NSAccessibility accessibilityRole].
{
return objc_getAssociatedObject(self, _cmd);
diff --git a/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp b/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp
index 98311ae553bb79..21f7bec8c37c23 100644
--- a/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp
+++ b/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp
@@ -67,6 +67,12 @@ AccessibilityProps::AccessibilityProps(
"accessibilityHint",
sourceProps.accessibilityHint,
"")),
+ accessibilityLanguage(convertRawProp(
+ context,
+ rawProps,
+ "accessibilityLanguage",
+ sourceProps.accessibilityLanguage,
+ "")),
accessibilityValue(convertRawProp(
context,
rawProps,
diff --git a/ReactCommon/react/renderer/components/view/AccessibilityProps.h b/ReactCommon/react/renderer/components/view/AccessibilityProps.h
index 53b85874a53555..d0df2cc21122a4 100644
--- a/ReactCommon/react/renderer/components/view/AccessibilityProps.h
+++ b/ReactCommon/react/renderer/components/view/AccessibilityProps.h
@@ -35,6 +35,7 @@ class AccessibilityProps {
AccessibilityLiveRegion::None};
std::string accessibilityRole{""};
std::string accessibilityHint{""};
+ std::string accessibilityLanguage{""};
AccessibilityValue accessibilityValue;
std::vector accessibilityActions{};
bool accessibilityViewIsModal{false};
diff --git a/packages/rn-tester/js/examples/Accessibility/AccessibilityIOSExample.js b/packages/rn-tester/js/examples/Accessibility/AccessibilityIOSExample.js
index 2328d62c146fa8..b8572b3fb98db2 100644
--- a/packages/rn-tester/js/examples/Accessibility/AccessibilityIOSExample.js
+++ b/packages/rn-tester/js/examples/Accessibility/AccessibilityIOSExample.js
@@ -55,6 +55,9 @@ class AccessibilityIOSExample extends React.Component {
This view's children are hidden from the accessibility tree
+
+ This view's language should be `it-IT`
+
);
}