Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to change active locale programatically? #25

Closed
willymulyana opened this issue Jul 31, 2017 · 15 comments
Closed

How to change active locale programatically? #25

willymulyana opened this issue Jul 31, 2017 · 15 comments

Comments

@willymulyana
Copy link

Hi,
I can't find a way to change active locale programmatically, I read the doc and issues on github to no luck, maybe I miss something?
Fyi, I have two locales in my App, I can access methods from the context, also with static methods. if I do:
this.context.globalize = new Globalize(alternateLocale);
it seem the locale changed briefly before it revert back (saw the text changed briefly).

@joshswan
Copy link
Owner

Are you using FormattedWrapper or your own getChildContext implementation?

With FormattedWrapper it should work out of the box:

const App = ({ currency, locale }) => (
  <FormattedWrapper locale={locale} currency={currency} messages={Messages}>
    <App />
  </FormattedWrapper>
);

// Redux example
function mapStateToProps(state) {
  return {
    currency: state.currency,
    locale: state.locale,
  };
}

export default connect(mapStateToProps)(App);

Basically you just need to update the locale and/or currency props on FormattedWrapper and that should propagate throughout the app. If you're using your own implementation, check out FormattedWrapper to see if anything is different. The key is the componentWillReceiveProps method switching out the Globalize instance. I see that you were attempting to do that by setting this.context.globalize to a new instance, but you can't do that directly. You need to update it in the component providing getChildContext, which FormattedWrapper does for you when you change the props.

@joshswan joshswan closed this as completed Aug 1, 2017
@willymulyana
Copy link
Author

willymulyana commented Aug 2, 2017

Thanks for the direction, it really help to fix the problem.
The app setup make it's not straightforward though, It use custom fonts, react navigation, apollo (graphql), and it is an Expo.io project. So my setup become like this:

... (render method portion of App class)

    return this.state.fontLoaded ?
        <FormattedWrapper locale={this.props.locale.locale} currency="USD" messages={lang.MESSAGES}>
		<View style={{paddingTop:24,flex:1}}>
			<MainNavigator />
		</View>
        </FormattedWrapper>
      : null
    ;
  }

}

const mapStateToProps = (state) => {
  return {
    locale: state.locale,
  }
}

const ConnectedApp = connect(mapStateToProps)(App);

const AppWithProvider = () => (
  <ApolloProvider client={apolloClient} store={store}>
    <ConnectedApp />
  </ApolloProvider>
)

Expo.registerRootComponent(AppWithProvider);

Maybe it can be put more nicely as I realize I'm not good enough with wrapping things up :)

By the way, I was looking for a method like setLocale("en") to simply change locale programatically but not exists. I'm not sure if it's possible or can be implemented.

Thanks.

@joshswan
Copy link
Owner

joshswan commented Aug 2, 2017

Based on the example code you posted, your current locale is stored in your redux store. You could therefore easily make an action creator called setLocale that dispatches and action and sets the locale. That sort of function is outside the scope of this library as the locale can already be changed very simply be changing the prop on FormattedWrapper. How you manage your locale and other state is not part of this package.

@AndersGerner
Copy link

@joshswan I have tried to archive the same as @willymulyana but even when I tried to switch my RootComponent to a const like your example, I couldn't get it to work. It only works if I add a FormattedWrapper in every component where I need to use the FormattedMessage.

Here is the root component:

... //imports and styles
class RootContainer extends Component {
  render() {
		const locale = this.props.locale;
		console.log("RootLocale",locale);
  return (
      <ThemeProvider theme={colors}>
        <FormattedWrapper locale={locale} messages={messages}>
          <Root>
            <StatusBar barStyle='light-content' backgroundColor='transparent' translucent />
            { Platform.OS === 'android' && Platform.Version >= 20 ? <StatusBarAndroid /> : null }
            <Navigator />
          </Root>
        </FormattedWrapper>
			</ThemeProvider>

    );
  }
}

const mapStateToProps = (state) => ({
	state,
	locale: state.Language.language
});

const ConnectedRootContainer = connect(mapStateToProps,null)(RootContainer);
class App extends Component {
  render() {
    return (
      <Provider store={store}>
			  <ConnectedRootContainer />
      </Provider>
    );
  }
}

export default App;

