React Native์—์„œ ๋‹ค๊ตญ์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ž‘์„ฑ

React Native์—์„œ ๋‹ค๊ตญ์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ž‘์„ฑ

์ƒˆ๋กœ์šด ๊ตญ๊ฐ€์™€ ์ง€์—ญ์„ ๊ฐœ์ฒ™ํ•˜๋Š” ๊ตญ์ œ ๊ธฐ์—…์—๊ฒŒ ์ œํ’ˆ ํ˜„์ง€ํ™”๋Š” ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ชจ๋ฐ”์ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋„ ํ˜„์ง€ํ™”๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๊ฐ€ ํ•ด์™ธ ํ™•์žฅ์„ ์‹œ์ž‘ํ•˜๋Š” ๊ฒฝ์šฐ ๋‹ค๋ฅธ ๊ตญ๊ฐ€์˜ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ชจ๊ตญ์–ด๋กœ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐํšŒ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธฐ์‚ฌ์—์„œ๋Š” ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ React Native ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜์‘ ๋„ค์ดํ‹ฐ๋ธŒ ํ˜„์ง€ํ™”.

Skillbox๋Š” ๋‹ค์Œ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ๊ต์œก์šฉ ์˜จ๋ผ์ธ ๊ณผ์ • "์ง์—… ์ž๋ฐ” ๊ฐœ๋ฐœ์ž".
์•Œ๋ฆผ: "Habr"์˜ ๋ชจ๋“  ๋…์ž๋ฅผ ์œ„ํ•œ - "Habr" ํ”„๋กœ๋ชจ์…˜ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Skillbox ๊ณผ์ •์— ๋“ฑ๋กํ•  ๋•Œ 10 ๋ฃจ๋ธ” ํ• ์ธ.

๋„๊ตฌ ๋ฐ ๊ธฐ์ˆ 

์ด ๊ธ€์„ ์ดํ•ดํ•˜๋ ค๋ฉด React Native ์ž‘์—…์— ๋Œ€ํ•œ ๊ธฐ๋ณธ ๊ธฐ์ˆ ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ž‘์—… ๊ธฐ๊ณ„์˜ ์„ค์ •์— ์ต์ˆ™ํ•ด์ง€๋ ค๋ฉด ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜์‹ญ์‹œ์˜ค. ๊ณต์‹ ์ง€์นจ์„ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค.

๋‹ค์Œ ๋ฒ„์ „์˜ ์†Œํ”„ํŠธ์›จ์–ด ๋„๊ตฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

  • ๋…ธ๋“œ v10.15.0
  • npm 6.4.1
  • ํ„ธ์‹ค 1.16.0
  • ๋ฐ˜์‘ ๋„ค์ดํ‹ฐ๋ธŒ 0.59.9
  • ๋ฐ˜์‘ ๋„ค์ดํ‹ฐ๋ธŒ ํ˜„์ง€ํ™” 1.1.3
  • i18n-js 3.3.0

ะะฐั‡ะธะฝะฐะตะผ

์˜์–ด, ํ”„๋ž‘์Šค์–ด, ์•„๋ž์–ด๋ฅผ ์ง€์›ํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค๊ฒ ์Šต๋‹ˆ๋‹ค. ๋จผ์ € React-native-cli๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ ค๋ฉด ํ„ฐ๋ฏธ๋„์— ๋‹ค์Œ์„ ์ž…๋ ฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

$ ๋ฐ˜์‘ ๋„ค์ดํ‹ฐ๋ธŒ ์ดˆ๊ธฐํ™” ๋‹ค๊ตญ์–ด
$ cd ๋‹ค๊ตญ์–ด

ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€

์ฒซ ๋ฒˆ์งธ ๋‹จ๊ณ„๋Š” ๋‹ค์Œ์„ ์ž…๋ ฅํ•˜์—ฌ React-native-localize๋ฅผ ์„ค์น˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
$ ์›์‚ฌ ๋ฐ˜์‘ ๋„ค์ดํ‹ฐ๋ธŒ ํ˜„์ง€ํ™” ์ถ”๊ฐ€

์„ค์น˜ ๊ณผ์ •์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ, ์„ค์น˜ ๋งค๋‰ด์–ผ์„ ์ฝ์–ด ๋ณผ ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

React-native-localize ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๊ฐœ๋ฐœ์ž์—๊ฒŒ ๋‹ค๊ตญ์–ด ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ๋…€์—๊ฒŒ๋Š” i18n์ด๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•˜๋‚˜ ๋” ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์ด ๋ฌธ์„œ์—์„œ๋Š” ์‚ฌ์šฉ๋ฒ•์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. I18n.js JavaScript๋กœ ๋ฒˆ์—ญ์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด.

$ ์›์‚ฌ i18n-js ์ถ”๊ฐ€

i18n-js๋Š” ์บ์‹ฑ์ด๋‚˜ ๋ฉ”๋ชจ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ด๋ฅผ ์œ„ํ•ด lodash.memoize๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

