Getting Started
    Core Concepts
    Building Features
    API Reference
    Configuration
    Deployment
    Upgrading
    Migrating
    Pro
    Misc
    Shakacode logoShakaCodeDeveloped by

    Copyright 2026 ShakaCode

    Internationalization

    You can use Rails internationalization (i18n) in your client code.

    1. Set config.i18n_dir in config/initializers/react_on_rails.rb:

      # Replace the following line by the directory containing your translation.js and default.js files.
      config.i18n_dir = Rails.root.join("PATH_TO", "YOUR_JS_I18N_FOLDER")

      If you do not want to use the YAML files from Rails.root.join("config", "locales") and installed gems, you can also set config.i18n_yml_dir:

      # Replace the following line by the location of your client i18n yml files
      # Without this option, all YAML files from Rails.root.join("config", "locales") and installed gems are loaded
      config.i18n_yml_dir = Rails.root.join("PATH_TO", "YOUR_YAML_I18N_FOLDER")
    2. Add that directory (or just the generated files translations.json and default.json) to your .gitignore.

    3. The locale files must be generated before building (e.g., npm run build, yarn build, or pnpm build).

      Option A: Direct Ruby API (best when you have a custom bin/dev script or multiple build steps)

      Use the ReactOnRails::Locales.compile method directly in your bin/dev script:

      # In bin/dev
      require_relative "../config/environment"
      
      # Generate locales using direct Ruby API (faster than rake task)
      ReactOnRails::Locales.compile if ReactOnRails.configuration.i18n_dir.present?

      Benefits:

      • Faster execution (no shell spawning overhead)
      • Better version manager compatibility (mise, asdf, rbenv)
      • Single place to manage all precompile tasks
      • Can be combined with other build steps in one script

      To force regeneration:

      ReactOnRails::Locales.compile(force: true)

      Option B: Rake task (simplest for projects using precompile_hook or one-off generation)

      Use rake react_on_rails:locale from the command line:

      bundle exec rake react_on_rails:locale
      # Subsequent calls will skip if already up-to-date

      To force regeneration:

      bundle exec rake react_on_rails:locale force=true

      The locale generation is idempotent - it will skip generation if files are already up-to-date. This makes it safe to call multiple times without duplicate work.

      Recommended: Use Shakapacker's precompile_hook with bin/dev (React on Rails 16.2+, Shakapacker 9.3+)

      Configure the idempotent task in config/shakapacker.yml to run automatically before webpack:

      # config/shakapacker.yml
      default: &default
        precompile_hook: 'bundle exec rake react_on_rails:locale'

      With this configuration, bin/dev will:

      • Run the precompile hook once before starting development processes

      • Set SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true to prevent duplicate execution

      • Pass the environment variable to all spawned processes (Rails, webpack, etc.)

      TIP

      For HMR with SSR setups (two webpack processes), use a script-based hook instead of a direct command. Script-based hooks can include a self-guard that prevents duplicate execution regardless of Shakapacker version:

      #!/usr/bin/env ruby
      # bin/shakapacker-precompile-hook
      exit 0 if ENV["SHAKAPACKER_SKIP_PRECOMPILE_HOOK"] == "true"
      system("bundle", "exec", "rake", "react_on_rails:locale", exception: true)

      Then configure: precompile_hook: 'bin/shakapacker-precompile-hook'

      Upgrading existing apps

      If your app already uses a direct command hook:

      precompile_hook: 'bundle exec rake react_on_rails:locale'

      switch to precompile_hook: 'bin/shakapacker-precompile-hook' and place the self-guard near the top of that script:

      exit 0 if ENV["SHAKAPACKER_SKIP_PRECOMPILE_HOOK"] == "true"

      This keeps locale generation reliable in SSR + HMR environments across Shakapacker versions.

      This eliminates the need for sleep hacks and manual coordination in Procfile.dev. See the Process Managers documentation for details.

      Alternative: Manual coordination

      For development, you can adjust your startup scripts (Procfiles) so that they run bundle exec rake react_on_rails:locale before running any Webpack watch process (e.g., npm run build:development, yarn run build:development, or pnpm run build:development).

      If you are not using the React on Rails test helper, you may need to configure your CI to run bundle exec rake react_on_rails:locale before any Webpack process as well.

      NOTE

      If you try to lint before running tests, and you depend on the test helper to build your locales, linting will fail because the translations won't be built yet.

      The fix is either:

      1. to run the rake task to build the translations before running the lint command, or
      2. to run the tests first.
    4. If your locale files (or one of the gems locale files) contains unsafe YAML, you may need to configure config.i18n_yml_safe_load_options if you can't fix such YAML files properly.

      config.i18n_yml_safe_load_options = { permitted_classes: [Symbol] }

    By default, the locales are generated as JSON, but you can also generate them as JavaScript with react-intl support:

    1. Specify the i18n output format in config/initializers/react_on_rails.rb:

      config.i18n_output_format = "js"
    2. Add react-intl & intl to client/package.json, and remember to run bundle install and your package manager's install command (e.g., npm install, yarn install, or pnpm install). The minimum supported versions are:

      "dependencies": {
        ...
        "intl": "^1.2.5",
        "react-intl": "^2.1.5",
        ...
      }
    3. In React, you need to initialize react-intl, and set its parameters:

      ...
      import { addLocaleData } from 'react-intl';
      import en from 'react-intl/locale-data/en';
      import de from 'react-intl/locale-data/de';
      import { translations } from 'path_to/i18n/translations';
      import { defaultLocale } from 'path_to/i18n/default';
      ...
      // Initialize all locales for react-intl.
      addLocaleData([...en, ...de]);
      ...
      // Set locale and messages for IntlProvider.
      const locale = method_to_get_current_locale() || defaultLocale;
      const messages = translations[locale];
      ...
      return (
        <IntlProvider locale={locale} key={locale} messages={messages}>
          <CommentScreen {...{ actions, data }} />
        </IntlProvider>
      )

      In your component:

      import { defaultMessages } from 'path_to/i18n/default';
      ...
      return (
        { formatMessage(defaultMessages.yourLocaleKeyInCamelCase) }
      )

    Notes