Class: RailsErrorDashboard::PrivateBackend

Inherits:
I18n::Backend::Simple
  • Object
show all
Defined in:
lib/rails_error_dashboard/private_backend.rb

Overview

The backend behind I18nStore: a Backend::Simple that the host app's I18n configuration cannot reach into.

Backend::Simple is very nearly private already — it holds its own translations hash and its own initialized flag. Two upstream behaviours break that, and both act at LOAD time rather than lookup time, so neither raises and neither leaves a trace. The dashboard simply renders English. Both were found in P4-T3, by a fixture locale that kept resolving to English; see tasks/i18n-sprint-plan.md.

This class states the two fixes as invariants of RED's backend. The previous implementation got the same result by clearing and restoring I18n.available_locales around the load and by poking @initialized from outside — correct, but it mutated a global that a concurrent thread could observe mid-window. Nothing here touches global state.

Instance Method Summary collapse

Instance Method Details

#pluralization_key(entry, count) ⇒ Object

Overrides the hook rather than #pluralize, so upstream's InvalidPluralizationData guard still fires for an entry that genuinely lacks the category asked for — a missing form must stay loud, and I18nStore's rescue must stay a safety net rather than the normal path.

A locale absent from PLURAL_RULES keeps upstream's English behaviour, which is the right default: de and en are English-shaped, and an unknown locale is better served by the documented upstream rule than by a guess.



183
184
185
186
187
188
189
190
191
# File 'lib/rails_error_dashboard/private_backend.rb', line 183

def pluralization_key(entry, count)
  rule = PLURAL_RULES[@current_pluralization_locale.to_s]
  return super unless rule

  return :zero if count == 0 && entry.has_key?(:zero)

  key = rule.call(count)
  entry.has_key?(key) ? key : :other
end

#pluralize(locale, entry, count) ⇒ Object

Upstream calls pluralization_key from #pluralize, which knows the locale; the hook itself does not receive it. Capture it around the call rather than reading I18n.locale, which is the HOST's current locale and need not be the locale this lookup is for (I18nStore always passes locale: explicitly, and jobs render several locales under one I18n.locale).



198
199
200
201
202
203
204
# File 'lib/rails_error_dashboard/private_backend.rb', line 198

def pluralize(locale, entry, count)
  previous = @current_pluralization_locale
  @current_pluralization_locale = locale
  super
ensure
  @current_pluralization_locale = previous
end

#store_translations(locale, data, options = ::I18n::EMPTY_HASH) ⇒ Object

DEFECT 1 — the host's allowlist must not decide what RED's dictionary CONTAINS.

Upstream store_translations opens with a guard that discards the data, silently, returning it unstored, when all three of these hold:

1. I18n.enforce_available_locales           (the Rails DEFAULT)
2. I18n.available_locales_initialized?      (a one-way latch, below)
3. the locale is absent from the host's list

Condition 2 is the fuse, and nothing can defuse it: it flips the first time anything ASSIGNS to I18n.available_locales, and I18n exposes no way to clear it. A host that configures locales at boot — or any gem that touches the setting once — arms the filter for the life of the process.

The effect is that a host with config.i18n.available_locales = [:en, :ja] strips RED's de/fr/es/pt-BR out of RED's OWN private dictionary, and every dashboard page renders English with nothing anywhere to say why.

The guard is the first statement in the upstream method, so there is no way to skip it via super and no supported hook to satisfy it — its three inputs are all host globals. What follows it is four lines of public I18n API, reproduced here without the guard.

BECAUSE THIS RESTATES AN UPSTREAM BODY, it is pinned by spec/lib/rails_error_dashboard/private_backend_spec.rb, which asserts the upstream semantics this reproduces. If an i18n upgrade changes store_translations, that spec fails loudly rather than this drifting silently out of step.



51
52
53
54
55
56
# File 'lib/rails_error_dashboard/private_backend.rb', line 51

def store_translations(locale, data, options = ::I18n::EMPTY_HASH)
  locale = locale.to_sym
  translations[locale] ||= Concurrent::Hash.new
  data = ::I18n::Utils.deep_symbolize_keys(data) unless options.fetch(:skip_symbolize_keys, false)
  STORE_MUTEX.synchronize { ::I18n::Utils.deep_merge!(translations[locale], data) }
end