# i18n-js: exporting Ruby i18n translations to JSON for JavaScript front ends

> i18n-js is a Ruby gem that turns Rails i18n YAML into JSON or TypeScript files a JavaScript app can load. It fits teams whose source of truth is still Ruby, and it is the wrong tool for anyone who already lives in npm.

**fnando/i18n-js** — It's a small library to provide the I18n translations on the Javascript. It comes with Rails support.

- Repository: https://github.com/fnando/i18n-js
- Stars: 3,811 · Forks: 506
- Language: Ruby
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/fnando-i18n-js

## The problem i18n-js solves for Rails teams with a JavaScript front end

Rails keeps translations in YAML and serves them through the Ruby i18n gem. A React, Vue or plain JavaScript front end cannot read that. The usual workarounds are duplicating strings into a JavaScript file, or exposing a JSON endpoint from the Rails app, which adds a request at boot and a cache to invalidate.

i18n-js takes a different route: it reads the Ruby i18n backend and writes the result to disk as JSON during your build or deploy step. The README describes the gem as a way to "Export i18n translations to JSON" and calls it "A perfect fit if you want to export translations to JavaScript." The audience is therefore narrow and specific: a project that already has Ruby translations and a JavaScript consumer, whether that consumer is a Rails asset pipeline bundle, a separate SPA, or a React Native app.

The README also notes that you do not need Ruby to use the companion JavaScript package on npm. That is a real split. The Ruby gem is the exporter; the npm package is the runtime that reads the exported files. Teams that only need the runtime should not install the gem at all.

## How the export pipeline works: config, patterns and placeholders

The mechanism is a config file that maps output files to glob patterns. Each entry has a file path and a patterns list, and the patterns select which parts of the translation tree go into that file. A pattern starting with ! excludes, and braces select a set of locales, so {pt-BR,en}.js.* includes only those two languages even when more exist. The README points at the glob gem for the full pattern syntax, which is worth reading because the wildcard rules are not invented here.

Output paths accept two placeholders. :locale is the language being exported, and :digest is the MD5 hex digest of the exported file. The README's example produces a name like app/frontend/locales/en.7bdc958e33231eafb96b81e3d108eff3.json. The digest placeholder is the cache-busting story: change a translation, the filename changes, and the browser fetches the new file instead of a stale one.

One detail that matters in practice: the config file is processed as ERB. The README shows a group variable defined at the top of the file and interpolated into the patterns. That means the config is code, not data, and you can compute locale lists rather than hand-maintaining them. It also means a broken ERB expression fails the export rather than falling back to a default.

Beyond JSON, the built-in export_files plugin lets you render an ERB template into another format. The README's example writes a TypeScript file that imports the i18n instance and calls i18n.store with the generated translations, with a banner comment that includes a timestamp.

## Installing i18n-js and running a first export

Install the gem directly, or add it to a Gemfile. Both forms appear in the README.

```bash
gem install i18n-js
```

```ruby
gem "i18n-js"
```

Then create the default configuration file. The README shows the init command writing ./config/i18n.yml.

```bash
i18n init
```

Edit that file so it points at your locale files and the output you want. This is the README's shape: a file path with :locale and :digest placeholders, and a patterns list that takes everything except the Rails internals you do not want in the browser.

```yaml
---
translations:
  - file: app/frontend/locales/:locale.:digest.json
    patterns:
      - "*"
      - "!*.activerecord.*"
      - "!*.errors.*"
```

Run the export. The README states that i18n uses config/i18n.yml and config/environment.rb by default, so from a Rails app root the bare command is enough.

```bash
i18n export
```

If those two files are not in the default locations, the README says you must pass both --config and --require. That is the failure most people hit first: the command runs, finds no environment, and the translation tree comes back empty or the process errors before writing anything. There is also a Ruby API that performs the same task as the CLI, I18nJS.call(config_file: "config/i18n.yml"), which returns the list of files it wrote, and a config hash form for callers that build the mapping at runtime.

## Where i18n-js gets in the way

The gem assumes Ruby is present at export time. That is fine in a Rails deploy and awkward in a JavaScript-only CI image, where you would need to install Ruby and the i18n gem purely to produce a JSON file. The README's own note about the npm companion package acknowledges this split but does not remove the dependency for the export half.

The glob-based pattern system is expressive and easy to get wrong. Exclusions are string patterns, not a schema, so a typo in !*.activerecord.* does not raise an error; it just fails to exclude, and the ActiveRecord strings end up in the browser bundle. Nothing in the README describes a validation step that would catch this.

