Giter Site home page Giter Site logo

Comments (29)

henrylingumi avatar henrylingumi commented on May 2, 2024 25

Hi all,

I came across this issue, and while it is old/closed I felt I might see if there's any new perspective from the React Intl team on how to allow users to select another language?

from formatjs.

Tomekmularczyk avatar Tomekmularczyk commented on May 2, 2024 6

Hi guys, this is what I came up with with a new React Context API. Pure React solution, no redux required. Tell me what you think:

IntlContext.jsx

import React from "react";
import Types from "prop-types";
import { IntlProvider, addLocaleData } from "react-intl";
import en from "react-intl/locale-data/en";
import de from "react-intl/locale-data/de";
import deTranslation from "../../lang/de";
import enTranslation from "../../lang/en";

addLocaleData([...en, ...de]);

const { Provider, Consumer } = React.createContext();

class IntlProviderWrapper extends React.Component {
  constructor(...args) {
    super(...args);

    this.switchToEnglish = () =>
      this.setState({ locale: "en", messages: enTranslation });

    this.switchToDeutsch = () =>
      this.setState({ locale: "de", messages: deTranslation });

    // pass everything in state to avoid creating object inside render method (like explained in the documentation)
    this.state = {
      locale: "en",
      messages: enTranslation,
      switchToEnglish: this.switchToEnglish, 
      switchToDeutsch: this.switchToDeutsch 
    };
  }

  render() {
    const { children } = this.props;
    const { locale, messages } = this.state;
    return (
      <Provider value={this.state}>
        <IntlProvider
          key={locale}
          locale={locale}
          messages={messages}
          defaultLocale="en"
        >
          {children}
        </IntlProvider>
      </Provider>
    );
  }
}

export { IntlProviderWrapper as IntlProvider, Consumer as IntlConsumer };

Main App.jsx component:

import { Provider } from "react-redux";
import {  IntlProvider } from "./IntlContext";

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <IntlProvider>
          ...
        </IntlProvider>
      </Provider>
    );
  }
}

LanguageSwitch.jsx

import React from "react";
import { Text, Button } from "native-base";
import { IntlConsumer } from "../IntlContext";

const LanguageSwitch = () => (
  <IntlConsumer>
    {({ switchToEnglish, switchToDeutsch }) => (
      <React.Fragment>
        <Button onPress={switchToEnglish}>
          <Text>English</Text>
        </Button>
        <Button onPress={switchToDeutsch}>
          <Text>Deutsch</Text>
        </Button>
      </React.Fragment>
    )}
  </IntlConsumer>
);

export default LanguageSwitch;

@ericf I would like to hear your thoughts.

I've put the idea into the repository

from formatjs.

ericf avatar ericf commented on May 2, 2024

We haven't written an a specific guide for how we recommend implementing a user-selectable language feature — as that's more application specific. We do provide details on automatically determining the locale.

But once we have the locale it can simply be passed as a prop to a React component that uses the ReactIntlMixin mixin. We recommend that you add this mixin to your top-level app component as it will allow any other component anywhere in your React component hierarchy which also uses the mixin to be able to access the locale. i.e. you don't have to thread through the locale through every component.

I'm not sure if this answers your question… if not, could you provide more details in the issue description?

from formatjs.

alanhogan avatar alanhogan commented on May 2, 2024

Thanks, that’s helpful. I guess what’s odd to me about the ReactIntlMixin “API” is that the top-level usage (which I am following) accepts a "locales" array, as well as a "messages" object. (which seems to work without the top level of the object being a locale, e.g., top level can be "buttons" and "errors", not "en" and "fr")… so… I’m confused by the semantics of passing multiple "locales" but one locale's messages. Maybe I should be adding a top-level locale key?

I see from the guide you linked to that I am supposed to chose a locale automatically for the user. But then if I have passed multiple locales to the React-Intl mixin, how does it know which one locale it should be using?

from formatjs.

alanhogan avatar alanhogan commented on May 2, 2024

I see that the React-Intl mixin makes the following available to children thu context:

  • formats
  • locales
  • messages

But what about the 1 locale that you can actually use at a time?

from formatjs.

caridy avatar caridy commented on May 2, 2024

@alanhogan all the low level APIs (e.g. Intl.numberFormat()) expect a list of locales to match that with the available locales in the runtime (or the polyfill CLDR data) to compute the most common denominator, more details here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat

