Class: RailsErrorDashboard::Services::LocalizedTimeFormatter

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_error_dashboard/services/localized_time_formatter.rb

Overview

strftime with the word-producing directives resolved from RED's own dictionary.

WHY THIS EXISTS

Ruby's strftime is not locale-aware: %B is "August" whatever RED's locale is. On the dashboard that does not matter, because a timestamp is rendered as a and the browser re-renders it through formatDateTime() using the red.js.* month/day/meridian vocabulary P3-T2 added. Anywhere there is no browser — email (P4-T2), Slack and Discord payloads (P4-T3) — the server has to do that substitution itself.

Sharing one implementation is the point: an email, a Slack message and the dashboard should not disagree about what a date looks like.

Takes the locale as an argument rather than reading Current, because every caller runs inside a job where Current is nil or belongs to an unrelated request.

Constant Summary collapse

LOCALIZED_DIRECTIVES =

The directives whose output is a word, and therefore locale-dependent: %B %b (month), %A %a (weekday), %p %P (meridian). Everything else (%Y, %m, %d, %H, %M, %S, %Z …) is digits or a timezone abbreviation and is left to strftime.

/%[BbAaPp]/
PLACEHOLDER =

Stand-in for a localized word while strftime runs. Contains no percent, so strftime cannot read it as a directive, and no space, so it cannot alter the spacing the pattern asked for. The index keeps two occurrences of the same directive independent.

"\u0000RED%<index>d\u0000"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(locale) ⇒ LocalizedTimeFormatter

Returns a new instance of LocalizedTimeFormatter.



44
45
46
# File 'lib/rails_error_dashboard/services/localized_time_formatter.rb', line 44

def initialize(locale)
  @locale = locale
end

Class Method Details

.call(time, pattern:, locale:) ⇒ String

Parameters:

  • time (Time)
  • pattern (String)

    a strftime pattern

  • locale (String)

Returns:

  • (String)


40
41
42
# File 'lib/rails_error_dashboard/services/localized_time_formatter.rb', line 40

def self.call(time, pattern:, locale:)
  new(locale).render(time, pattern)
end

Instance Method Details

#render(time, pattern) ⇒ Object

Two-pass, via placeholders: substituting a translated month name directly would put arbitrary text into the pattern, and while a French "%A" -> "mardi" is harmless, a translation containing a literal percent sign would corrupt every directive after it. The placeholder carries no percent, so strftime cannot misread it.



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/rails_error_dashboard/services/localized_time_formatter.rb', line 53

def render(time, pattern)
  replacements = []

  staged = pattern.gsub(LOCALIZED_DIRECTIVES) do |directive|
    word = localized_word(directive, time)
    next directive if word.nil?

    replacements << word
    format(PLACEHOLDER, index: replacements.length - 1)
  end

  rendered = time.strftime(staged)

  replacements.each_with_index do |word, index|
    rendered = rendered.sub(format(PLACEHOLDER, index: index), word)
  end

  rendered
end