rails_error_dashboard 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -0
  3. data/app/controllers/rails_error_dashboard/application_controller.rb +156 -0
  4. data/app/controllers/rails_error_dashboard/errors_controller.rb +95 -73
  5. data/app/controllers/rails_error_dashboard/locales_controller.rb +87 -0
  6. data/app/helpers/rails_error_dashboard/application_helper.rb +20 -14
  7. data/app/helpers/rails_error_dashboard/backtrace_helper.rb +15 -6
  8. data/app/helpers/rails_error_dashboard/i18n_helper.rb +154 -0
  9. data/app/helpers/rails_error_dashboard/mailer_i18n_helper.rb +69 -0
  10. data/app/helpers/rails_error_dashboard/overview_helper.rb +18 -6
  11. data/app/helpers/rails_error_dashboard/user_agent_helper.rb +25 -11
  12. data/app/jobs/rails_error_dashboard/application_job.rb +5 -0
  13. data/app/jobs/rails_error_dashboard/baseline_alert_job.rb +18 -16
  14. data/app/jobs/rails_error_dashboard/concerns/localized_job.rb +83 -0
  15. data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +4 -2
  16. data/app/jobs/rails_error_dashboard/email_error_notification_job.rb +4 -2
  17. data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +6 -2
  18. data/app/jobs/rails_error_dashboard/scheduled_digest_job.rb +8 -3
  19. data/app/jobs/rails_error_dashboard/slack_error_notification_job.rb +6 -4
  20. data/app/jobs/rails_error_dashboard/storm_notification_job.rb +26 -8
  21. data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +4 -2
  22. data/app/mailers/rails_error_dashboard/application_mailer.rb +14 -0
  23. data/app/mailers/rails_error_dashboard/digest_mailer.rb +18 -2
  24. data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +19 -2
  25. data/app/views/layouts/rails_error_dashboard.html.erb +255 -92
  26. data/app/views/rails_error_dashboard/digest_mailer/digest_summary.html.erb +23 -22
  27. data/app/views/rails_error_dashboard/digest_mailer/digest_summary.text.erb +30 -19
  28. data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb +18 -16
  29. data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb +21 -14
  30. data/app/views/rails_error_dashboard/errors/_ai_help_panel.html.erb +11 -7
  31. data/app/views/rails_error_dashboard/errors/_breadcrumbs_group.html.erb +45 -38
  32. data/app/views/rails_error_dashboard/errors/_co_occurring_errors.html.erb +17 -14
  33. data/app/views/rails_error_dashboard/errors/_discussion.html.erb +27 -9
  34. data/app/views/rails_error_dashboard/errors/_error_cascades.html.erb +25 -21
  35. data/app/views/rails_error_dashboard/errors/_error_info.html.erb +25 -22
  36. data/app/views/rails_error_dashboard/errors/_error_row.html.erb +24 -12
  37. data/app/views/rails_error_dashboard/errors/_instance_variables.html.erb +12 -11
  38. data/app/views/rails_error_dashboard/errors/_issue_section.html.erb +19 -13
  39. data/app/views/rails_error_dashboard/errors/_llm_summary.html.erb +14 -14
  40. data/app/views/rails_error_dashboard/errors/_local_variables.html.erb +8 -8
  41. data/app/views/rails_error_dashboard/errors/_modals.html.erb +60 -52
  42. data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +59 -49
  43. data/app/views/rails_error_dashboard/errors/_request_context.html.erb +23 -20
  44. data/app/views/rails_error_dashboard/errors/_show_scripts.html.erb +14 -14
  45. data/app/views/rails_error_dashboard/errors/_sidebar_metadata.html.erb +118 -93
  46. data/app/views/rails_error_dashboard/errors/_similar_errors.html.erb +18 -12
  47. data/app/views/rails_error_dashboard/errors/_source_code.html.erb +20 -5
  48. data/app/views/rails_error_dashboard/errors/_stats.html.erb +5 -5
  49. data/app/views/rails_error_dashboard/errors/_timeline.html.erb +7 -7
  50. data/app/views/rails_error_dashboard/errors/_user_errors_table.html.erb +15 -12
  51. data/app/views/rails_error_dashboard/errors/actioncable_health_summary.html.erb +25 -25
  52. data/app/views/rails_error_dashboard/errors/activestorage_health_summary.html.erb +28 -28
  53. data/app/views/rails_error_dashboard/errors/analytics.html.erb +82 -82
  54. data/app/views/rails_error_dashboard/errors/cache_health_summary.html.erb +27 -27
  55. data/app/views/rails_error_dashboard/errors/correlation.html.erb +61 -57
  56. data/app/views/rails_error_dashboard/errors/database_health_summary.html.erb +64 -65
  57. data/app/views/rails_error_dashboard/errors/deprecations.html.erb +23 -23
  58. data/app/views/rails_error_dashboard/errors/diagnostic_dumps.html.erb +48 -36
  59. data/app/views/rails_error_dashboard/errors/index.html.erb +121 -70
  60. data/app/views/rails_error_dashboard/errors/job_health_summary.html.erb +30 -30
  61. data/app/views/rails_error_dashboard/errors/llm_health_summary.html.erb +38 -39
  62. data/app/views/rails_error_dashboard/errors/n_plus_one_summary.html.erb +28 -25
  63. data/app/views/rails_error_dashboard/errors/overview.html.erb +45 -39
  64. data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +45 -44
  65. data/app/views/rails_error_dashboard/errors/rack_attack_summary.html.erb +39 -28
  66. data/app/views/rails_error_dashboard/errors/releases.html.erb +46 -47
  67. data/app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb +48 -44
  68. data/app/views/rails_error_dashboard/errors/settings.html.erb +122 -112
  69. data/app/views/rails_error_dashboard/errors/show.html.erb +44 -37
  70. data/app/views/rails_error_dashboard/errors/storms.html.erb +30 -24
  71. data/app/views/rails_error_dashboard/errors/swallowed_exceptions.html.erb +26 -26
  72. data/app/views/rails_error_dashboard/errors/user_impact.html.erb +31 -30
  73. data/config/locales/de.yml +2007 -0
  74. data/config/locales/en.yml +2719 -0
  75. data/config/locales/es.yml +2079 -0
  76. data/config/locales/fr.yml +2082 -0
  77. data/config/locales/it.yml +2060 -0
  78. data/config/locales/ja.yml +1802 -0
  79. data/config/locales/pl.yml +2108 -0
  80. data/config/locales/pt-BR.yml +2064 -0
  81. data/config/locales/ru.yml +2109 -0
  82. data/config/locales/uk.yml +2106 -0
  83. data/config/locales/zh-CN.yml +1791 -0
  84. data/config/routes.rb +5 -0
  85. data/db/migrate/20260325000001_fix_swallowed_exceptions_index_for_mysql.rb +9 -3
  86. data/lib/rails_error_dashboard/commands/batch_delete_errors.rb +3 -1
  87. data/lib/rails_error_dashboard/commands/batch_mute_errors.rb +4 -2
  88. data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +4 -2
  89. data/lib/rails_error_dashboard/commands/batch_unmute_errors.rb +4 -2
  90. data/lib/rails_error_dashboard/commands/create_issue.rb +5 -3
  91. data/lib/rails_error_dashboard/commands/link_existing_issue.rb +4 -2
  92. data/lib/rails_error_dashboard/commands/log_error.rb +1 -1
  93. data/lib/rails_error_dashboard/configuration.rb +23 -0
  94. data/lib/rails_error_dashboard/current.rb +56 -0
  95. data/lib/rails_error_dashboard/i18n_store.rb +293 -0
  96. data/lib/rails_error_dashboard/private_backend.rb +213 -0
  97. data/lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb +89 -22
  98. data/lib/rails_error_dashboard/services/digest_builder.rb +21 -7
  99. data/lib/rails_error_dashboard/services/discord_payload_builder.rb +27 -15
  100. data/lib/rails_error_dashboard/services/error_notification_dispatcher.rb +10 -5
  101. data/lib/rails_error_dashboard/services/localized_time_formatter.rb +117 -0
  102. data/lib/rails_error_dashboard/services/notification_helpers.rb +87 -4
  103. data/lib/rails_error_dashboard/services/pagerduty_payload_builder.rb +22 -5
  104. data/lib/rails_error_dashboard/services/slack_payload_builder.rb +48 -28
  105. data/lib/rails_error_dashboard/services/storm_protection/gate.rb +2 -1
  106. data/lib/rails_error_dashboard/services/webhook_payload_builder.rb +15 -1
  107. data/lib/rails_error_dashboard/translation.rb +39 -0
  108. data/lib/rails_error_dashboard/version.rb +1 -1
  109. data/lib/rails_error_dashboard.rb +4 -0
  110. data/lib/tasks/error_dashboard.rake +5 -1
  111. metadata +24 -4