from formatjs.

alanhogan avatar alanhogan commented on May 2, 2024

@caridy I think I see. So, correct me if I am wrong, but an optimal implementation might look like:

  1. Guess from the browser (or remember from preferences) the user's locale
  2. Load that locale’s messages into the browser, and set only one locale as the locales prop, or that one locale (first) and others as fallbacks. So, ['fr-CA','fr-US','en-US'] ?
  3. If the user picks another locale, initiate load of the required messages
  4. On load, replace the locales prop with the new locale-with-fallbacks (maybe just ['en-US'] in this example) and the messages prop with the new messages

Yeah?

from formatjs.

caridy avatar caridy commented on May 2, 2024

@alanhogan

  1. guess from request, many app frameworks will do that for you, e.g.: expressjs uses this: https://github.com/jshttp/negotiator/#accept-language-negotiation).
  2. messages has nothing to do with the locales since you only load the messages in a particular locale (the one resolved by the server). it is true that inside a message you might format numbers and other stuff, in which case it will have to use the locales computed when producing the page.
  3. switching locales on the fly is complicated, I haven't see a real use-case for this in years, you switch language, the app is reloaded :)
  4. keep in mind that you have to load the locale-data for the polyfill when needed, plus the messages, plus the locale-data for react-intl.

from formatjs.

alanhogan avatar alanhogan commented on May 2, 2024

Right… true. Thanks very much.

from formatjs.

ericf avatar ericf commented on May 2, 2024

@alanhogan the one vs many confusion is something we can work to fix — I agree that it makes it hard to understand that when the prop's name is locales (plural). Maybe we could do something like:

Have two props locale and fallbackLocales, where fallbackLocales is more of an advanced feature and not introduced in the intro docs. Under the the mixin could do this:

this.props.locales = [this.props.locale].concat(this.props.fallbackLocales);

Essentially the idea would to make locales the singular locale since that's the main use case as @caridy is explaining above.

from formatjs.

caridy avatar caridy commented on May 2, 2024

I think we should stick to locales, and be more explicit on how this piece works, essentially the browser (and the polyfill) covers a subset of the locales that you can use, and that should be understood for anyone doing i18n.

from formatjs.

gpbl avatar gpbl commented on May 2, 2024

@caridy

switching locales on the fly is complicated, I haven't see a real use-case for this in years, you switch language, the app is reloaded :)

This should be trivial, since props can be reset with setProps() to render again the components tree. There's also a working solution when using react-router.

from formatjs.

ericf avatar ericf commented on May 2, 2024

@gpbl switching the locales prop will be trivial, but what @caridy is talking about is loading the locale data on demand.

When you combine the locale data for the Intl.js polyfill, Format.js, and your app's strings it will be large enough that you'll only want to load this data for the user's current locale. Switching the locale on the fly means that you'll need to implement a script loader that makes sure the locale data for the new locale is loaded before re-rendering the page in React.

To complicate things more, the Intl.js polyfill should ideally only be loaded in the browsers that actually need it. We're working this make Intl.js available through Polyfill.io.

from formatjs.

gpbl avatar gpbl commented on May 2, 2024

@ericf I see what you mean. However it's not an uncommon task to require modules asynchronously, for example with webpack's require.ensure().

As for the missing Intl, in my own implementation I thought to create, for each language, two different webpack chunks: one with just the strings, and the other with the app strings and the Intl's locale-data. According to the browser's support, I'd load async one or the other:

function switchLocale(i18n) {
  // Render the component
  this.setProps({messages: i18n.messages, locales: i18n.locales});
);

function switchToJapanese() {
  if (has('Intl'))
    require.ensure(['../i18n/jp'], function(require){
       // Now we have loaded japanese data
       var jp = require('../i18n/jp');
       switchLocale(jp);
    });
  else
    require.ensure(['../i18n/jp-with-intl'], function(require){
       var jp = require('../i18n/jp-with-intl'); // Load Intl locale-data as well
       switchLocale(jp);
    });
}

Not sure if there are better ways for doing this, anyway :-) Since I don't believe dynamic requires would work, I need to explicitly specify the requires for each language.

I still have to check if polyfills.io may help, but I believe if using web pack it wouldn't.

from formatjs.

ericf avatar ericf commented on May 2, 2024

I still have to check if polyfills.io may help, but I believe if using web pack it wouldn't.

Modern browsers (with the exception of Safari) don't need the Intl.js polyfill, and it's pretty large (14kb for the polyfill + data for a locale), so this is why we want to get this on polyfill.io. We feel strongly that you should not require() polyfills as they're not the same kind of dependencies as libs or app code, and we think of them as our JS runtime requirements.

from formatjs.

gpbl avatar gpbl commented on May 2, 2024

@ericf I don't see why it shouldn't work – just make sure you "require" the polyfills and the localized strings before mounting the app, or before setting the new props.

Here is a working example of what I mean: https://github.com/gpbl/react-locale-hot-switch
This react app loads the i18n data only as the user switches the language, without reloading the page. On Safari it loads also the Intl polyfills.

What could go wrong? Maybe I'm missing an important part of the whole concept :-)

@alanhogan is this something can help you too?

from formatjs.

alanhogan avatar alanhogan commented on May 2, 2024

Yes, this is relevant to my interests, as they say on the Internet.

Thanks. I’ll be taking a look before long.

Alan

Le 21 déc. 2014 à 15:44, Giampaolo Bellavite [email protected] a écrit :

@ericf I don't see why it shouldn't work – just make sure you "require" the polyfills and the localized strings before mounting the app, or before setting the new props.

Here is a working example of what I mean: https://github.com/gpbl/react-locale-hot-switch
This react app loads the i18n data only as the user switches the language, without reloading the page. On Safari it loads also the Intl polyfills.

What could go wrong? Maybe I'm missing an important part of the whole concept :-)

@alanhogan is this something can help you too?


Reply to this email directly or view it on GitHub.

from formatjs.

ericf avatar ericf commented on May 2, 2024

@gpbl I wasn't saying it's not possible, just qualifying hot-swap locale switching as being complex rather than trivial :)

It also never felt like a strong requirement to support hot-swapping locales without reloading the page. But that may be because our apps also support server-side rendering.

from formatjs.

gpbl avatar gpbl commented on May 2, 2024

@ericf Actually the reason I landed to hot switching the locale was because my projects also start from a server rendered page.

This also comes easy with webpack. When naming the i18n chunks during the build process, we can <script>-load the localised data without requiring it before the mount.

This way we can have the same URL for different languages. Crawlers will still see the alternate localised domain (e.g. fr.example.com), but we can now avoid showing the user a foreign language on a localised url (common case when it has been shared on a social network). In fact with different urls for different languages, english users may reach the french domain seeing it in english (= wrong and confusing) or in french (= lost visitor).

Hot switching the locale is not mandatory but it's nice to have and I still believe requires little effort. Plus it's a benefit for our translators :-)

from formatjs.

caridy avatar caridy commented on May 2, 2024

ok ok, there is nothing (as far as I can tell) that will prevent you for doing all those things (even though we think those are crazy things jejejeje). The internal cache system used by react-intl will take in consideration the locales, and therefore if you change locales, formats and messages values, it will automatically create a new internal instance in cache when calling render().

As for the use of context under the hood to resolve locales, you can always avoid that by passing those props into every component in your application, that should do the trick since it will automatically resolve those values without trying to do so thru the context.

from formatjs.

sebastienbarre avatar sebastienbarre commented on May 2, 2024

Thanks for all the pointers, guys. I have to admit I have a tough time making this work with Safari & webpack. If I use require.ensure to load intl from a separate chunk, it seems that it "finishes" loading it after the react-intl's mixins is evaluated from its own chunk, which results in an error because it is referencing intl already. I've described the problem in gpbl/react-locale-hot-switch#2.

from formatjs.

ericf avatar ericf commented on May 2, 2024

@sebastienbarre not being able to use dependencies when a module is being executed seems like a problem with Webpack or how it's being configured. How are you conditionally loading other polyfills your app uses?

from formatjs.

sebastienbarre avatar sebastienbarre commented on May 2, 2024

@ericf so far I had bit the bullet and included my polyfills in my vendor chunk, because they were pretty small. intl is... rather large. Using require('intl') brings a whopping 850+KB to the plate for some reasons (before minification), using require('intl/Intl') and just the en-US locale is about 150+KB, still pretty big, hence me trying to get by with require.ensure on that one...

from formatjs.

ericf avatar ericf commented on May 2, 2024

The Intl polyfill + en-US is ~17kb gz IIRC. Hopefully we can land it in Polyfill.io which will make loading it and your other polyfills much simpler: polyfillpolyfill/polyfill-service#108

from formatjs.

sebastienbarre avatar sebastienbarre commented on May 2, 2024

Yes, ultimately when UglifyJS has done its job and the server is gzipping, we are down to much smaller numbers. But I've to admit my surprise when I first tried require('intl') and saw my vendor chunk inflate so much. Looking forward what will happen with Polyfill.io. Thanks.

from formatjs.

gpbl avatar gpbl commented on May 2, 2024

@sebastienbarre my bad: the react-locale-hot-switch app wasn't using the react-intl@latest, which indeed requires Intl in the global scope. So you need to require react-intl in the webpack's ensure callback, hence after the polyfill has been loaded (see updated project).

from formatjs.

sebastienbarre avatar sebastienbarre commented on May 2, 2024

@gpbl I see. Thanks a lot for the quick reply. It seems the change was made by @ericf in e188cc1.

exports["default"] = {
[...]
    getNumberFormat  : intl$format$cache$$["default"](Intl.NumberFormat),
    getDateTimeFormat: intl$format$cache$$["default"](Intl.DateTimeFormat),
    getMessageFormat : intl$format$cache$$["default"](intl$messageformat$$["default"]),
[...]
    _format: function (type, value, options, formatOptions) {
[...]
        switch(type) {
            case 'date':
            case 'time':
                return this.getDateTimeFormat(locales, options).format(value);
            case 'number':
                return this.getNumberFormat(locales, options).format(value);
            case 'relative':
                return this.getRelativeFormat(locales, options).format(value, formatOptions);
            default:

My gut feeling is that people will use the formatDate, formatNumber, etc. functions, not getNumberFormat, etc. directly. Could they be initialized later? in _format maybe? Or simply turned into functions and a call to apply?

It's not a huge issue, but the problem here is that it involves loading yet one more chunk, thus requiring yet another round-trip to the server. Performance-wise, you would want to avoid that.
For example:
index.html: load bootstrap.js using a <script> tag.
bootstrap.js: load Intl conditionally, using require.ensure, which puts intl in its own chunk,
bootstrap.js: now that we have Intl (whether we had it already or not), load app.js using require, which will require('react-intl'), etc.
The consequence of this, is that webpack is going to create a separate chunk for bootstrap.js, intl, and app.js (since app.js is called in two different code paths). That's 3 round-trips. Previously we could get by with 2 round-trips. @ericf ?

Again, thanks for the all the hard work.

from formatjs.

ericf avatar ericf commented on May 2, 2024

My gut feeling is that people will use the formatDate, formatNumber, etc. functions, not getNumberFormat, etc. directly. Could they be initialized later? in _format maybe? Or simply turned into functions and a call to apply?

This is like saying I can't access Math eagerly in my module and have to wait until a function is invoked before I can access it. The Intl APIs must be available before the code for React Intl is executed. This sounds like it will be a problem for Webpack for any polyfill you conditionally load in your app. This is why the Polyfill.io solution is so attractive — you simpye include a script to polyfill the runtime, then load your app code.

The consequence of this, is that webpack is going to create a separate chunk for bootstrap.js, intl, and app.js (since app.js is called in two different code paths). That's 3 round-trips. Previously we could get by with 2 round-trips. @ericf ?

I would assume Webpack loads the intl.js and app.js chunks in parallel. This extra request for intl.js is only for browsers which don't have the Intl APIs built in. If your app only has three JavaScript HTTP requests you're doing really well!

from formatjs.

sebastienbarre avatar sebastienbarre commented on May 2, 2024

Agreed, 3 separate requests wouldn't be too bad, I guess I was curious as to why I suddenly needed an extra one. I'm wholeheartedly with you on this one:

The Intl APIs must be available before the code for React Intl is executed.

but I think that might be where my confusion is here, I'm not executing any react-intl code here, at all. I'm only loading my vendor bundle/chunk, where all the third-party exports are. I noticed that issue in an app where only one page uses i18n. The other pages do not. What I'm hinting at is that my user might never route to that i18n page, but the whole app fails to load now because my vendor chunk bundles 'react-intl', which makes this explicit reference to Intl. The functionality exposed by react-intl is not executed/called at that point and might never be.

I've to admit I've not have had that issue with other polyfills before, that's why I would put them in my vendor chunk (small so far). No worries, but I figured I might not be the only one bumping into that, might as well document it.

from formatjs.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.