-1

I am quite new in React Native and I am trying to understand how function written in a parent Component could be passed (inherited) to any children and sub-children. In particular I am using a library to internationalise my App using:

import * as RNLocalize from 'react-native-localize'
import i18n from 'i18n-js'

But I noticed that I have to implement the translate(...) function for each Component of the whole project and this seems to be exaggerated because it requires a lot of work to implement the translation feature (I followed this tutorial).

Please, note that I have a basic understanding how to pass a function or some data using this.props, so I am not asking how props works from a parent to a single child. What I am asking is: how to avoid to repeat the code from //BEGIN ... to //END... (please see WithSecurityScreen file) and to avoid to repeat the implementation of handleLocalizationChange, RNLocalize.addEventListener, RNLocalize.removeEventListener and translate.

Please also note that the translation library works, test is provided at following line of WithSecurityScreen:

const SecurityScreen = () => <View><Text>{translate('USER_SURNAME')}</Text></View>;

But I am not be able to pass translate(...) function to each components of the whole project.

The project structure is:

  • App.js (wraps SecureApp.js)
  • SecureApp.js (wrapped in App.js and runs WithSecurityScreen.js)
  • WithSecurityScreen.js (wraps routes to views, e.g. Welcome.js)
  • Welcome.js (main view)

App.js

import { withSecurityScreen } from './src/components/withSecurityScreen'

import App from "./SecureApp.js"

export default withSecurityScreen(App);

SecureApp.js

const MainNavigator = createStackNavigator({
    Home: {
        screen: Welcome,
        navigationOptions: {
            headerShown: false
        }
    },
    UserProfile: {
        screen: CoreApp,
        navigationOptions: {
            headerShown: false
        }
    },
    NumPad: {
        screen: NumPad,
        navigationOptions: {
            header: 'PIN Creation',
            headerShown: false
        }
    }, /* , navigationOptions: {headerLeft: () => null} */
    QrScan: {
        screen: QrScan, navigationOptions: {
            header: 'QR Scan',
            headerShown: false
        }
    },
    ...
});

export default createAppContainer(MainNavigator);

WithSecurityScreen.js

// START: https://heartbeat.fritz.ai/how-to-use-react-native-localize-in-react-native-apps-3bb3d510f801
import * as RNLocalize from 'react-native-localize'
import i18n from 'i18n-js'
import memoize from 'lodash.memoize'
const translationGetters = {
    en: () => require('./../../assets/locales/en/en.json'),
    it: () => require('./../../assets/locales/it/it.json')
};

const translate = memoize(
    (key, config) => i18n.t(key, config),
    (key, config) => (config ? key + JSON.stringify(config) : key)
)

const setI18nConfig = () => {
    const fallback = { languageTag: 'en' }
    const { languageTag } =
    RNLocalize.findBestAvailableLanguage(Object.keys(translationGetters)) ||
    fallback

    translate.cache.clear()

    i18n.translations = { [languageTag]: translationGetters[languageTag]() }
    i18n.locale = languageTag
}
// END: https://heartbeat.fritz.ai/how-to-use-react-native-localize-in-react-native-apps-3bb3d510f801

const SecurityScreen = () => <View><Text>{translate('USER_SURNAME')}</Text></View>;

const showSecurityScreenFromAppState = appState =>
    ['background', 'inactive'].includes(appState);

const withSecurityScreenIOS = Wrapped => {
    return class WithSecurityScreen extends React.Component {

        constructor(props) {
            super(props)
            setI18nConfig()
        }

        state = {
            showSecurityScreen: showSecurityScreenFromAppState(AppState.currentState)
        };

        componentDidMount() {
            AppState.addEventListener('change', this.onChangeAppState)
            RNLocalize.addEventListener('change', this.handleLocalizationChange)
        }

        componentWillUnmount() {
            AppState.removeEventListener('change', this.onChangeAppState)
            RNLocalize.removeEventListener('change', this.handleLocalizationChange)
        }

        handleLocalizationChange = () => {
            setI18nConfig()
                .then(() => this.forceUpdate())
                .catch(error => {
                    console.error(error)
                })
        }

        onChangeAppState = nextAppState => {
            const showSecurityScreen = showSecurityScreenFromAppState(nextAppState);

            this.setState({showSecurityScreen})
        };

        render() {
            return this.state.showSecurityScreen
                ? <SecurityScreen/>
                : <Wrapped {...this.props} />
        }
    }
};

