Skip to main content

Right-to-Left Layout Support For React Native Apps

· 13 min read
Mengjue (Mandy) Wang
Software Engineer Intern at Facebook

앱 스토어에 앱을 출시한 후, 다음 단계는 더 많은 사용자를 확보하기 위해 국제화를 진행하는 것이다. 전 세계 20개 이상의 국가와 수많은 사람들이 오른쪽에서 왼쪽으로 쓰는 언어(RTL)를 사용한다. 따라서 앱이 RTL을 지원하도록 만드는 것이 필요하다.

React Native가 RTL 레이아웃을 지원하도록 개선되었다는 소식을 전하게 되어 기쁘다. 이 기능은 현재 react-native 마스터 브랜치에서 사용할 수 있으며, 다음 RC 버전인 v0.33.0-rc에서도 제공될 예정이다.

이를 위해 RN이 사용하는 핵심 레이아웃 엔진인 css-layout과 RN 코어 구현, 그리고 특정 오픈소스 JS 컴포넌트를 RTL 지원을 위해 변경했다.

RTL 지원을 실제 환경에서 테스트하기 위해, 최신 버전의 Facebook Ads Manager 앱(첫 번째 크로스 플랫폼 100% RN 앱)이 아랍어와 히브리어로 iOSAndroid에서 RTL 레이아웃으로 제공된다. 다음은 RTL 언어로 된 앱의 모습이다:

RN의 RTL 지원을 위한 주요 변경 사항

css-layout은 이미 레이아웃을 위해 startend 개념을 도입했다. Left-to-Right(LTR) 레이아웃에서 startleft를 의미하고, endright를 의미한다. 하지만 RTL 레이아웃에서는 startright를, endleft를 의미한다. 이를 통해 RN은 startend 계산에 의존해 position, padding, margin을 포함한 올바른 레이아웃을 계산할 수 있다.

또한 css-layout은 각 컴포넌트의 방향을 부모로부터 상속받도록 설계했다. 따라서 루트 컴포넌트의 방향을 RTL로 설정하면 전체 앱이 뒤집힌다.

아래 다이어그램은 이러한 변경 사항을 높은 수준에서 설명한다:

주요 변경 사항은 다음과 같다:

이 업데이트를 통해 앱에서 RTL 레이아웃을 허용하면:

  • 모든 컴포넌트 레이아웃이 수평으로 뒤집힌다.
  • RTL을 지원하는 OSS 컴포넌트를 사용 중이라면 일부 제스처와 애니메이션이 자동으로 RTL 레이아웃을 적용한다.
  • 앱을 완전히 RTL 지원으로 만들기 위해 필요한 추가 작업은 최소화된다.

앱을 RTL 지원으로 만들기

  1. RTL을 지원하려면 먼저 앱에 RTL 언어 번들을 추가한다.

    • iOSAndroid의 일반 가이드를 참고한다.
  2. 앱이 RTL 레이아웃을 지원할 수 있도록 네이티브 코드 시작 부분에서 allowRTL() 함수를 호출한다. 이 유틸리티는 앱이 준비되었을 때만 RTL 레이아웃을 적용한다. 예제는 다음과 같다.

    iOS:

    // AppDelegate.m 파일에서
    [[RCTI18nUtil sharedInstance] allowRTL:YES];

    Android:

    // MainActivity.java 파일에서
    I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
    sharedI18nUtilInstance.allowRTL(context, true);
  3. Android의 경우 AndroidManifest.xml 파일의 <application> 엘리먼트에 android:supportsRtl="true"를 추가한다.

이제 앱을 다시 컴파일하고 기기 언어를 RTL 언어(예: 아랍어 또는 히브리어)로 변경하면 앱 레이아웃이 자동으로 RTL로 변경된다.

RTL 대응 컴포넌트 작성하기

일반적으로 대부분의 컴포넌트는 이미 RTL(오른쪽에서 왼쪽) 대응이 되어 있다. 예를 들어:

  • 왼쪽에서 오른쪽으로 배치된 레이아웃
  • 오른쪽에서 왼쪽으로 배치된 레이아웃

하지만 몇 가지 주의해야 할 경우가 있으며, 이때는 I18nManager를 사용해야 한다. I18nManager에는 isRTL이라는 상수가 있어 앱의 레이아웃이 RTL인지 아닌지를 확인할 수 있다. 이를 통해 레이아웃에 따라 필요한 변경을 적용할 수 있다.

방향성을 가진 아이콘 처리

컴포넌트에 아이콘이나 이미지를 사용할 때, RN은 소스 이미지를 자동으로 뒤집지 않기 때문에 LTR과 RTL 레이아웃에서 동일하게 표시된다. 따라서 레이아웃 스타일에 따라 아이콘을 직접 뒤집어야 한다.

  • 좌에서 우로(LTR) 레이아웃
  • 우에서 좌로(RTL) 레이아웃

아이콘의 방향을 조정하는 두 가지 방법은 다음과 같다:

  • 이미지 컴포넌트에 transform 스타일 추가:

    <Image
    source={...}
    style={{transform: [{scaleX: I18nManager.isRTL ? -1 : 1}]}}
    />
  • 또는, 방향에 따라 이미지 소스 변경:

    let imageSource = require('./back.png');
    if (I18nManager.isRTL) {
    imageSource = require('./forward.png');
    }
    return <Image source={imageSource} />;

제스처와 애니메이션