$ ์›์‚ฌ lodash.memoize ์ถ”๊ฐ€

๋ฒˆ์—ญ ์ž‘์—…

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋‹ค๋ฅธ ์–ธ์–ด์™€ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ์œผ๋ ค๋ฉด ๋จผ์ € src ๋‚ด์— ๋ฒˆ์—ญ ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋งŒ๋“  ๋‹ค์Œ ๊ฐ ์–ธ์–ด์— ๋Œ€ํ•œ ์„ธ ๊ฐœ์˜ JSON ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

1. ์˜์–ด์˜ ๊ฒฝ์šฐ en.json;

2. ํ”„๋ž‘์Šค์–ด์˜ ๊ฒฝ์šฐ fr.json;

3. ์•„๋ž์–ด์˜ ๊ฒฝ์šฐ ar.json.

์ด๋Ÿฌํ•œ ํŒŒ์ผ์—๋Š” ํ‚ค์™€ ๊ฐ’์ด ํฌํ•จ๋œ JSON ๊ฐœ์ฒด๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ‚ค๋Š” ๊ฐ ์–ธ์–ด๋งˆ๋‹ค ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—์„œ ํ…์ŠคํŠธ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

๊ฐ’์€ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ‘œ์‹œ๋˜์–ด์•ผ ํ•˜๋Š” ํ…์ŠคํŠธ์ž…๋‹ˆ๋‹ค.

์˜์–ด:

{"์•ˆ๋…•ํ•˜์„ธ์š”": "์•ˆ๋…•ํ•˜์„ธ์š”!"}

ะคั€ะฐะฝั†ัƒะทัะบะธะน

{"์•ˆ๋…•ํ•˜์„ธ์š”": "Salut le Monde!"}

ะั€ะฐะฑัะบะธะน

{ "์•ˆ๋…•ํ•˜์„ธ์š”": "ุงู‡ู„ุงู‹ ุจุงู„ุนุงู„ู…"}

๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋‹ค๋ฅธ ์–ธ์–ด๋„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฉ”์ธ์ฝ”๋“œ

์ด ์‹œ์ ์—์„œ App.js ํŒŒ์ผ์„ ์—ด๊ณ  ์—ฌ๊ธฐ์— ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

import React from "react";
import * as RNLocalize from "react-native-localize";
import i18n from "i18n-js";
import memoize from "lodash.memoize"; // Use for caching/memoize for better performance
 
import {
  I18nManager,
  SafeAreaView,
  ScrollView,
  StyleSheet,
  Text,
  View
} from "react-native";

๊ทธ ๋‹ค์Œ์—๋Š” ๋‚˜์ค‘์— ์œ ์šฉํ•  ๋ณด์กฐ ๊ธฐ๋Šฅ๊ณผ ์ƒ์ˆ˜๊ฐ€ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค.

const translationGetters = {
  // lazy requires (metro bundler does not support symlinks)
  ar: () => require("./src/translations/ar.json"),
  en: () => require("./src/translations/en.json"),
  fr: () => require("./src/translations/fr.json")
};
 
const translate = memoize(
  (key, config) => i18n.t(key, config),
  (key, config) => (config ? key + JSON.stringify(config) : key)
);
 
const setI18nConfig = () => {
  // fallback if no available language fits
  const fallback = { languageTag: "en", isRTL: false };
 
  const { languageTag, isRTL } =
    RNLocalize.findBestAvailableLanguage(Object.keys(translationGetters)) ||
    fallback;
 
  // clear translation cache
  translate.cache.clear();
  // update layout direction
  I18nManager.forceRTL(isRTL);
  // set i18n-js config
  i18n.translations = { [languageTag]: translationGetters[languageTag]() };
  i18n.locale = languageTag;
};

์ด์ œ App ํด๋ž˜์Šค์˜ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

export default class App extends React.Component {
  constructor(props) {
    super(props);
    setI18nConfig(); // set initial config
  }
 
  componentDidMount() {
    RNLocalize.addEventListener("change", this.handleLocalizationChange);
  }
 
  componentWillUnmount() {
    RNLocalize.removeEventListener("change", this.handleLocalizationChange);
  }
 
  handleLocalizationChange = () => {
    setI18nConfig();
    this.forceUpdate();
  };
 
  render() {
    return (
      <SafeAreaView style={styles.safeArea}>
        <Text style={styles.value}>{translate("hello")}</Text>
      </SafeAreaView>
    );
  }
}
 
const styles = StyleSheet.create({
  safeArea: {
    backgroundColor: "white",
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  },
  value: {
    fontSize: 18
  }
});

์ฒซ ๋ฒˆ์งธ ์š”์†Œ์ธ setI18nConfig()๋Š” ์ดˆ๊ธฐ ๊ตฌ์„ฑ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ ๋‹ค์Œ, componentDidMount()์— ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์š”์†Œ๋Š” ์—…๋ฐ์ดํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜๊ณ  ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด handlerLocalizationChange()๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

handlerLocalizationChange() ๋ฉ”์„œ๋“œ๋Š” setI18nConfig() ๋ฐ forceUpdate()๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ˆˆ์— ๋„๊ฒŒ ํ•˜๋ ค๋ฉด ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋ Œ๋”๋งํ•ด์•ผ ํ•˜๋ฏ€๋กœ Android ์žฅ์น˜์— ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ ๋‹ค์Œ componentWillUnmount() ๋ฉ”์„œ๋“œ์—์„œ ์ฒญ์ทจ๋ฅผ ์ œ๊ฑฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ render()๋Š” ๋ฒˆ์—ญ()์„ ์‚ฌ์šฉํ•˜๊ณ  ์—ฌ๊ธฐ์— ์ฃผ์š” ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ hello๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋‹จ๊ณ„๋ฅผ ๋งˆ์น˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์–ด๋–ค ์–ธ์–ด๊ฐ€ ํ•„์š”ํ•œ์ง€ "์ดํ•ด"ํ•˜๊ณ  ๊ทธ ์•ˆ์— ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ถœ์‹œ

์ด์ œ ๋ฒˆ์—ญ์ด ์–ด๋–ป๊ฒŒ ์ง„ํ–‰๋˜๋Š”์ง€ ํ™•์ธํ•  ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค.

๋จผ์ € ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ๋‚˜ ์—๋ฎฌ๋ ˆ์ดํ„ฐ์—์„œ ๋‹ค์Œ์„ ์ž…๋ ฅํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

$ ๋ฐ˜์‘ ๋„ค์ดํ‹ฐ๋ธŒ ์‹คํ–‰ iOS
$ ๋ฐ˜์‘ ๋„ค์ดํ‹ฐ๋ธŒ ์‹คํ–‰ ์•ˆ๋“œ๋กœ์ด๋“œ

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณด์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค:

React Native์—์„œ ๋‹ค๊ตญ์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ž‘์„ฑ

์ด์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜์—ฌ ์–ธ์–ด๋ฅผ ํ”„๋ž‘์Šค์–ด๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

React Native์—์„œ ๋‹ค๊ตญ์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ž‘์„ฑ

์šฐ๋ฆฌ๋Š” ์•„๋ž์–ด์—๋„ ๋˜‘๊ฐ™์€ ์ผ์„ ํ•ฉ๋‹ˆ๋‹ค. ์ฐจ์ด๋Š” ์—†์Šต๋‹ˆ๋‹ค.

์—ฌํƒœ๊นŒ์ง€๋Š” ๊ทธ๋Ÿฐ๋Œ€๋กœ ์ž˜๋๋‹ค.

ํ•˜์ง€๋งŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋ฒˆ์—ญ์ด ์—†๋Š” ์–ธ์–ด๋ฅผ ๋ฌด์ž‘์œ„๋กœ ์„ ํƒํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”?

findBestLanguage์˜ ์ž„๋ฌด๋Š” ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๋ฒˆ์—ญ ์ค‘์—์„œ ์ตœ์ ์˜ ๋ฒˆ์—ญ์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ ๊ธฐ๋ณธ ์–ธ์–ด๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋Š” ์ „ํ™” ์„ค์ •์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด iOS ์—๋ฎฌ๋ ˆ์ดํ„ฐ์—์„œ๋Š” ์–ธ์–ด ์ˆœ์„œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

React Native์—์„œ ๋‹ค๊ตญ์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ž‘์„ฑ

์„ ํƒํ•œ ์–ธ์–ด๊ฐ€ ๊ธฐ๋ณธ ์–ธ์–ด๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ findBestAvailableLanguage๋Š” ๊ธฐ๋ณธ ์–ธ์–ด๊ฐ€ ํ‘œ์‹œ๋˜๋„๋ก ์ •์˜๋˜์ง€ ์•Š์€ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

๋ณด๋„ˆ์Šค

React-native-localize์—๋Š” ์ˆ˜๋งŽ์€ ์–ธ์–ด ์š”์†Œ์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” API๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ์„ ์‹œ์ž‘ํ•˜๊ธฐ ์ „, ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•ด ๋ณผ ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์กฐ์‚ฌ ๊ฒฐ๊ณผ

์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์€ ๋ฌธ์ œ์—†์ด ๋‹ค๊ตญ์–ด๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. React-native-localize๋Š” ์•ฑ์˜ ์‚ฌ์šฉ์ž ๊ธฐ๋ฐ˜์„ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋Š” ํ›Œ๋ฅญํ•œ ์˜ต์…˜์ž…๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ์†Œ์Šค ์ฝ”๋“œ ์—ฌ๊ธฐ์—.

Skillbox๋Š” ๋‹ค์Œ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€