@@ -1,40 +1,48 @@
1
- <% content_for :page_title, "Rate Limit Events" %>
1
+ <% content_for :page_title, red_t("red.errors.rack_attack_page.title") %>
2
2
 
3
3
  <div>
4
4
  <div class="d-flex justify-content-between align-items-center mb-4">
5
5
  <h1 style="font-size: 20px; font-weight: 700; margin: 0;">
6
6
  <i class="bi bi-shield-exclamation me-2"></i>
7
- Rate Limit Events
7
+ <%= red_t("red.errors.rack_attack_page.title") %>
8
8
  </h1>
9
9
 
10
10
  <div class="btn-group" role="group">
11
11
  <%= link_to rack_attack_summary_errors_path(days: 7), class: "btn btn-sm #{@days == 7 ? 'btn-primary' : 'btn-outline-primary'}" do %>
12
- 7 Days
12
+ <%= red_t("red.common.days_filter.7") %>
13
13
  <% end %>
14
14
  <%= link_to rack_attack_summary_errors_path(days: 30), class: "btn btn-sm #{@days == 30 ? 'btn-primary' : 'btn-outline-primary'}" do %>
15
- 30 Days
15
+ <%= red_t("red.common.days_filter.30") %>
16
16
  <% end %>
17
17
  <%= link_to rack_attack_summary_errors_path(days: 90), class: "btn btn-sm #{@days == 90 ? 'btn-primary' : 'btn-outline-primary'}" do %>