안드로이드와 iOS 개발에서 RTL 레이아웃으로 변경하면, 제스처와 애니메이션이 LTR 레이아웃과 반대 방향으로 동작한다. 현재 리액트 네이티브(RN)에서는 제스처와 애니메이션이 RN 코어 코드 수준에서 지원되지 않고, 컴포넌트 수준에서 지원된다. 다행히 일부 컴포넌트는 이미 RTL을 지원하고 있으며, 예를 들어 SwipeableRowNavigationExperimental가 있다. 그러나 제스처를 사용하는 다른 컴포넌트들은 수동으로 RTL을 지원해야 한다.

제스처 RTL 지원을 잘 보여주는 예로 SwipeableRow를 들 수 있다.

제스처 예제
// SwipeableRow.js
_isSwipingExcessivelyRightFromClosedPosition(gestureState: Object): boolean {
// ...
const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
return (
this._isSwipingRightFromClosed(gestureState) &&
gestureStateDx > RIGHT_SWIPE_THRESHOLD
);
},
애니메이션 예제
// SwipeableRow.js
_animateBounceBack(duration: number): void {
// ...
const swipeBounceBackDistance = IS_RTL ?
-RIGHT_SWIPE_BOUNCE_BACK_DISTANCE :
RIGHT_SWIPE_BOUNCE_BACK_DISTANCE;
this._animateTo(
-swipeBounceBackDistance,
duration,
this._animateToClosedPositionDuringBounce,
);
},

RTL 지원 앱 유지 관리

초기 RTL 호환 앱 출시 이후에도 새로운 기능을 반복적으로 추가해야 할 경우가 많다. 개발 효율성을 높이기 위해 I18nManager는 테스트 기기의 언어 설정을 변경하지 않고도 빠르게 RTL을 테스트할 수 있는 forceRTL() 함수를 제공한다. 앱 내에 이 기능을 간단히 전환할 수 있는 스위치를 추가하는 것도 좋은 방법이다. 다음은 RNTester의 RTL 예제에서 가져온 코드다:

<RNTesterBlock title={'Quickly Test RTL Layout'}>
<View style={styles.flexDirectionRow}>
<Text style={styles.switchRowTextView}>forceRTL</Text>
<View style={styles.switchRowSwitchView}>
<Switch
onValueChange={this._onDirectionChange}
style={styles.rightAlignStyle}
value={this.state.isRTL}
/>
</View>
</View>
</RNTesterBlock>;

_onDirectionChange = () => {
I18nManager.forceRTL(!this.state.isRTL);
this.setState({isRTL: !this.state.isRTL});
Alert.alert(
'Reload this page',
'Please reload this page to change the UI direction! ' +
'All examples in this app will be affected. ' +
'Check them out to see what they look like in RTL layout.',
);
};

새로운 기능을 개발할 때 이 버튼을 쉽게 토글하고 앱을 리로드해 RTL 레이아웃을 확인할 수 있다. 이 방법의 장점은 언어 설정을 변경하지 않고도 테스트가 가능하다는 점이다. 하지만 다음 섹션에서 설명하듯 일부 텍스트 정렬은 변경되지 않을 수 있다. 따라서 앱 출시 전에 반드시 RTL 언어로 테스트하는 것이 좋다.

한계와 향후 계획

RTL(오른쪽에서 왼쪽) 지원은 앱의 대부분의 사용자 경험을 커버해야 한다. 하지만 현재 몇 가지 제한 사항이 존재한다:

  • 텍스트 정렬 방식이 Android와 iOS에서 다르게 동작한다.
    • iOS에서는 기본 텍스트 정렬이 활성 언어 번들에 따라 결정되며, 항상 한쪽으로 고정된다. Android에서는 텍스트 콘텐츠의 언어에 따라 달라진다. 예를 들어, 영어는 왼쪽 정렬, 아랍어는 오른쪽 정렬된다.
    • 이론적으로는 플랫폼 간 일관성을 유지해야 하지만, 사용자가 앱을 사용할 때 한 동작을 다른 동작보다 선호할 수 있다. 텍스트 정렬에 대한 최적의 방식을 찾기 위해 더 많은 사용자 경험 연구가 필요할 수 있다.
  • "진짜" 왼쪽/오른쪽 개념이 없다.

    앞서 논의한 것처럼, JS 측면에서 left/right 스타일을 start/end로 매핑한다. RTL 레이아웃에서 코드의 모든 left는 화면에서 "오른쪽"이 되고, 코드의 right는 화면에서 "왼쪽"이 된다. 이는 제품 코드를 크게 변경할 필요가 없어 편리하지만, 코드에서 "진짜 왼쪽"이나 "진짜 오른쪽"을 지정할 방법이 없다는 의미이다. 향후에는 언어와 무관하게 컴포넌트가 방향을 제어할 수 있도록 하는 것이 필요할 수 있다.

  • 제스처와 애니메이션에 대한 RTL 지원을 개발자 친화적으로 개선한다.

    현재 제스처와 애니메이션이 RTL과 호환되도록 하려면 여전히 프로그래밍 노력이 필요하다. 앞으로는 제스처와 애니메이션의 RTL 지원을 더 개발자 친화적으로 만드는 방법을 찾는 것이 이상적일 것이다.

직접 해보기!

RTL 지원에 대해 더 깊이 이해하려면 RNTesterRTLExample을 확인해 보세요. 여러분의 경험을 공유해 주시면 감사하겠습니다!

마지막으로, 이 글을 읽어 주셔서 감사합니다! React Native의 RTL 지원이 여러분의 앱을 전 세계 사용자에게 더 나은 경험을 제공하는 데 도움이 되길 바랍니다!