I18n-js v4 is a breaking change release and diverges quite a lot from how the previous version worked. This guides summarizes the process of upgrading an app that uses i18n-js v3 to v4.
Previously, you could use a middleware to export translations (some people even
used this in production 😬). In development, you can now use whatever your want,
because i18n-js doesn't make any assumptions. All you need to do is running
i18n export
, either manually or by using something that listens to file
changes.
If you like watchman, you can use something like this:
#!/usr/bin/env bash
root=`pwd`
watchman watch-del "$root"
watchman watch-project "$root"
watchman trigger-del "$root" i18n
watchman -j <<-JSON
[
"trigger",
"$root",
{
"name": "i18n",
"expression": [
"anyof",
["match", "config/locales/**/*.yml", "wholename"],
["match", "config/locales/**/*.po", "wholename"],
["match", "config/i18n.yml", "wholename"]
],
"command": ["i18n", "export"]
}
]
JSON
# If you're running this through Foreman, then uncomment the following lines:
# while true; do
# sleep 1
# done
You can also use guard. Make sure you have both guard and guard-compat installed and use Guardfile file with the following contents:
guard(:"i18n-js",
run_on_start: true,
config_file: "./config/i18n.yml",
require_file: "./config/environment.rb") do
watch(%r{^config/locales/.+\.(yml|po)$})
watch(%r{^config/i18n.yml$})
watch("Gemfile")
end
To run guard, use guard start -i
.
Finally, you can use listen. Create the file
config/initializers/i18n.rb
with the following content:
Rails.application.config.after_initialize do
require "i18n-js/listen"
# This will only run in development.
I18nJS.listen
end
Warning:
No matter which approach you choose, the idea is that you precompile your translations when going to production. DO NOT RUN any of the above in production.
The build process for i18n now relies on an external CLI called i18n
. All you
need to do is executing i18n export
in your build step to generate the json
files for your translations.
The JavaScript package is now a separate thing and need to be installed using your favorite tooling (e.g. yarn, npm, pnpm, etc).
$ yarn add i18n-js@latest
$ npm i --save-dev i18n-js@latest
From now on, the way you load translations and set up I18n-js is totally up to you, but means you need to load the json files and attach to the I18n-js instance. This is how I do it in a project I'm doing right now (Rails 7 + esbuild + TypeScript). First, we need to load the I18n-js configuration from the main JavaScript file:
// app/javascript/application.ts
import { i18n } from "./config/i18n";
Then we need to load our translations and instantiate the I18n-js class.
// app/javascript/config/i18n.ts
import { I18n } from "i18n-js";
import translations from "translations.json";
// Fetch user locale from html#lang.
// This value is being set on `app/views/layouts/application.html.erb` and
// is inferred from `ACCEPT-LANGUAGE` header.
const userLocale = document.documentElement.lang;
export const i18n = new I18n();
i18n.store(translations);
i18n.defaultLocale = "en";
i18n.enableFallback = true;
i18n.locale = userLocale;
The best thing about the above is that it is a pretty straightforward pattern in the JavaScript community. It doesn't rely on specific parts from Sprockets (I'm not even using it on my projects) or eRb files.
The configuration file loaded from config/i18n.yml
has changed. Given the v3
configuration below
---
translations:
- file: "app/assets/javascripts/date_formats.js"
only: "*.date.formats"
- file: "app/assets/javascripts/other.js"
only: ["*.activerecord", "*.admin.*.title"]
- file: "app/assets/javascripts/everything_else.js"
except:
- "*.activerecord"
- "*.admin.*.title"
- "*.date.formats"
the equivalent configuration file for v4 would be
---
translations:
- file: "app/assets/javascripts/date_formats.js"
patterns:
- "*.date.formats"
- file: "app/assets/javascripts/other.js"
patterns:
- "*.activerecord"
- "*.admin.*.title"
- file: "app/assets/javascripts/everything_else.js"
patterns:
# Notice the exclamation mark.
- "*"
- "!*.activerecord"
- "!*.admin.*.title"
- "!*.date.formats"
Other configuration options:
export_i18n_js
: replaced by export_files pluginfallbacks
: replaced by embed_fallback_translations pluginjs_available_locales
: removed (on v4 you can use groups, like in{pt-BR,en}.*
)namespace
: removed without an equivalentsort_translation_keys
: removed (on v4 keys will always be sorted)translations[].prefix
: removed without an equivalenttranslations[].pretty_print
: removed (on v4 files will always be exported in a readable format)
Previously, v3 had the %{locale}
placeholder, which can be used as part of the
directory and/or file name. Now, the syntax is just :locale
. Additionally, you
can also use :digest
, which uses a MD5 hex digest of the exported file.