18
- 90 Days
18
+ <%= red_t("red.common.days_filter.90") %>
19
19
  <% end %>
20
20
  </div>
21
21
  </div>
22
22
 
23
23
  <% if @unique_rules == 0 %>
24
24
  <div class="red-empty-state">
25
- <i class="bi bi-shield-check display-1 text-success mb-3"></i>
26
- <div class="red-empty-state-title">No Rate Limit Events Found</div>
27
- <p class="text-muted">
28
- No Rack Attack throttle, blocklist, or track events were recorded over the last <%= @days %> days.
29
- </p>
25
+ <% if @rack_attack_missing %>
26
+ <i class="bi bi-exclamation-triangle display-1 text-warning mb-3"></i>
27
+ <div class="red-empty-state-title"><%= red_t("red.errors.rack_attack_page.missing_title") %></div>
28
+ <p class="text-muted">
29
+ <%= red_t("red.errors.rack_attack_page.missing_body_html", gem: content_tag(:code, "rack-attack")) %>
30
+ </p>
31
+ <% else %>
32
+ <i class="bi bi-shield-check display-1 text-success mb-3"></i>
33
+ <div class="red-empty-state-title"><%= red_t("red.errors.rack_attack_page.empty_title") %></div>
34
+ <p class="text-muted">
35
+ <%= red_t("red.errors.rack_attack_page.empty_body", days: @days) %>
36
+ </p>
37
+ <% end %>
30
38
  <div class="card mx-auto" style="max-width: 500px;">
31
39
  <div class="card-body text-start">
32
- <h6>How Rack Attack tracking works:</h6>
40
+ <h6><%= red_t("red.errors.rack_attack_page.how_title") %></h6>
33
41
  <ul class="mb-0">
34
- <li>Rack Attack tracking must be enabled (<code>enable_rack_attack_tracking = true</code>)</li>
35
- <li>Rack Attack must be installed and configured in your app</li>
36
- <li>Throttle, blocklist, and track events are recorded whenever a rule matches — no error required</li>
37
- <li>Events are buffered and written every <%= RailsErrorDashboard.configuration.rack_attack_flush_interval %>s, so allow a short delay before they appear</li>
42
+ <li><%= red_t("red.errors.rack_attack_page.how_enabled_html", option: content_tag(:code, "enable_rack_attack_tracking = true")) %></li>
43
+ <li><%= red_t("red.errors.rack_attack_page.how_installed") %></li>
44
+ <li><%= red_t("red.errors.rack_attack_page.how_recorded") %></li>
45
+ <li><%= red_t("red.errors.rack_attack_page.how_buffered", seconds: RailsErrorDashboard.configuration.rack_attack_flush_interval) %></li>
38
46
  </ul>
39
47
  </div>
40
48
  </div>
@@ -45,7 +53,7 @@
45
53
  <div class="card text-center">
46
54
  <div class="card-body">
47
55
  <div class="display-6 text-warning"><%= @unique_rules %></div>
48
- <small class="text-muted">Unique Rules</small>
56
+ <small class="text-muted"><%= red_t("red.errors.rack_attack_page.unique_rules") %></small>
49
57
  </div>
50
58
  </div>
51
59
  </div>
@@ -53,7 +61,7 @@
53
61
  <div class="card text-center">
54
62
  <div class="card-body">
55
63
  <div class="display-6 text-danger"><%= @total_events %></div>
56
- <small class="text-muted">Total Events</small>
64
+ <small class="text-muted"><%= red_t("red.errors.rack_attack_page.total_events") %></small>
57
65
  </div>
58
66
  </div>
59
67
  </div>
@@ -61,7 +69,7 @@
61
69
  <div class="card text-center">
62
70
  <div class="card-body">
63
71
  <div class="display-6 text-info"><%= @unique_ips %></div>
64
- <small class="text-muted">Unique IPs</small>
72
+ <small class="text-muted"><%= red_t("red.errors.rack_attack_page.unique_ips") %></small>
65
73
  </div>
66
74
  </div>
67
75
  </div>
@@ -71,7 +79,7 @@
71
79
  <div class="card-header d-flex justify-content-between align-items-center">
72
80
  <h5 class="mb-0">
73
81
  <i class="bi bi-shield-exclamation text-warning me-2"></i>
74
- Rate Limit Events by Rule
82
+ <%= red_t("red.errors.rack_attack_page.all_title") %>
75
83
  <span class="badge bg-warning text-dark"><%= @unique_rules %></span>
76
84
  </h5>
77
85
  <small class="text-muted"><%== @pagy.info_tag %></small>
@@ -81,12 +89,12 @@
81
89
  <table class="table table-hover mb-0">
82
90
  <thead class="table-light">
83
91
  <tr>
84
- <th>Rule</th>
85
- <th width="100">Type</th>
86
- <th width="80">Count</th>
87
- <th width="80">IPs</th>
88
- <th>Top Path</th>
89
- <th width="140">Last Seen</th>
92
+ <th><%= red_t("red.errors.rack_attack_page.column_rule") %></th>
93
+ <th width="100"><%= red_t("red.errors.rack_attack_page.column_type") %></th>
94
+ <th width="80"><%= red_t("red.errors.rack_attack_page.column_count") %></th>
95
+ <th width="80"><%= red_t("red.errors.rack_attack_page.column_ips") %></th>
96
+ <th><%= red_t("red.errors.rack_attack_page.column_top_path") %></th>
97
+ <th width="140"><%= red_t("red.errors.pages.column_last_seen") %></th>
90
98
  </tr>
91
99
  </thead>
92
100
  <tbody>
@@ -99,7 +107,10 @@
99
107
  when "throttle" then "warning"
100
108
  else "secondary"
101
109
  end %>
102
- <span class="badge bg-<%= type_color %>"><%= event[:match_type] %></span>
110
+ <%# The match type is a machine value the case above compares
111
+ by string equality, so the comparison keeps the English
112
+ literal and only the display goes through a key. %>
113
+ <span class="badge bg-<%= type_color %>"><%= red_t("red.errors.rack_attack_page.match_type.#{event[:match_type]}") %></span>
103
114
  </td>
104
115
  <td><strong><%= event[:count] %></strong></td>
105
116
  <td><%= event[:unique_ips] %></td>
@@ -114,11 +125,11 @@
114
125
  <div class="card-footer border-top d-flex justify-content-between align-items-center">
115
126
  <div>
116
127
  <small class="text-muted">
117
- <i class="bi bi-lightbulb text-warning"></i> High counts may indicate abuse or misconfigured rules.
128
+ <i class="bi bi-lightbulb text-warning"></i> <%= red_t("red.errors.rack_attack_page.footer_tip") %>
118
129
  </small>
119
130
  <small class="ms-3">
120
131
  <a href="https://github.com/rack/rack-attack" target="_blank" rel="noopener" class="text-decoration-none">
121
- <i class="bi bi-book"></i> Rack Attack Docs <i class="bi bi-box-arrow-up-right" style="font-size: 0.7em;"></i>
132
+ <i class="bi bi-book"></i> <%= red_t("red.errors.rack_attack_page.docs") %> <i class="bi bi-box-arrow-up-right" style="font-size: 0.7em;"></i>
122
133
  </a>
123
134
  </small>
124
135
  </div>
@@ -1,21 +1,21 @@
1
- <% content_for :page_title, "Releases" %>
1
+ <% content_for :page_title, red_t("red.analytics.releases.title") %>
2
2
 
3
3
  <div>
4
4
  <div class="d-flex justify-content-between align-items-center mb-4">
5
5
  <h1 style="font-size: 20px; font-weight: 700; margin: 0;">
6
6
  <i class="bi bi-rocket-takeoff me-2"></i>
7
- Releases
7
+ <%= red_t("red.analytics.releases.title") %>
8
8
  </h1>
9
9
 
10
10
  <div class="btn-group" role="group">
11
11
  <%= link_to releases_errors_path(days: 7), class: "btn btn-sm #{@days == 7 ? 'btn-primary' : 'btn-outline-primary'}" do %>
12
- 7 Days
12
+ <%= red_t("red.common.days_filter.7") %>
13
13
  <% end %>
14
14
  <%= link_to releases_errors_path(days: 30), class: "btn btn-sm #{@days == 30 ? 'btn-primary' : 'btn-outline-primary'}" do %>
15
- 30 Days
15
+ <%= red_t("red.common.days_filter.30") %>
16
16
  <% end %>
17
17
  <%= link_to releases_errors_path(days: 90), class: "btn btn-sm #{@days == 90 ? 'btn-primary' : 'btn-outline-primary'}" do %>
18
- 90 Days
18
+ <%= red_t("red.common.days_filter.90") %>
19
19
  <% end %>
20
20
  </div>
21
21
  </div>
@@ -23,18 +23,20 @@
23
23
  <% if @releases.empty? %>
24
24
  <div class="red-empty-state">
25
25
  <i class="bi bi-rocket-takeoff display-1 text-muted mb-3"></i>
26
- <div class="red-empty-state-title">No Release Data Found</div>
26
+ <div class="red-empty-state-title"><%= red_t("red.analytics.releases.empty.title") %></div>
27
27
  <p class="text-muted">
28
- No version data was detected in errors over the last <%= @days %> days.
28
+ <%= red_t("red.analytics.releases.empty.message", days: @days) %>
29
29
  </p>
30
30
  <div class="card mx-auto" style="max-width: 500px;">
31
31
  <div class="card-body text-start">
32
- <h6>How to enable release tracking:</h6>
32
+ <h6><%= red_t("red.analytics.releases.empty.how_to_title") %></h6>
33
33
  <ul class="mb-0">
34
- <li>Set <code>config.app_version</code> in your initializer</li>
35
- <li>Or set the <code>APP_VERSION</code> environment variable</li>
36
- <li>Git SHA is auto-detected from <code>GIT_SHA</code>, <code>HEROKU_SLUG_COMMIT</code>, or <code>RENDER_GIT_COMMIT</code></li>
37
- <li>Version and SHA are captured automatically when errors occur</li>
34
+ <li><%= red_t("red.analytics.releases.empty.step_config_html", option: content_tag(:code, "config.app_version")) %></li>
35
+ <li><%= red_t("red.analytics.releases.empty.step_env_html", env_var: content_tag(:code, "APP_VERSION")) %></li>
36
+ <li><%= red_t("red.analytics.releases.empty.step_git_sha_html",
37
+ git_sha: content_tag(:code, "GIT_SHA"), heroku: content_tag(:code, "HEROKU_SLUG_COMMIT"),
38
+ render: content_tag(:code, "RENDER_GIT_COMMIT")) %></li>
39
+ <li><%= red_t("red.analytics.releases.empty.step_captured") %></li>
38
40
  </ul>
39
41
  </div>
40
42
  </div>
@@ -50,15 +52,15 @@
50
52
  <div class="card text-center">
51
53
  <div class="card-body">
52
54
  <div class="display-6 text-primary"><%= @summary[:total_releases] %></div>
53
- <small class="text-muted">Releases</small>
55
+ <small class="text-muted"><%= red_t("red.analytics.releases.summary.releases") %></small>
54
56
  </div>
55
57
  </div>
56
58
  </div>
57
59
  <div class="col-md-3">
58
60
  <div class="card text-center">
59
61
  <div class="card-body">
60
- <div class="display-6"><code><%= @summary[:current_version] || "N/A" %></code></div>
61
- <small class="text-muted">Current Release</small>
62
+ <div class="display-6"><code><%= @summary[:current_version] || red_t("red.common.not_available") %></code></div>
63
+ <small class="text-muted"><%= red_t("red.analytics.releases.summary.current_release") %></small>
62
64
  </div>
63
65
  </div>
64
66
  </div>
@@ -66,7 +68,7 @@
66
68
  <div class="card text-center">
67
69
  <div class="card-body">
68
70
  <div class="display-6 text-warning"><%= @summary[:avg_errors_per_release] %></div>
69
- <small class="text-muted">Avg Errors / Release</small>
71
+ <small class="text-muted"><%= red_t("red.analytics.releases.summary.avg_errors") %></small>
70
72
  </div>
71
73
  </div>
72
74
  </div>
@@ -77,7 +79,7 @@
77
79
  problematic_count = @releases.count { |r| r[:problematic] }
78
80
  %>
79
81
  <div class="display-6 <%= problematic_count > 0 ? 'text-danger' : 'text-success' %>"><%= problematic_count %></div>
80
- <small class="text-muted">Problematic Releases</small>
82
+ <small class="text-muted"><%= red_t("red.analytics.releases.summary.problematic") %></small>
81
83
  </div>
82
84
  </div>
83
85
  </div>
@@ -88,19 +90,19 @@
88
90
  <div class="card mb-4 border-primary">
89
91
  <div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
90
92
  <h5 class="mb-0">
91
- <i class="bi bi-broadcast"></i> Current Release: <strong><%= current_release[:version] %></strong>
93
+ <i class="bi bi-broadcast"></i> <%= red_t("red.analytics.releases.current_card.title") %> <strong><%= current_release[:version] %></strong>
92
94
  </h5>
93
- <span class="badge bg-light text-primary">Live</span>
95
+ <span class="badge bg-light text-primary"><%= red_t("red.analytics.releases.current_card.live_badge") %></span>
94
96
  </div>
95
97
  <div class="card-body">
96
98
  <div class="row text-center">
97
99
  <div class="col-md-2">
98
100
  <div class="h4 mb-0"><%= current_release[:total_errors] %></div>
99
- <small class="text-muted">Errors</small>
101
+ <small class="text-muted"><%= red_t("red.analytics.releases.current_card.errors") %></small>
100
102
  </div>
101
103
  <div class="col-md-2">
102
104
  <div class="h4 mb-0"><%= current_release[:unique_error_types] %></div>
103
- <small class="text-muted">Error Types</small>
105
+ <small class="text-muted"><%= red_t("red.analytics.releases.current_card.error_types") %></small>
104
106
  </div>
105
107
  <div class="col-md-2">
106
108
  <div class="h4 mb-0">
@@ -110,7 +112,7 @@
110
112
  <span class="text-success">0</span>
111
113
  <% end %>
112
114
  </div>
113
- <small class="text-muted">New Errors</small>
115
+ <small class="text-muted"><%= red_t("red.analytics.releases.current_card.new_errors") %></small>
114
116
  </div>
115
117
  <div class="col-md-2">
116
118
  <%
@@ -123,18 +125,18 @@
123
125
  %>
124
126
  <div class="h4 mb-0">
125
127
  <span class="badge bg-<%= stability_color %>">
126
- <%= current_release[:stability].to_s.capitalize %>
128
+ <%= red_t("red.analytics.stability.#{current_release[:stability]}", default: current_release[:stability].to_s) %>
127
129
  </span>
128
130
  </div>
129
- <small class="text-muted">Stability</small>
131
+ <small class="text-muted"><%= red_t("red.analytics.releases.current_card.stability") %></small>
130
132
  </div>
131
133
  <div class="col-md-2">
132
134
  <% if current_release[:git_shas].any? %>
133
135
  <div class="h4 mb-0"><%= git_commit_link(current_release[:git_shas].first) %></div>
134
136
  <% else %>
135
- <div class="h4 mb-0 text-muted">N/A</div>
137
+ <div class="h4 mb-0 text-muted"><%= red_t("red.common.not_available") %></div>
136
138
  <% end %>
137
- <small class="text-muted">Git SHA</small>
139
+ <small class="text-muted"><%= red_t("red.analytics.releases.current_card.git_sha") %></small>
138
140
  </div>
139
141
  <div class="col-md-2">
140
142
  <% if current_release[:delta_from_previous] %>
@@ -149,7 +151,7 @@
149
151
  <% else %>
150
152
  <div class="h4 mb-0 text-muted">&mdash;</div>
151
153
  <% end %>
152
- <small class="text-muted">vs Previous</small>
154
+ <small class="text-muted"><%= red_t("red.analytics.releases.current_card.vs_previous") %></small>
153
155
  </div>
154
156
  </div>
155
157
  </div>
@@ -161,7 +163,7 @@
161
163
  <div class="card-header d-flex justify-content-between align-items-center">
162
164
  <h5 class="mb-0">
163
165
  <i class="bi bi-clock-history text-primary me-2"></i>
164
- Release Timeline
166
+ <%= red_t("red.analytics.releases.timeline.title") %>
165
167
  <span class="badge bg-primary"><%= @summary[:total_releases] %></span>
166
168
  </h5>
167
169
  <small class="text-muted"><%== @pagy.info_tag %></small>
@@ -171,14 +173,14 @@
171
173
  <table class="table table-hover mb-0">
172
174
  <thead class="table-light">
173
175
  <tr>
174
- <th>Version</th>
175
- <th>Git SHA</th>
176
- <th>Time Range</th>
177
- <th width="100">Errors</th>
178
- <th width="100">Types</th>
179
- <th width="120">New Errors</th>
180
- <th width="100">Stability</th>
181
- <th width="120">vs Previous</th>
176
+ <th><%= red_t("red.analytics.releases.timeline.column_version") %></th>
177
+ <th><%= red_t("red.analytics.releases.timeline.column_git_sha") %></th>
178
+ <th><%= red_t("red.analytics.releases.timeline.column_time_range") %></th>
179
+ <th width="100"><%= red_t("red.analytics.releases.timeline.column_errors") %></th>
180
+ <th width="100"><%= red_t("red.analytics.releases.timeline.column_types") %></th>
181
+ <th width="120"><%= red_t("red.analytics.releases.timeline.column_new_errors") %></th>
182
+ <th width="100"><%= red_t("red.analytics.releases.timeline.column_stability") %></th>
183
+ <th width="120"><%= red_t("red.analytics.releases.timeline.column_vs_previous") %></th>
182
184
  </tr>
183
185
  </thead>
184
186
  <tbody>
@@ -189,20 +191,20 @@
189
191
  <strong><%= release[:version] %></strong>
190
192
  <% end %>
191
193
  <% if release[:current] %>
192
- <span class="badge bg-primary ms-1">Current</span>
194
+ <span class="badge bg-primary ms-1"><%= red_t("red.analytics.releases.timeline.current_badge") %></span>
193
195
  <% end %>
194
196
  <% if release[:problematic] %>
195
- <span class="badge bg-danger ms-1">Problematic</span>
197
+ <span class="badge bg-danger ms-1"><%= red_t("red.analytics.releases.timeline.problematic_badge") %></span>
196
198
  <% end %>
197
199
  </td>
198
200
  <td>
199
201
  <% if release[:git_shas].any? %>
200
202
  <%= git_commit_link(release[:git_shas].first) %>
201
203
  <% if release[:git_shas].size > 1 %>
202
- <small class="text-muted">+<%= release[:git_shas].size - 1 %></small>
204
+ <small class="text-muted"><%= red_t("red.analytics.releases.timeline.more_shas", count: release[:git_shas].size - 1) %></small>
203
205
  <% end %>
204
206
  <% else %>
205
- <span class="text-muted">N/A</span>
207
+ <span class="text-muted"><%= red_t("red.common.not_available") %></span>
206
208
  <% end %>
207
209
  </td>
208
210
  <td>
@@ -222,11 +224,11 @@
222
224
  <td>
223
225
  <% if release[:new_error_count] > 0 %>
224
226
  <span class="badge bg-danger">
225
- <i class="bi bi-exclamation-circle"></i> <%= release[:new_error_count] %> new
227
+ <i class="bi bi-exclamation-circle"></i> <%= red_tp("red.analytics.releases.timeline.new_count", count: release[:new_error_count]) %>
226
228
  </span>
227
229
  <% else %>
228
230
  <span class="badge bg-success">
229
- <i class="bi bi-check-circle"></i> 0 new
231
+ <i class="bi bi-check-circle"></i> <%= red_tp("red.analytics.releases.timeline.new_count", count: 0) %>
230
232
  </span>
231
233
  <% end %>
232
234
  </td>
@@ -241,7 +243,7 @@
241
243
  %>
242
244
  <span class="badge bg-<%= stability_color %>">
243
245
  <i class="bi bi-circle-fill" style="font-size: 0.5em; vertical-align: middle;"></i>
244
- <%= release[:stability].to_s.capitalize %>
246
+ <%= red_t("red.analytics.stability.#{release[:stability]}", default: release[:stability].to_s) %>
245
247
  </span>
246
248
  </td>
247
249
  <td>
@@ -272,10 +274,7 @@
272
274
  <div class="card-footer border-top d-flex justify-content-between align-items-center">
273
275
  <small class="text-muted">
274
276
  <i class="bi bi-info-circle"></i>
275
- Stability: <span class="badge bg-success">Green</span> &le; avg errors,
276
- <span class="badge bg-warning">Yellow</span> 1-2x avg,
277
- <span class="badge bg-danger">Red</span> &gt; 2x avg.
278
- "New" errors are error types first seen in that release.
277
+ <%= red_t("red.analytics.releases.timeline.footer_html") %>
279
278
  </small>
280
279
  <div>
281
280
  <%== @pagy.series_nav(:bootstrap) if @pagy.pages > 1 %>