Skip to content

Add a setting to load I18n translations when an API is compiled - #3019

Open
ericproulx wants to merge 1 commit into
masterfrom
perf/eager-load-i18n
Open

ericproulx wants to merge 1 commit into
masterfrom
perf/eager-load-i18n

Conversation

@ericproulx

@ericproulx ericproulx commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

What

A new setting, Grape.config.eager_load_i18n (default false). When it is on, Instance#initialize eager-loads the I18n backend (when the backend answers eager_load!), so compiling an API reads the locale files instead of leaving that to the first lookup.

Why

I18n loads lazily, so the first error a process answers pays for the load. Measured in a fresh process after API.compile!: first 400 3.65 ms lazy vs 0.87 ms eager. Steady state is unchanged (0.095 vs 0.084 ms).

Why off by default

Grape is most often mounted in Rails, which already handles this:

  • config.eager_load on (production by default): ActiveSupport's I18n railtie registers config.eager_load_namespaces << I18n, so Rails eager-loads the translations at boot.
  • config.eager_load off (development, test): Rails keeps I18n lazy on purpose. Forcing it there would change two things.
    • An eager-loaded backend reloads at once on every I18n.reload!, which Rails triggers when a locale file changes.
    • A compile! in an initializer would read the translations before Rails' after_initialize has set the app's load path.

An app outside Rails that precompiles its API can turn it on to move the load from its first error to boot:

Grape.configure do |config|
  config.eager_load_i18n = true
end

Notes

  • Where the load runs: Instance#initialize runs once per API build, under the compile lock, on both an explicit and a lazy compile.
  • Backends without eager_load!: the guard asks the backend, not I18n. I18n.eager_load! just calls backend.eager_load!, and a backend that does not inherit I18n::Backend::Base may not have it. That is spec'd: such a backend still answers errors.
  • Adding locale files once it is on: a file appended to I18n.load_path with << after compiling is read only after I18n.reload!; assigning the path (I18n.load_path += [path]) reloads at once. The README's I18n section says so; the compile! section and the Configuration list point to it.
  • No UPGRADING entry: the default leaves behavior unchanged, so the setting is additive.

Tests

  • Compiling leaves the translations unloaded by default.
  • With the setting on, compiling loads them, the first error is still translated, and a backend with no eager_load! still answers.
  • Removing the guard or inverting it fails specs.
  • Full RSpec suite passes locally (3077 examples, 0 failures); RuboCop clean.
  • CI green, including the Rails gemfiles.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Danger Report

No issues found.

View run

@github-actions

Copy link
Copy Markdown

Danger Report

No issues found.

View run

@ericproulx
ericproulx force-pushed the perf/eager-load-i18n branch 4 times, most recently from 9240107 to 1e2f663 Compare September 29, 2026 07:27
@ericproulx ericproulx changed the title Load I18n translations when an API is compiled Add a setting to load I18n translations when an API is compiled Sep 29, 2026
@dblock

dblock commented Sep 29, 2026

Copy link
Copy Markdown
Member

I think we should we provide eager_load! to match Rails and automatically call it when Rails.eager_load! is.

@ericproulx

Copy link
Copy Markdown
Contributor Author

I think we should we provide eager_load! to match Rails and automatically call it when Rails.eager_load! is.

Rails is already doing calling it through i18n. This is only when it's not a Rails app. I like the eager_load! load though.

@dblock

dblock commented Sep 30, 2026

Copy link
Copy Markdown
Member

I like the eager_load! load though.

Rename?

@ericproulx
ericproulx force-pushed the perf/eager-load-i18n branch 5 times, most recently from e3f5d35 to 291702f Compare October 17, 2026 20:42
@dblock

dblock commented Oct 19, 2026

Copy link
Copy Markdown
Member

Could we expose this through an explicit Twitter::API.eager_load! rather than introducing Grape.config.eager_load_i18n and coupling translation loading to Instance#initialize?

The first-error improvement makes sense, but compile! also runs lazily on the first request. With the setting enabled, this can move translation loading onto the first request—even a successful one—rather than necessarily moving it to boot.

An explicit eager_load! could load translations (guarding for backend support) and compile the API, leaving compile! and normal request handling unchanged. Applications outside Rails could call it at boot after configuring their locale paths, without another global setting.

Rails already eager-loads I18n when application eager loading is enabled. Automatically invoking Grape API eager loading from Rails would need explicit integration; naming the method eager_load! alone would not do that. That integration could be addressed separately.

— AI posting on behalf of @dblock

I18n reads its locale files on the first lookup, so in an app that
precompiles its API the first error a process answered still paid for the
load: 3.65 ms against 0.87 ms once the translations were in memory.
Grape.config.eager_load_i18n makes compiling eager-load the I18n backend,
so an app calling compile! at boot pays it there.

It is off by default. Rails, where Grape is most often mounted, already
eager-loads I18n when config.eager_load is on, and keeps it lazy
otherwise; forcing it there would make every I18n.reload! load at once in
development, and could read the translations before the app's
initializers have set its load path.

The backend is asked directly, as one that does not inherit
I18n::Backend::Base may have no eager_load!.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ericproulx
ericproulx force-pushed the perf/eager-load-i18n branch from 291702f to 5ce1cc2 Compare October 19, 2026 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants