erikras / react-redux-universal-hot-example

A starter boilerplate for a universal webapp using express, react, redux, webpack, and react-transform
MIT License
11.99k stars 2.5k forks source link

I18N Support #624

Open louy2 opened 8 years ago

louy2 commented 8 years ago

Internet is international so I think a good framework cannot go far without i18n (or because I myself am bilingual but whatever). People has done great job on this and I'd like to refer to those with respect:

This issue hope to track further progress and discussion on the issue.

hank7444 commented 8 years ago

Hi @louy2, I got a new job this week and I am adapting, I will continue figure this feature soon, thx for your attention :dog:

catamphetamine commented 8 years ago

@louy2 I got it working with react intl v2. Some tips:

louy2 commented 8 years ago

@halt-hammerzeit So each component defines messages it needs and the actual messages are still defined elsewhere. What do you think about define once and use anywhere?

catamphetamine commented 8 years ago

@louy2 Each component defines messages and those are what are gonna be the labels in English (=default). Then you create separate bulk translation files for other languages.

A major pain-point we faced at Yahoo which every app experienced 
was not wanting to wait for new translations to be finished before deploying, 
or placeholders like {name} getting translated to {nombre} accidentally.

https://github.com/yahoo/react-intl/issues/162 "Default Message Declaration and Extraction"

Whatever

quicksnap commented 8 years ago

@halt-hammerzeit this is nearly exactly what I have done in my project today with React Intl v2. I took a different direction with the polyfills, following more closely the examples found on Intl.js and Format.JS website:


const component = (
  <IntlProvider locale={store.yaddayadda} messages={{ /* stuff from your store */}} >
    <ReduxRouter routes={getRoutes(store)} />
  </IntlProvider>
);

if (!window.Intl) {
  require.ensure([
    'intl',
    'intl/locale-data/jsonp/en.js',
    'intl/locale-data/jsonp/es.js',
    'intl/locale-data/jsonp/fr.js',
    'intl/locale-data/jsonp/de.js',
    'intl/locale-data/jsonp/it.js',
    'intl/locale-data/jsonp/ja.js',
  ], (require) => {
    require('intl');
    require('intl/locale-data/jsonp/en.js');
    require('intl/locale-data/jsonp/es.js');
    require('intl/locale-data/jsonp/fr.js');
    require('intl/locale-data/jsonp/de.js');
    require('intl/locale-data/jsonp/it.js');
    require('intl/locale-data/jsonp/ja.js');

    renderApp();
  });
} else {
  renderApp();
}

function renderApp() {
  ReactDOM.render(
    <Provider store={store}>
      {component}
    </Provider>,
    dest
  );
YarivGilad commented 8 years ago

Hi all, so given that 3 or 4 contributors suggested an implementation for i18n in the project What keeps the moderator from integrating one of them? This is a meaningfull and essential feature.

bbilger commented 8 years ago

+1

YarivGilad commented 8 years ago

How can we promote this subject? Months go by and existing PRs are not being integrated. Can anyone point on the reason for this critical feature not integrated yet??? we can +1 for months without any response or check out other implementations like http://mern.io/ that do include i18n support out of the box... What are your thoughts?

louy2 commented 8 years ago

Well it is understandable IMO. If the author doesn't use the i18n personally, then he would have little interest in adding and maintaining it. It is common in open source. Using a fork listed above or some alternatives is perfectly fine IMO. Exercise your freedom.

simpleblack commented 8 years ago

This i18n example repo

https://github.com/simpleblack/react-redux-universal-hot-example

Iuriy-Budnikov commented 8 years ago

@simpleblack , how to switch between langs on client / server depends on params in url ?

andreimc commented 8 years ago

@Iuriy-Budnikov I load my messages into redux based on the url I read the lang from query string and it stays in that language ping me if you need more examples. https://hk.housemeerkat.com/ at the bottom.

It's not optimal for big sites cause I load all the messages once but you can split it per page.

here is what I do:

setLocale.js (express app that sets my locale either from url or query string):

import config from '../config';

const setLocale = (req, res, next) => {
  const currentCountry = req.hostname.split('.')[0];
  const countryConfig = config.countries[currentCountry] || config.countries.default
  const countryLocales = countryConfig.locales;

  if (countryLocales != null) {
    req.locale = countryLocales[0];
  }
  // Locale can be changed by passing ?hl=<locale> in the querystring
  if (req.query.hl) {
    // But only the supported ones!
    if (countryLocales.indexOf(req.query.hl) > -1) {
      req.locale = req.query.hl;
    }
  }

  // Or by setting a `hl` cookie
  else if (req.cookies.hl) {
    if (countryLocales.indexOf(req.cookies.hl) > -1) {
      req.locale = req.cookies.hl;
    }
  }

  next();
};

export default setLocale;

in server.js

  store.dispatch(loadIntlMessages(locale, country));

and this is the reducer:

const LOAD_INTL_SERVER = 'intl/LOAD_INTL_SERVER';

const initialState = {
  currentLocale: 'en-AU',
  country: 'AU',
  messages: {},
};

export default function select(state = initialState, action = {}) {
  switch (action.type) {
    case LOAD_INTL_SERVER:
      return {
        ...state,
        messages: action.messages,
        currentLocale: action.locale,
        country: action.country,
      };
    default:
      return state;
  }
}

export function loadIntlMessages(locale, country) {
  const messages = require(`../../../build/lang/${locale}.json`);
  return {
    type: LOAD_INTL_SERVER,
    country: country,
    locale: locale,
    messages: messages
  };
}

it is only ever called once on the server but can be extended to load json dynamically on each page load.

basically in code I use defineMessage and I extract the messages with webpack react intl and put them in files.

I'm happy to help if you have any more queries.

Regards. Andrei

Iuriy-Budnikov commented 8 years ago

@andreimc thanks for quick response. I found some quick solutions with example @simpleblack

Here my code. Maybe it will helps somebody

i18n-server.js

import i18n from 'i18next';
import Backend from 'i18next-node-fs-backend';
import { LanguageDetector } from 'i18next-express-middleware';

i18n
  .use(Backend)
  .use(LanguageDetector)
  .init({
    detection: {
      order: ['querystring', 'cookie', 'header'],
      // keys or params to lookup language from
      lookupQuerystring: 'lang',
      lookupCookie: 'i18next',

      // cache user language
      caches: false // ['cookie']
    },

    whitelist: ['en', 'ar'],
    fallbackLng: 'en',

    // have a common namespace used around the full app
    ns: ['common'],
    defaultNS: 'common',

    debug: true,

    interpolation: {
      escapeValue: false // not needed for react!!
    },

    backend: {
      loadPath: 'locales/{{lng}}/{{ns}}.json',
      jsonIndent: 2
    }
  });

export default i18n;

i18n-client.js

import i18n from 'i18next';
import { LanguageDetector } from 'i18next-express-middleware';

i18n
  .use(LanguageDetector)
  .init({
    detection: {
      order: ['querystring', 'cookie', 'header'],
      // keys or params to lookup language from
      lookupQuerystring: 'lang',
      lookupCookie: 'i18next',
      // cache user language
      caches: false // ['cookie']
      // optional expire and domain for set cookie
    },
    whitelist: ['en', 'ar'],
    fallbackLng: 'en',

    // have a common namespace used around the full app
    ns: ['common'],
    defaultNS: 'common',

    interpolation: {
      escapeValue: false // not needed for react!!
    }
  });

export default i18n;