Skip to content

Instantly share code, notes, and snippets.

@jhilden
Last active October 3, 2021 10:32
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save jhilden/44378421ad54e617b900 to your computer and use it in GitHub Desktop.
Save jhilden/44378421ad54e617b900 to your computer and use it in GitHub Desktop.
Setup for using i18n-js together with react-rails i18n-js for server side prerendering

When using react-rails for an internationalized app it makes a lot of sense to use i18n-js for translations, so that you can reuse the the strings from your rails app's .yml files (and all the tooling & services that exist around that).

When you use the prerender feature of react-rails you face 2 problems:

  • The first is that translation.js & i18n.js from i18n-js need to be loaded inside the server-side JS prerendering processes, which is achieved by loading them inside the components.js.
  • The second problem is the server processes need to be aware of the current locale of each HTTP request. This is done by adding a custom renderer and using the before_render hook to configure i18n-js accordingly for each render call.
require 'i18n_js_renderer'
module MyRailsApp
class Application < Rails::Application
config.react.server_renderer = React::ServerRendering::I18nJsRenderer
end
end
//= require i18n
//= require i18n/translations
//= require_tree ./components
module React
module ServerRendering
class I18nJsRenderer < SprocketsRenderer
def before_render(component_name, props, prerender_options)
super + "I18n.defaultLocale = '#{MyRailsApp::Application.config.i18n.default_locale}'; I18n.fallbacks = true; I18n.locale = '#{I18n.locale}';"
end
end
end
end
@dmitry
Copy link

dmitry commented Dec 11, 2015

@jhilden thanks for sharing this and hello from 9flats! I'm not using any patches like that, but instead require_self is enough:

//= require i18n
//= require i18n/translations
//= require_self
//= require lodash
//= require pubsub
//= require_tree ./components

I18n.defaultLocale = "en";
I18n.fallbacks = true;

@jhilden
Copy link
Author

jhilden commented Dec 11, 2015

@dmitry thanks for the feedback and greetings back to 9flats :)

You are right, for setting the defaultLocale, fallbacks, and possibly other static i18n-js config options your solution also works and might even be preferrable. But for setting I18n.locale (which can be different for each request) I think it has to be done like in my gist. Or am I missing something?

@dmitry
Copy link

dmitry commented Dec 13, 2015

@jhilden normally in a react applications I'm setting I18n.locale in a root component, and passing I18n.locale into a react_component. Benefit - you can change locales inside an client application without reloading whole app on a server-side, through it depends on high level application architecture, looks more js-friendly :)

@jhilden
Copy link
Author

jhilden commented Dec 14, 2015

@dmitry I see. I guess we have a different setup with a number of mid-size react "widget components" on top of a classic rails app, but no full react "application" where it would make sense to change the locale on the client-side. But it's definitely very good to know about this other alternative.

@folivi
Copy link

folivi commented Jan 16, 2016

@jhilden
I tried to implement your solution but couldn't get it to work.
where do you put i18n_js_renderer file?
I put it under my lib directory but seems like it's not being loaded.
Checkout this sample app here https://github.com/folivi/rails-react-i18njs

@harmaty
Copy link

harmaty commented Jan 29, 2016

I have a warning:

Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server

After I added in my layout:

    = javascript_tag do
      I18n.defaultLocale = "#{I18n.default_locale}"; I18n.locale = "#{I18n.locale}";

I works fine

@giedriusr
Copy link

Hello,

We have recently added react-rails to our rails app which is multilingual website. Kinda many sources, but lots of confusion at the same time on how to achieve that in react component I could reuse i18n translations from Rails .yml files?

Thanks in advance for any feedback!

@giedriusr
Copy link

giedriusr commented Jul 14, 2017

My gist: https://gist.github.com/giedriusr/650ec9d6dbe57faf53c5cd470e0ff1ee

The problem is that react component is not aware of the other locale when I try to change it. Even if I change default locale to something else rather than "en", it still defaults to EN. No idea how to work around this. Anyone?

@balakirevs
Copy link

My gist: https://gist.github.com/giedriusr/650ec9d6dbe57faf53c5cd470e0ff1ee

The problem is that react component is not aware of the other locale when I try to change it. Even if I change default locale to something else rather than "en", it still defaults to EN. No idea how to work around this. Anyone?

@giedriusr, have you managed to resolve it?

@MichaelSeeberger
Copy link

When using Rails 6, this will not work due to webpacker (I18n is not global). Here's my solution that should work with both server and client side rendering.

In config/application.rb:

module MyApp
  class Application < Rails::Application
    #...
    # other config stuff

    config.react.server_renderer = Class.new(React::ServerRendering::BundleRenderer) do
      def before_render(component_name, props, prerender_options)
        custom_before = <<-EOF
          global.I18n.defaultLocale='#{MyApp::Application.config.i18n.default_locale}';
          global.I18n.locale='#{I18n.locale}';
          I18n.fallbacks = true;
        EOF
        super + custom_before
      end
    end
  end
end

(I don't like the anonymous class, but I couldn't work out in what file to put this class)

app/javascript/packs/server_rendering.js:

// other code

import I18n from 'i18n-js/index.js.erb'
global.I18n = I18n

app/javascript/packs/application.js:

// other code

import I18n from 'i18n-js/index.js.erb'
I18n.locale = LOCALE

app/javascript/i18n-js/index.js.erb:

import I18n from "i18n-js"
I18n.translations = <%= I18n::JS.filtered_translations.to_json %>

export default I18n

In application.html.erb I added

<script type="text/javascript">
  const LOCALE = "<%= I18n.locale %>";
</script>

Somehow, this feels quite "hacky". If anyone has a better solution, please let me know.

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