const withSecurityScreenAndroid = Wrapped => Wrapped;

export const withSecurityScreen = Platform.OS === 'ios'
    ? withSecurityScreenIOS
    : withSecurityScreenAndroid;

Welcome.js

export default class Welcome extends Component {
    let username = 'UserName';
    render() {
        return (
            <View style={styles.container}>
                <LinearGradient colors={globalStyles.colors.gradientGreen} style={{flex: 1}}>
                    <View style={styles.upperView}><Text style={styles.upperViewText}>{this.props.translate('WELCOME_TEXT')}{this.username}</Text>
                    </View>

                </LinearGradient>
            </View>
        );
    }
}

I get following error:

enter image description here

shogitai
  • 1,823
  • 1
  • 23
  • 50

2 Answers2

1

First of all in your case you can declare translate function in separate js file locale.js and can declare all your translation logic in that file and export the functions translate and setI18nConfig

local.js

  import * as RNLocalize from 'react-native-localize'
    import i18n from 'i18n-js'
    import memoize from 'lodash.memoize'
    const translationGetters = {
        en: () => require('./../../assets/locales/en/en.json'),
        it: () => require('./../../assets/locales/it/it.json')
    };

   export const translate = memoize(
        (key, config) => i18n.t(key, config),
        (key, config) => (config ? key + JSON.stringify(config) : key)
    )

   export const setI18nConfig = () => {
        const fallback = { languageTag: 'en' }
        const { languageTag } =
        RNLocalize.findBestAvailableLanguage(Object.keys(translationGetters)) ||
        fallback

        translate.cache.clear()

        i18n.translations = { [languageTag]: translationGetters[languageTag]() }
        i18n.locale = languageTag
    } 

and import this functions in your components where you want to use this like

App.js

import React, { Component } from 'react';
import { View, Text } from 'react-native';
import { setI18nConfig, translate } from './locale';

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }

  render() {
    setI18nConfig(); // This should be called only once through out the app at the time of changing locale. 
    return (
      <View>
        <Text> translate ('key_from_json_to_label') </Text>
      </View>
    );
  }
}

For more details you can refer this repo where I have implemented the same.

Hope this will help.

Bhagwat K
  • 2,982
  • 2
  • 23
  • 33
0

i use import i18n from "i18next", for translation. Here is the config file:


//config/i18n
import i18n from "i18next";
import { reactI18nextModule, initReactI18next } from "react-i18next";
import translationFR from '../translation/fr/translation.json';
import translationEN from '../translation/en/translation.json';
import DeviceInfo from 'react-native-device-info';

let locale = DeviceInfo.getDeviceLocale().substring(0, 2);
if (locale != 'fr') {
  locale = 'en';
}

// the translations
const resources = {
  en: translationEN,
  fr: translationFR,
};

i18n
  .use(reactI18nextModule) // passes i18n down to react-i18next
  .init({
    resources: resources,
    lng: locale,
    fallbackLng: ['en', 'fr'],
    keySeparator: false, // we do not use keys in form messages.welcome
    interpolation: {
      escapeValue: false // react already safes from xss
    }
  });

export default i18n;

One of the uses is to

import i18n from 'config/i18n'

and use translate in file like this

i18n.t('bottomTab:home_tab')

You can also wrap component with withNamespaces from 'react-i18next', like this:


export default withNamespaces([], {wait: true})(Welcome)

and then access translation with:


this.props.t('welcome_message')
B. Mohammad
  • 2,152
  • 1
  • 13
  • 28