synacor / preact-i18n

Simple localization for Preact.
BSD 3-Clause "New" or "Revised" License
205 stars 18 forks source link

example of dynamically changing the definiotn #26

Closed OrenMe closed 6 years ago

OrenMe commented 6 years ago

Is this possible? can you add a sample of how to dynamically change the definition and thus causing a refresh of the text nodes in all of the children.

thanks.

billneff79 commented 6 years ago

If you use the IntlProvider component to provide the definition file, anytime you (re)render the IntlProvider it will (re)render its tree, which will update any child components with the new definitions. Thus, in order to have dynamic definitions on the screen, you just need the definition prop to IntlProvider to itself be dynamic. For example, here is a trivial example that would alternate "Hello" and "Ciao" on the screen every second:

const English = {hello: 'Hello'};
const Italian = {hello: "Ciao"};

class App extends Component {
  state = {
    definition: English
  }

  componentDidMount() {
    setInterval(() => this.setState({definition: this.state.definition === English ? Italian : English}), 1000);
  }

  render(props, {definition}) {
    return (
      <IntlProvider definition={definition}>
        <Text id="hello" />
      </IntlProvider>
    );
  }
}

In a more real-world scenario, you would get the desired language file "key" from something like a users preferences or the browsers language preference, dynamically lazy-load/require the language file, and update state appropriately with the new language file, triggering a rerender. Something like this:

import defaultLanguage from '../languages/en-us.json';

class App extends Component {

  state = { definition: defaultLanguage };

  componentWillMount() {
    let key = ??? //get preferred language file key somehow

    //use webpack dynamic import if you have all of your language files at build time - could also use fetch or another mechanism to get a remote file if you don't have them at build time
    import(`../languages/${key}.json`).then(definition => this.setState({definition}));
  }  

  render(props, {definition}) {
      <IntlProvider definition={definition}>
        <Text id="hello" />
      </IntlProvider>
  }

}

See more about webpack dynamic imports here: https://webpack.js.org/guides/code-splitting/#dynamic-imports

billneff79 commented 6 years ago

This would be good to add to the README. If anyone wants to clean it up and update it, that would be helpful

OrenMe commented 6 years ago

thanks @billneff79 , great example. Although, this is a bit of simplistic example. In my case I have complex tree structure that doesn't get re-rendered all the time. Currently I have one top-level <IntlProvider /> for my entire app, which worked OK for initial one time init. Should I use multiple <IntlProvider /> so parts which dynamically get's re-rendered will use their own scoped provider?

billneff79 commented 6 years ago

@OrenMe - you can have nested <IntlProvider /> components at different levels in the tree. The current behavior of that nesting is that it is purely additive: if a higher-up IntlProvider has key a with value foo and a lower IntlProvider has key a with value bar, then the resulting merge will have key a with value foo. If the lower IntlProvider has keys that the higher IntlProvider does not have, then they will be added to the merged definition for components to use.

The typical pattern that we use is that each widget we export from a repo is wrapped in an IntlProvider that provides the default language in that repo. That makes it easy to test the base functionality of a widget. We then have an Application level repo that imports all of the necessary named widgets and it is itself wrapped in an IntlProvider. It does not provide anything for our default language, as the base widgets already have that, but it provides all language updates for every widget in alternative languages (e.g Spanish). Any components that selective render through something like shouldComponentUpdate take account for updates to the language definition file - it's so rare that it's not a big burden.

An alternative you could pursue is a publish-subscribe mechanism (e.g. using mitt for something simple or redux for something more complex if you already have that in your codebase) to reach deep into the your tree structure to inform certain components that they need to re-render because of intl language file changes.

Either way, if you find something that works for you, we'd love to hear about it. Good luck!

OrenMe commented 6 years ago

thanks @billneff79 !