Module: RailsErrorDashboard::I18nHelper

Includes:
ActionView::Helpers::JavaScriptHelper
Included in:
BacktraceHelper, MailerI18nHelper, OverviewHelper, UserAgentHelper
Defined in:
app/helpers/rails_error_dashboard/i18n_helper.rb

Overview

View-layer entry point for RED's translations.

The helper is deliberately named red_t, not t. Two reasons:

1. Overriding `t` in an engine helper risks colliding with the host app's
 own helpers and with Rails' built-in translate, which has lazy-lookup
 behaviour ("t('.title')") that RED's private backend does not provide.
2. An explicit name makes every translated site greppable — worth a lot
 when the remaining ~1,600 strings get extracted a page at a time.

Instance Method Summary collapse

Instance Method Details

#red_chart_date(time, pattern) ⇒ String

A finished, localized date string for somewhere the browser will not re-render it.

Almost every timestamp on the dashboard goes out as a that formatDateTime() localizes client-side. Chart labels cannot: they are serialized into a JS array and handed to Chart.js as plain strings, so whatever the server writes is what the axis shows. Calling strftime directly there is what put English month names on every locale's charts (#178) — Ruby's strftime is not locale-aware.

Same formatter the mailers and the Slack/Discord payloads use, so a chart axis and an email agree about what a date looks like.

Parameters:

  • time (Time, Date, nil)
  • pattern (String)

    a strftime pattern

Returns:

  • (String)

    "" for nil — an axis label must never read "undefined"



170
171
172
173
174
175
176
177
178
# File 'app/helpers/rails_error_dashboard/i18n_helper.rb', line 170

def red_chart_date(time, pattern)
  return "" if time.nil?

  time = time.to_time if time.respond_to?(:to_time) && !time.is_a?(Time)
  Services::LocalizedTimeFormatter.call(time, pattern: pattern, locale: red_locale)
rescue StandardError
  # A chart with an English axis beats a chart that fails to render.
  time.respond_to?(:strftime) ? time.strftime(pattern) : time.to_s
end

#red_js_t(key, **options) ⇒ String

Translate for interpolation into a JavaScript string literal.

red_t html-escapes, which is right for page text and wrong here. The two sinks in the layout's script blocks disagree about entities: showToast and innerHTML decode ' back to an apostrophe, but textContent renders it literally, so a French string would display "d'accéder" on screen.

escape_javascript is what the surrounding code already uses for flash messages (<%= j flash[:notice] %>), and it is the correct escaping for this position: it neutralizes quotes, backslashes and line terminators that would break out of or truncate the literal, without touching characters the sink will render.

Values still pass through the same total lookup, so a missing key is readable text rather than a raise inside a breakout — and then consumed as JS strings, so ERB escaping here would render literal &amp; in the browser.

Returns:

  • (Hash)

    never nil, never raises. {} at worst.



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'app/helpers/rails_error_dashboard/i18n_helper.rb', line 116

def red_js_translations
  locale = red_locale

  {
    "locale" => locale,
    "js" => I18nStore.subtree("red.js", locale: locale),
    "formats" => I18nStore.subtree("red.time.formats", locale: locale),
    # Not under red.js because the server renders it too — local_time_ago
    # wraps the same key. Sharing one key is the point: both sides then
    # put "ago" wherever the language wants it, rather than each guessing.
    "ago" => I18nStore.translate("red.time.ago", locale: locale)
  }
rescue StandardError
  {}
end

#red_localeString

The locale this render should use.

An explicit @red_locale wins over Current. Mailer and notification templates render outside the dashboard's around_action: there is no request, so Current.locale is nil at best and — on a reused Puma or job-runner thread — another request's leftover value at worst. That is the #143/#148 bug class. Those templates are handed a locale resolved at enqueue time (Concerns::LocalizedJob) and assigned to @red_locale, so reading it here is what makes the async path independent of thread state.

Views rendered in a real dashboard request set no @red_locale and fall through to Current, which is correct there.

Returns:

  • (String)

    a locale RED ships



64
65
66
67
68
69
70
71
# File 'app/helpers/rails_error_dashboard/i18n_helper.rb', line 64

def red_locale
  explicit = defined?(@red_locale) ? @red_locale : nil
  return I18nStore.resolve(explicit) if explicit.present?

  Current.locale_or_default
rescue StandardError
  I18nStore::DEFAULT_LOCALE
end

#red_t(key, **options) ⇒ String

Translate a key in the current request's locale.

Output is HTML-escaped unless the key ends in _html, matching Rails' convention. Escaping happens here rather than in I18nStore because it is a view concern — mailers' text parts and JS payloads must not be escaped.

Parameters:

  • key (String, Symbol)

    e.g. "red.nav.errors"

Returns:

  • (String)

    never nil, never raises



26
27
28
29
30
31
32
33
34
35
36
# File 'app/helpers/rails_error_dashboard/i18n_helper.rb', line 26

def red_t(key, **options)
  value = I18nStore.translate(key, locale: red_locale, **options)

  if key.to_s.end_with?("_html")
    value.html_safe
  else
    ERB::Util.html_escape(value)
  end
rescue StandardError
  ""
end

#red_time_format(preset) ⇒ String

A strftime pattern for one of ApplicationHelper#local_time's presets, localized. Falls back to the English pattern for an unknown preset.

Kept here rather than inline in the view so the Ruby fallback rendering and the browser's data-format re-render always agree on the pattern.

Parameters:

  • preset (Symbol, String)

    :full, :short, :date_only, :time_only, :datetime

Returns:

  • (String)

    a raw strftime pattern — NOT html-escaped, since it is consumed by strftime and by the JS date formatter, not rendered as text



141
142
143
144
145
146
147
148
149
150
151
152
# File 'app/helpers/rails_error_dashboard/i18n_helper.rb', line 141

def red_time_format(preset)
  key = "red.time.formats.#{preset}"
  pattern = I18nStore.translate(key, locale: red_locale)

  # A miss returns humanized key text, which would be a nonsense strftime
  # pattern. Detect it by the absence of any % directive and fall back.
  return pattern if pattern.include?("%")

  I18nStore.translate("red.time.formats.full", locale: I18nStore::DEFAULT_LOCALE)
rescue StandardError
  "%B %d, %Y %I:%M:%S %p"
end

#red_tp(key, count:, **options) ⇒ Object

Pluralized translation. Selects the plural form from count using the locale's CLDR rules, so it handles languages with more than English's two forms.

Always use this rather than a ternary on "s" — an English binary plural is wrong in most languages, and several have no plural distinction at all.

Parameters:

  • count (Integer)

    drives form selection and is available as %count



46
47
48
# File 'app/helpers/rails_error_dashboard/i18n_helper.rb', line 46

def red_tp(key, count:, **options)
  red_t(key, count: count, **options)
end