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` + ); }