My locale is changed with Redux, and to show it, I have add a console.log to the RootComponent.
Here is an screenshot of the log:
Screenshot

As mentioned, if I add a FormattedWrapper in my component, then it works:

class MainScreen extends Component {
  render() {
    return (
 <FormattedWrapper locale={this.props.state.Language.language} messages={messages}>
      <ContainerView>
        <TitleText text={<FormattedMessage
            message="Welcome"/>} />
      </ContainerView>
</FormattedWrapper>
    );
  }
}

Same version without the FormattedWrapper:

class MainScreen extends Component {
  render() {
    return (
      <ContainerView>
        <TitleText text={<FormattedMessage
            message="Welcome"/>} />
      </ContainerView>
    );
  }
}

Am I doing something wrong?

@joshswan
Copy link
Owner

joshswan commented Apr 6, 2018

I don't see anything super obvious, but I just tried a basic setup loosely based on yours and had no issue. So there must be some issue somewhere in your hierarchy. You definitely shouldn't need to put FormattedWrapper in more than one place. Are you sure you have messages set up for your different locales?

You can also try putting a test component in to see what the current locale is, i.e.

import React, { Component } from 'react';
import { PropTypes as GlobalizePropTypes } from 'react-native-globalize';
import { Text } from 'react-native';

class Test extends Component {
  static contextTypes = {
    globalize: GlobalizePropTypes.globalizeShape,
  }

  render() {
    return (
      <Text>{this.context.globalize.globalize.cldr.locale}</Text>
    );
  }
}

// The above component will output a Text node with the locale string (e.g. en). Place it somewhere and you can see what's being propagated.

class MainScreen extends Component {
  render() {
    <ContainerView>
      <Test />
    </ContainerView>
  }
}

@AndersGerner
Copy link

@joshswan

New screenshot

CODE:

import React, { Component } from 'react';
import { StatusBar, Platform, Text } from 'react-native';
import { Provider, connect } from 'react-redux';
import { ThemeProvider } from 'styled-components';
import styled from 'styled-components/native';
import { PropTypes as GlobalizePropTypes, FormattedWrapper } from 'react-native-globalize';

import messages from './Messages';
import store from './store';

import Navigator from './Navigator';
import { colors } from './utils/constants';

const Root = styled.View`
flex: 1;
background-color: ${props => props.theme.BACKGROUND_GREY};
`;

const StatusBarAndroid = styled.View`
height: 24;
background-color: ${props => props.theme.HEADER_DARK};
`;

class Test extends Component {
  static contextTypes = {
    globalize: GlobalizePropTypes.globalizeShape,
  }

  render() {
		console.log("globalize",this.context.globalize.globalize.cldr.locale);
    return (
      <Text>{this.context.globalize.globalize.cldr.locale}</Text>
    );
  }
}

class RootContainer extends Component {
  render() {
		const locale = this.props.locale;
		console.log("RootLocale",locale);
  return (
      <ThemeProvider theme={colors}>
        <FormattedWrapper locale={locale} messages={messages}>
          <Root>
            <StatusBar barStyle='light-content' backgroundColor='transparent' translucent />
						{ Platform.OS === 'android' && Platform.Version >= 20 ? <StatusBarAndroid /> : null }
						<Test />
            <Navigator />
          </Root>
        </FormattedWrapper>
			</ThemeProvider>

    );
  }
}

const mapStateToProps = (state) => ({
	state,
	locale: state.Language.language
});

const ConnectedRootContainer = connect(mapStateToProps,null)(RootContainer);
class App extends Component {
  render() {
    return (
      <Provider store={store}>
			  <ConnectedRootContainer />
      </Provider>
    );
  }
}

export default App;

Messages:

const messages = {
	en: {
		Welcome: 'Welcome!!',
		Settings: 'Settings'
	},
	es: {
		Welcome: 'Bienvenido',
		Settings: 'Configuraciones'
	},
  };
export default messages;

It is in fact changed :)

I'm using Expo.io by the way, but the lib should be compatible? :)

@AndersGerner
Copy link

@joshswan By the way, it's an open source boilerplate I'm trying to contribute to: https://github.com/ipeedy/react-native-boilerplate

I have gotten my initial version merged, but I would really like to finish it off with the correct change of language :D

@joshswan
Copy link
Owner

joshswan commented Apr 6, 2018

If the locale is propagating correctly, then the issue is probably elsewhere. Try putting the test component in your screen and also try rendering the FormattedMessage directly in the component instead of as a prop of TitleText.

@AndersGerner
Copy link

screen shot 2018-04-07 at 19 58 54

screen shot 2018-04-07 at 19 59 00

@joshswan It's changed back to "en" right after.

The render code for that screen is:

  render() {
    return (
        <ContainerView>
          <TitleText><FormattedMessage
            message="Settings"
          /></TitleText>
	<Text><FormattedMessage
            message="Settings"/></Text>
	<FormattedMessage
            message="Settings"/>
	<Test />
        <Button text="Change language to es" onPress={() => {this.props.changeLanguage('es')}} />
        </ContainerView>
    );
  }

I really appreciate you trying to help me solve this! Do you have any ideas why this happens?

@joshswan
Copy link
Owner

joshswan commented Apr 7, 2018

I dug into this a bit deeper and the issue here is the use of PureComponent throughout react-navigation and the flaws of the old context API in React. This should be fixed by the new context API in 16.3, but this package still needs to be updated to support it.

In the meantime, if you remove the FormattedWrapper in App.js, the language change in screens/Settings.js will start working. Basically the workaround would be to put FormattedWrapper at the top level of the stack after the navigator. There may also be a way of forcing the react-navigation components to update, but I didn't look too deep into that.

@AndersGerner
Copy link

Thanks for the update.

I'm trying to figure out how it works with adding a component to the top of the stack in React-Navigator.

Do you have any input of how to do that? I really think it would be nice, if we could somehow sum up this issue, and add a small "Troubleshoot" part to the README.md :)

@joshswan
Copy link
Owner

joshswan commented Apr 9, 2018

v2.0.0 has just been published which uses the new React Context API. This fixes the issues you've had. However, react >= 16.3.0 is required so you'll need to update expo/react-native based on the versions I see in the repo you linked. Be sure to check out the notes in Releases because there are a couple of breaking changes and new features.

@AndersGerner
Copy link

@joshswan AWESOME!

I have swapped the FormattedWrapper with a FormattedProvider and then it start complaining about context.changedBits.
See:
screen shot 2018-04-09 at 11 23 57

Code:

class RootContainer extends Component {
  render() {
		const locale = this.props.locale;
		console.log("RootLocale",locale);
  return (
		<ThemeProvider theme={colors}>
		  <FormattedProvider locale={locale} messages={messages}>
          <Root>
            <StatusBar barStyle='dark-content' backgroundColor='transparent' translucent />
						{ Platform.OS === 'android' && Platform.Version >= 20 ? <StatusBarAndroid /> : null }
            <Navigator />
          </Root>
			</FormattedProvider>
		</ThemeProvider>
    );
  }
}

App.js:30 is: <FormattedProvider locale={locale} messages={messages}>

All dependencies are updated:

  "dependencies": {
    "expo": "26.0.0",
    "react": "16.3.1",
    "react-native": "github:expo/react-native#sdk-26.0.0",
    "react-native-globalize": "2.0.0",
    "react-navigation": "1.5.11",
    "react-redux": "5.0.7",
    "redux": "3.7.2",
    "redux-logger": "3.0.6",
    "redux-thunk": "2.2.0",
    "styled-components": "3.2.5"
  }

@joshswan
Copy link
Owner

joshswan commented Apr 9, 2018

React >= 16.3.1 and React Native >= 0.55.0 are required. Expo SDK 26 is still on RN 0.54. Should be an update soon based on their update history.

@AndersGerner
Copy link

AndersGerner commented Apr 9, 2018

For Expo, these are the correct dependency versions:

"dependencies": {
   "expo": "26.0.0",
   "react": "16.3.0-alpha.1",
   "react-native": "https://github.com/expo/react-native/archive/sdk-26.0.0.tar.gz",
   "react-native-globalize": "2.0.0",
   "react-navigation": "1.5.8",
   "react-redux": "5.0.7",
   "redux": "3.7.2",
   "redux-logger": "3.0.6",
   "redux-thunk": "2.2.0",
   "styled-components": "3.2.5"
 }

Souce: https://blog.expo.io/expo-sdk-v26-0-0-is-now-available-2be6d9805b31

Thank you so much for you help @joshswan !! The app works so great now!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants