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

i18n support DOCS with ReactOnRails using "RailsContext" #212

Closed
justin808 opened this issue Jan 19, 2016 · 39 comments
Closed

i18n support DOCS with ReactOnRails using "RailsContext" #212

justin808 opened this issue Jan 19, 2016 · 39 comments

Comments

@justin808
Copy link
Member

Eventually, we want to do something for supporting i18n.

Maybe there’s some file that gets built for the JS code.

Maybe we should integrate/support: https://github.com/fnando/i18n-js?

Here's on a SO topic on Rails integration for client side:
http://stackoverflow.com/a/10610347/1009332

CC: @callumacrae

@justin808
Copy link
Member Author

Might be just a doc thing where you use i18n-js to build the file and put this into your webpack bundles.

@thewoolleyman
Copy link
Contributor

I'd like to see this solution leverage the i18n support that's already built into rails. So the translations can all live in one place.

Whatever is used on the JS side, you can imagine a translation layer that just loads the rails YAML files containing the translations, then - ahem - translates them into whatever JSON format is needed for the client/react side.

I'm doing exactly this with Rails fixtures, so I can use fixture_builder to autogenerate fixtures in Rails, then have a small javascript shim/proxy module that just reads the Rails YAML fixtures directly from the filesystem, parses the into a JS object, and exposes them into whatever needs it on the JS side.

@justin808
Copy link
Member Author

@robwise
Copy link
Contributor

robwise commented Jan 21, 2016

I definitely agree with @thewoolleyman and @rstudner (on the other issue) that we should leverage the rails i18n support.

@martyphee
Copy link
Contributor

I would rather see a pluggable interface. We use fast_gettext and Rails I18n wouldn't help us.

As an example react_rails has React::ServerRendering::ExecJSRenderer. This allows us to inject the proper locale specific JS file.

My initial idea would be to put a call to a plugin (prerend) right before here which would alllow the plugin to add additional JS.

There's also this: https://github.com/webpack/i18n-webpack-plugin which we've considered.

@lucke84
Copy link
Contributor

lucke84 commented Jan 27, 2016

+1

This is what some people are doing with react_rails and js-i18n: https://gist.github.com/jhilden/44378421ad54e617b900

@martyphee
Copy link
Contributor

I was going to look at implementing the same behavior with a prerender hook.

On Wed, Jan 27, 2016 at 5:54 AM, Luca Canducci [email protected]
wrote:

+1

This is what some people are doing with react_rails and js-i18n:
https://gist.github.com/jhilden/44378421ad54e617b900


Reply to this email directly or view it on GitHub
#212 (comment)
.

@lucke84
Copy link
Contributor

lucke84 commented Jan 27, 2016

The prerender hook is fine for setting up the locale per each request (I guess you mean that?) but I'm wondering how to make the files required per each locale (in case of js-18n they'd be translations.js and i18n.js).

To be honest, I'd be happier to have a solution which is only driven by Rails locale files, although you will still need to get the formatting rules (e.g. numbers, dates) somewhere reliable (say Yahoo's Intl.js); either way, you need to provide the server side rendering with those file/info.. not sure if that's what you have in mind.

@martyphee
Copy link
Contributor

We could create a default Renderer which handles Rails i18n, but my project
uses Gettext and we need to be able to inject different files.

On Wed, Jan 27, 2016 at 6:54 AM, Luca Canducci [email protected]
wrote:

The prerender hook is fine for setting up the locale per each request (I
guess you mean that?) but I'm wondering how to make the files required per
each locale (in case of js-18n they'd be translations.js and i18n.js).

To be honest, I'd be happier to have a solution which is only driven by
Rails locale files, although you will still need to get the formatting
rules (e.g. numbers, dates) somewhere reliable (say Yahoo's Intl.js);
either way, you need to provide the server side rendering with those
file/info.. not sure if that's what you have in mind.


Reply to this email directly or view it on GitHub
#212 (comment)
.

@justin808
Copy link
Member Author

@martyphee @lucke84 @thewoolleyman The best way to evaluate this is to create a super simple example from the generated app, or maybe on the https://github.com/shakacode/react-webpack-rails-tutorial/

@martyphee
Copy link
Contributor

Has anyone done work on this yet? I'm in London this week talking to
developers and we're looking at moving from react_rails to
react_on_rails.

If not I'll see if I can do something tonight.

@lucke84 https://github.com/lucke84 @thewoolleyman
https://github.com/thewoolleyman

On Thu, Jan 28, 2016 at 5:33 AM, Justin Gordon [email protected]
wrote:

@martyphee https://github.com/martyphee @lucke84
https://github.com/lucke84 @thewoolleyman
https://github.com/thewoolleyman The best way to evaluate this is to
create a super simple example from the generated app, or maybe on the
https://github.com/shakacode/react-webpack-rails-tutorial/


Reply to this email directly or view it on GitHub
#212 (comment)
.

@lucke84
Copy link
Contributor

lucke84 commented Feb 16, 2016

We're working on a solution but atm it's rather custom. If it proves to be good enough, we'll try to extract a general one and happily share it with everybody. This could take a while though, so I suggest others give it a try as well 😄

@martyphee
Copy link
Contributor

Sure, we need this for our POC. We're currently using it to inject Jed
i18n files based on the locale (we're in a lot of countries and some
support multiple languages). Eventually it would be nice to replace text
in the JS files and just serve the country specific one.

On Tue, Feb 16, 2016 at 3:15 PM, Luca Canducci [email protected]
wrote:

We're working on a solution but atm it's rather custom. If it proves to be
good enough, we'll try to extract a general one and happily share it with
everybody. This could take a while though, so I suggest others give it a
try as well [image: 😄]


Reply to this email directly or view it on GitHub
#212 (comment)
.

@martyphee
Copy link
Contributor

https://gist.github.com/martyphee/2a36d9429cb084228c7b

This adds before_render and after_render hooks.

We're currently using Jed as our GetText implementation.

On Tue, Feb 16, 2016 at 3:15 PM, Luca Canducci [email protected]
wrote:

We're working on a solution but atm it's rather custom. If it proves to be
good enough, we'll try to extract a general one and happily share it with
everybody. This could take a while though, so I suggest others give it a
try as well [image: 😄]


Reply to this email directly or view it on GitHub
#212 (comment)
.

@martyphee
Copy link
Contributor

Has anyone had a chance to look at the gist I created?

On Thu, Feb 18, 2016 at 8:14 AM, Martin Phee [email protected] wrote:

https://gist.github.com/martyphee/2a36d9429cb084228c7b

This adds before_render and after_render hooks.

We're currently using Jed as our GetText implementation.

On Tue, Feb 16, 2016 at 3:15 PM, Luca Canducci [email protected]
wrote:

We're working on a solution but atm it's rather custom. If it proves to
be good enough, we'll try to extract a general one and happily share it
with everybody. This could take a while though, so I suggest others give it
a try as well [image: 😄]


Reply to this email directly or view it on GitHub
#212 (comment)
.

@valscion
Copy link

Maybe we should integrate/support: https://github.com/fnando/i18n-js?

Hi there! We have implemented a semi-nice integration with i18n-js with Webpack which does the following:

  1. Load all translation files (we use YAML) to a JSON serializable object

  2. Add a new I18nRuntimePlugin to webpack plugins:

    new I18nRuntimePlugin({ fullTranslations: /* translations loaded in step 1 */ })
  3. Make sure to setup placeholders and I18n.js in an entry point for webpack:

    import I18n from 'i18n-js';
    window.I18n = I18n;
    I18n.locale = /* use the current locale somehow */;
    I18n.defaultLocale = 'fi';
    // This placeholder will be replaced by the I18nRuntimePlugin
    I18n.translations = I18N_RUNTIME_TRANSLATIONS;

This workflow essentially looks up all the calls to I18n.t or I18n.translate (these can be configured, see the I18nRuntimePlugin gist) and then passes only those translations used on the JS side to the global I18n.translations object which i18n-js uses.

Feel free to use the gist, but beware that it is pretty much coded to support only our use case and we've just tested it manually that it works. We have been looking at properly open-sourcing this plugin to support this workflow, but we're not sure whether others besides us have a need for such plugin.

This is how the code using the translations look, very similar to Rails:

// With I18n.locale = 'fi';
const reviewsText = I18n.t('some_namespace.reviews', { count: reviewsCount })

The YAML translations looked like this:

---
fi:
  some_namespace:
    reviews:
      count:
        one: "%{count} arvostelu"
        other: "%{count} arvostelua"

We also create separate JS builds for all locales we support. With that, we are able to replace special translation calls compile-time to translated strings. It uses a heavily configured version of i18n-webpack-plugin to do the magic. We could open-source that component, too, if there's interest. It essentially does the following compile-time transform:

// Before
const myStr = __('some_namespace.hello_world');

// After, for main-fi.js bundle
const myStr = 'Hei, maailma!';
// And for main-en.js bundle
const myStr = 'Hello, world!';
---
fi:
  some_namespace:
    hello_world: Hei, maailma!
---
en:
  some_namespace:
    hello_world: Hello, world!

Would there be interest outside to help us develop these approaches should we open-source this? It would be awesome to be able to contribute to i18n tooling, but I'm afraid that we might not have much time to support other use cases / workflows besides ours even if we'd want to.

@rstudner
Copy link
Contributor

@valscion this is great. I had done a far more hacky version (exporting the translations.js periodically and just "using it" but his is much more elegant)

Definitely should put this out publicly! :)

@valscion
Copy link

Thanks @rstudner, I'll try to ping you when there's more news :)

@justin808
Copy link
Member Author

@valscion @rstudner @martyphee Thanks to all for your efforts here!!! 👍

@jbhatab
Copy link
Member

jbhatab commented Apr 26, 2016

Are we dropping this because of the railsContext thing we added?

@martyphee
Copy link
Contributor

The railsContext solved my issue. I use it to pass in the translations and then use those to initialize Jed.

@justin808
Copy link
Member Author

@martyphee @jbhatab Any chance that we can get this documented?

@justin808 justin808 changed the title i18n support with ReactOnRails i18n support DOCS with ReactOnRails using "RailsContext" Apr 26, 2016
@jbhatab
Copy link
Member

jbhatab commented Apr 26, 2016

@justin808 isn't the railscontext documentation more than enough since how you integrate that with your react setup is custom?

@martyphee
Copy link
Contributor

martyphee commented Apr 26, 2016

Our implementation is pretty custom. In rails we use FastGettext and customer rake tasks to pull translations from phraseapp. The rack task them builds json files which I include in the railsContext based on the current locale.

@justin808
Copy link
Member Author

I'm going to close this for now.

The solution is to use the railsContext feature. This has been proven for https://deliveroo.co.uk/.

Yes, we'd love to get some articles and examples on how to do this!

@reconstructions
Copy link
Contributor

React on Rails is amazing, and I really appreciate all the hard work everyone has put into this. It is hands down the best way to get started with React and Node coming from a Rails background. I have been able to get incredible tooling running and have been able to build and test components in a large, existing app (1500+ rspec tests) by adapting code the react-webpack-rails-tutorial, all in a single weekend. But I cannot get railsContext to work, probably because, as @jbhatab says, the react setup is custom, and there is only one usage of railsContext in that demo, and I can't figure out how to access the data in there. If I log the location const I just get Location { valueOf: [Function: valueOf] }" In my opinion, the value in this framework for experienced JS and Rails coders who are new to React is in the extraordinary demos and tooling setup help. I can't continue until I get my Rails translations passed through, one way or another. I am also trying i18-js and will work on the @valscion solution, but even a single working example of a /config/locales/en.yml translation passed in the react-webpack-rails-tutorial would be invaluable.

@justin808
Copy link
Member Author

@martyphee, can you maybe give us a hand with some tips?

@justin808
Copy link
Member Author

justin808 commented Jun 27, 2016

@reconstructions I've got examples here of the railsContext, albeit not for i18n.

Having some great docs for this would be so useful for the community! Anybody game to help?

@reconstructions
Copy link
Contributor

reconstructions commented Jun 27, 2016

I am game to help. I am making some progress now, although painfully so since I am new to ES6, React, and Redux, as well as how how you have structured your elegant implementation. I have the railsContext object passing through now, so thanks for the links. Since I have been coding JS and Rails for a long time, I think the fastest way for me to master your approach will be to build a new Todo component which matches the Egghead Redux tutorial functionality, only implemented using the React on Rails framework. I am on my way doing this in my own app, and then once I have test coverage I will checkout your demo and get it working there. So give me some time to go all out on this. After I have something I would really appreciate refactoring advice, and then I will write the process up as a tutorial which reflects all the gotcha wisdom that comes from being a newbie to ES6, React, Redux, and React on Rails. I (will) really appreciate any advice!

@justin808
Copy link
Member Author

@reconstructions the best thing for us to do is to try to implement the i18n JS functionality in the https://github.com/shakacode/react-webpack-rails-tutorial/ as an example. I'm hoping that @martyphee can provide some guidance. I can help as well.

@reconstructions
Copy link
Contributor

Sounds good, I'll do what I can.

@martyphee
Copy link
Contributor

martyphee commented Jul 1, 2016

We're not using /config/locales/en.yml files for our UI translations. We're using gettext pofiles which we generate json files from using their tooling.

module RenderingExtension
  @translations = {}

  # Return a Hash that contains custom values from the view context that will get passed to
  # all calls to react_component and redux_store for rendering
  def self.custom_context(view_context)
    {
        translations: translations(view_context.locale)
    }
  end

  def self.load_locale_file(locale)
    file_content = ::Rails.application.assets["locale/macaroon-#{locale}.json"].to_s

    file_without_trailing_semicolon = file_content.gsub(/;$/, '')

    JSON.parse(file_without_trailing_semicolon).to_hash
  end

  def self.translations(locale)
    if Rails.env.development?
      RenderingExtension.load_locale_file(locale)
    else
      @translations[locale.to_sym] ||= RenderingExtension.load_locale_file(locale)
    end
  end
end

@stuarthannig
Copy link

stuarthannig commented Aug 11, 2016

Seems to be a lot of opinions on adding RailsContext, thought I'd share a bare no-fluff approach.

client/app/bundles/Homepage/startup/HomepageClient.jsx

import React from 'react'
import Homepage from '../containers/Homepage'

export default (props, railsContext) => (
  <Homepage {...props} railsContext={railsContext} />
)

Then you can access it using props.railsContext in your Homepage (App) component.

@justin808
Copy link
Member Author

@stuarthannig any chance you can throw in a doc PR?

@stuarthannig
Copy link

stuarthannig commented Aug 13, 2016

I can try when I get time, and more understanding. But to add to the i18n-js side of things, this got the ball rolling so server side rendering didn't error out. I hope this helps others and we can get a bullet-proof solution down the road, but better to share than not (for other's sake).

Add "i18n-js": "https://github.com/fnando/i18n-js/archive/v3.0.0.rc12.tar.gz" to package.json dependancies. (Note: the package I used at time of writing may be deprecated by the time you read this)

config/initializers/react_on_rails.rb

module RenderingExtension
  def self.custom_context(view_context)
      {
        translations: I18n.backend.send(:translations)
      }
  end
end

...

client/app/bundles/Homepage/containers/Homepage.js

import React, { PropTypes, Component } from 'react'
import I18n from 'i18n-js'

const scope = 'react.containers.homepage'

class Homepage extends Component {
  constructor(props) {
    I18n.translations  = props.railsContext.translations
    I18n.defaultLocale = props.railsContext.i18nDefaultLocale
    I18n.locale        = props.railsContext.i18nLocale
    I18n.fallbacks     = true
    super(props)
  }

render() {
    return (
      <div>
        { I18n.t([scope, 'some_key_to_translate']) }
      </div>
    }
  }
}

...

You'll need to add import I18n from 'i18n-js' to any child components for the container, but you don't need to construct the I18n global object anymore (like we did in the constructor, that's a one time thing obviously). The reason we did it there is because railsContext is passed in as a prop to the container app.

@justin808
Copy link
Member Author

@Judahmeek This would be a great addition to our gitbook!

@Judahmeek
Copy link
Contributor

@justin808 A lot of discussion here. By "this", are you referring to Stuart's comments here at the end?

@stuarthannig Any chance you and I could connect on Slack, Skype, or another IRC-like platform so I could get a better understanding of your solution?

@justin808
Copy link
Member Author

@Judahmeek yes to your question directed to me.

@justin808
Copy link
Member Author

CC @stuarthannig @Judahmeek

FYI, this PR seems like a good example of i18n:

shakacode/react-webpack-rails-tutorial#340

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

No branches or pull requests