The export_files plugin executes arbitrary Ruby through ERB, and the README says so plainly: "You can execute arbitrary Ruby code, so be careful." A template is not a sandbox, and a translation file that is generated by running project code is a build step with the same trust level as the rest of the build.

Fallback handling is opt-in and has sharp edges. The embed_fallback_translations plugin copies fallback values into each locale so the client does not need to load the default locale alongside the target. The README warns that assigning a plain hash to I18n.fallbacks raises no error but returns a hash rather than an I18n::Locale::Fallbacks instance, and that array assignment needs the default locale as the last argument because precedence runs left to right. Those are silent misconfigurations, not loud ones.

## i18n-js compared with i18next and react-i18next

The comparison people search for is i18n-js versus i18next. The difference is where translations are authored and who owns the runtime.

i18next is a JavaScript library first. Translation files are usually JSON authored for JavaScript, loaded at runtime through a backend plugin, with interpolation, plural rules and language detection handled in the browser. There is no Ruby step and no build-time export; the app fetches or bundles its own resources.

i18n-js inverts that. Ruby or Rails remains the source of truth, and the JavaScript side receives a generated artifact. Plurals, interpolation and fallback behaviour are decided by the Ruby i18n gem at export time, then frozen into the file. That is the trade: you get one place to edit strings and no runtime lookup cost for missing keys, but you give up i18next's runtime features and you take on a build step that must run before the front end can render anything.

If the team already writes Rails views and a React app against the same locale files, the export approach removes a whole class of drift. If the team has no Ruby, i18next is the shorter path, and adding a Ruby toolchain to a Node image to generate JSON is a cost with no matching benefit.

## Maintenance, licence and the cost of upgrading

The repository is not archived, and the last push was on 2026-07-14. That is recent enough that the project is not abandoned, but the README does not describe a release cadence, and no recent releases were retrieved, so there is no version history to reason about here.

The upgrade story is documented in the repository itself: MIGRATING_FROM_V3_TO_V4.md, MIGRATING_CONFIG_FOR_USERS.md and MIGRATING_PLUGINS_FOR_PLUGIN_DEVELOPERS.md sit at the top level. Three separate migration documents are a signal that the configuration format has changed across major versions, and that a version bump is not a drop-in. Anyone planning an upgrade should read the config migration file before touching the Gemfile, because the patterns and pipeline structure are the parts most likely to move.

The gem is MIT licensed. In practical terms that permits commercial and closed-source use with the licence text retained, but the repository's LICENSE.md is the authoritative text and this is not legal advice. Note that the gem pulls in the glob gem for pattern matching, so a dependency audit covers more than i18n-js alone.

## Conclusion

Adopt i18n-js if your translations already live in Ruby or Rails and a JavaScript front end needs them as JSON; the gem keeps one source of truth and the export step is a single command. Do not adopt it if your app is JavaScript-only and has no Ruby in the build, because the CLI depends on config/environment.rb and the Ruby i18n gem being loadable. Before committing, run i18n export against your real config/i18n.yml and check the generated files for the patterns you excluded with ! entries, since a wrong glob silently ships keys you meant to drop.

## FAQ

### What is i18n-js used for?

It exports Ruby i18n translations to JSON so a JavaScript front end can load them, and the README describes it as a fit for exporting translations to JavaScript. It also ships a companion npm package for the JavaScript side.

### What is i18n-js?

It is a small Ruby library, distributed as the i18n-js gem, that provides I18n translations to JavaScript and comes with Rails support. Its job is to export i18n translations as JSON files.

### Does i18n-js work with Rails?

Yes. The README says the gem comes with Rails support, and by default the CLI reads config/i18n.yml and config/environment.rb, which are the conventional Rails locations.

### Do I need Ruby to use i18n-js?

Not for the runtime. The README notes that if you do not use Ruby you can still use i18n-js through the companion JavaScript package on npm. The export step itself is the Ruby gem.

### How do I exclude some translations from the exported file?

Add a pattern starting with ! to the patterns list for that file, for example !*.activerecord.* to exclude all ActiveRecord translations. The README states that patterns use the glob gem for their syntax.

### Can i18n-js export something other than JSON?

Yes, through the built-in export_files plugin, which renders an ERB template to an output path. The README's example generates a TypeScript file that calls i18n.store with the translations.

## Sources

- [fnando/i18n-js on GitHub](https://github.com/fnando/i18n-js)
- [Issues](https://github.com/fnando/i18n-js/issues)
- [License: MIT](https://github.com/fnando/i18n-js/blob/main/LICENSE)
- [README](https://github.com/fnando/i18n-js/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/fnando-i18n-js
