solid_stack_web 1.5.0 → 1.6.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +157 -3
  3. data/app/assets/stylesheets/solid_stack_web/_02_layout.css +12 -0
  4. data/app/assets/stylesheets/solid_stack_web/_07_dashboard.css +4 -3
  5. data/app/controllers/solid_stack_web/application_controller.rb +10 -0
  6. data/app/controllers/solid_stack_web/audit_controller.rb +1 -1
  7. data/app/controllers/solid_stack_web/cable/channel_purges_controller.rb +1 -1
  8. data/app/controllers/solid_stack_web/cable/purges_controller.rb +1 -1
  9. data/app/controllers/solid_stack_web/cache/flushes_controller.rb +1 -1
  10. data/app/controllers/solid_stack_web/cache_entries_controller.rb +1 -1
  11. data/app/controllers/solid_stack_web/failed_jobs/arguments_controller.rb +3 -3
  12. data/app/controllers/solid_stack_web/failed_jobs/selections_controller.rb +4 -4
  13. data/app/controllers/solid_stack_web/failed_jobs_controller.rb +2 -2
  14. data/app/controllers/solid_stack_web/jobs/selections_controller.rb +2 -2
  15. data/app/controllers/solid_stack_web/jobs_controller.rb +2 -2
  16. data/app/controllers/solid_stack_web/recurring_tasks/runs_controller.rb +4 -4
  17. data/app/controllers/solid_stack_web/scheduled_jobs_controller.rb +5 -5
  18. data/app/helpers/solid_stack_web/application_helper.rb +30 -17
  19. data/app/views/layouts/solid_stack_web/application.html.erb +28 -24
  20. data/app/views/solid_stack_web/audit/index.html.erb +18 -18
  21. data/app/views/solid_stack_web/cable/index.html.erb +22 -19
  22. data/app/views/solid_stack_web/cable_messages/index.html.erb +15 -14
  23. data/app/views/solid_stack_web/cache/index.html.erb +19 -19
  24. data/app/views/solid_stack_web/cache_entries/index.html.erb +16 -15
  25. data/app/views/solid_stack_web/cache_entries/show.html.erb +11 -11
  26. data/app/views/solid_stack_web/dashboard/index.html.erb +54 -33
  27. data/app/views/solid_stack_web/errors/internal_server_error.html.erb +4 -4
  28. data/app/views/solid_stack_web/errors/not_found.html.erb +4 -4
  29. data/app/views/solid_stack_web/failed_jobs/destroy.turbo_stream.erb +2 -2
  30. data/app/views/solid_stack_web/failed_jobs/errors/index.html.erb +10 -10
  31. data/app/views/solid_stack_web/failed_jobs/index.html.erb +22 -22
  32. data/app/views/solid_stack_web/failed_jobs/show.html.erb +16 -16
  33. data/app/views/solid_stack_web/history/index.html.erb +19 -18
  34. data/app/views/solid_stack_web/jobs/_empty.html.erb +8 -8
  35. data/app/views/solid_stack_web/jobs/index.html.erb +35 -34
  36. data/app/views/solid_stack_web/jobs/show.html.erb +16 -16
  37. data/app/views/solid_stack_web/processes/index.html.erb +8 -8
  38. data/app/views/solid_stack_web/queues/index.html.erb +13 -13
  39. data/app/views/solid_stack_web/queues/show.html.erb +16 -16
  40. data/app/views/solid_stack_web/recurring_tasks/index.html.erb +16 -16
  41. data/app/views/solid_stack_web/shared/_locale_switcher.html.erb +14 -0
  42. data/app/views/solid_stack_web/stats/index.html.erb +13 -13
  43. data/config/locales/en.yml +395 -0
  44. data/config/locales/es.yml +395 -0
  45. data/lib/solid_stack_web/engine.rb +1 -0
  46. data/lib/solid_stack_web/version.rb +1 -1
  47. data/lib/solid_stack_web.rb +14 -1
  48. metadata +4 -1
@@ -1,9 +1,9 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en">
2
+ <html lang="<%= I18n.locale %>">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Solid Stack Dashboard</title>
6
+ <title><%= t("solid_stack_web.layout.title") %></title>
7
7
  <link rel="icon" href="data:,">
8
8
  <%= csrf_meta_tags %>
9
9
  <%= csp_meta_tag %>
@@ -11,61 +11,65 @@
11
11
  <%= javascript_importmap_tags "solid_stack_web" %>
12
12
  </head>
13
13
  <body data-controller="theme">
14
- <a href="#main-content" class="sqw-skip-link">Skip to main content</a>
14
+ <a href="#main-content" class="sqw-skip-link"><%= t("solid_stack_web.layout.skip_to_content") %></a>
15
15
  <header class="sqw-header">
16
16
  <div class="sqw-header__inner">
17
- <%= link_to "Solid Stack", root_path, class: "sqw-header__logo" %>
18
- <nav class="sqw-nav" aria-label="Main navigation">
19
- <%= link_to "Queue", jobs_path,
17
+ <%= link_to t("solid_stack_web.layout.logo"), root_path, class: "sqw-header__logo" %>
18
+ <nav class="sqw-nav" aria-label="<%= t("solid_stack_web.layout.nav.main_navigation") %>">
19
+ <%= link_to t("solid_stack_web.layout.nav.queue"), jobs_path,
20
20
  class: "sqw-nav__link#{" sqw-nav__link--active" if current_section == :queue}" %>
21
- <%= link_to "Cache", cache_path,
21
+ <%= link_to t("solid_stack_web.layout.nav.cache"), cache_path,
22
22
  class: "sqw-nav__link#{" sqw-nav__link--active" if current_section == :cache}" %>
23
- <%= link_to "Cable", cable_path,
23
+ <%= link_to t("solid_stack_web.layout.nav.cable"), cable_path,
24
24
  class: "sqw-nav__link#{" sqw-nav__link--active" if current_section == :cable}" %>
25
+ <% SolidStackWeb.nav_links.each do |link| %>
26
+ <%= link_to link[:label], link[:url], class: "sqw-nav__link" %>
27
+ <% end %>
25
28
  </nav>
26
- <button class="sqw-theme-toggle" aria-label="Switch to dark mode"
29
+ <%= render "solid_stack_web/shared/locale_switcher" %>
30
+ <button class="sqw-theme-toggle" aria-label="<%= t("solid_stack_web.layout.theme_toggle") %>"
27
31
  data-theme-target="toggle" data-action="theme#toggle">☽</button>
28
32
  </div>
29
33
  </header>
30
34
 
31
35
  <% if current_section == :cache %>
32
- <nav class="sqw-subnav" aria-label="Cache section">
36
+ <nav class="sqw-subnav" aria-label="<%= t("solid_stack_web.layout.subnav.cache_section") %>">
33
37
  <div class="sqw-subnav__inner">
34
- <%= link_to "Overview", cache_path,
38
+ <%= link_to t("solid_stack_web.layout.subnav.overview"), cache_path,
35
39
  class: "sqw-subnav__link#{" sqw-subnav__link--active" if controller_name == "cache"}" %>
36
- <%= link_to "Entries", cache_entries_path,
40
+ <%= link_to t("solid_stack_web.layout.subnav.entries"), cache_entries_path,
37
41
  class: "sqw-subnav__link#{" sqw-subnav__link--active" if controller_name == "cache_entries"}" %>
38
42
  </div>
39
43
  </nav>
40
44
  <% end %>
41
45
 
42
46
  <% if current_section == :cable %>
43
- <nav class="sqw-subnav" aria-label="Cable section">
47
+ <nav class="sqw-subnav" aria-label="<%= t("solid_stack_web.layout.subnav.cable_section") %>">
44
48
  <div class="sqw-subnav__inner">
45
- <%= link_to "Overview", cable_path,
49
+ <%= link_to t("solid_stack_web.layout.subnav.overview"), cable_path,
46
50
  class: "sqw-subnav__link#{" sqw-subnav__link--active" if controller_name == "cable"}" %>
47
51
  </div>
48
52
  </nav>
49
53
  <% end %>
50
54
 
51
55
  <% if current_section == :queue %>
52
- <nav class="sqw-subnav" aria-label="Queue section">
56
+ <nav class="sqw-subnav" aria-label="<%= t("solid_stack_web.layout.subnav.queue_section") %>">
53
57
  <div class="sqw-subnav__inner">
54
- <%= link_to "Jobs", jobs_path,
58
+ <%= link_to t("solid_stack_web.layout.subnav.jobs"), jobs_path,
55
59
  class: "sqw-subnav__link#{" sqw-subnav__link--active" if controller_name == "jobs"}" %>
56
- <%= link_to "Failed", failed_jobs_path,
60
+ <%= link_to t("solid_stack_web.layout.subnav.failed"), failed_jobs_path,
57
61
  class: "sqw-subnav__link#{" sqw-subnav__link--active" if controller_name == "failed_jobs"}" %>
58
- <%= link_to "Queues", queues_path,
62
+ <%= link_to t("solid_stack_web.layout.subnav.queues"), queues_path,
59
63
  class: "sqw-subnav__link#{" sqw-subnav__link--active" if controller_name == "queues"}" %>
60
- <%= link_to "Recurring", recurring_tasks_path,
64
+ <%= link_to t("solid_stack_web.layout.subnav.recurring"), recurring_tasks_path,
61
65
  class: "sqw-subnav__link#{" sqw-subnav__link--active" if controller_name == "recurring_tasks"}" %>
62
- <%= link_to "Stats", stats_path,
66
+ <%= link_to t("solid_stack_web.layout.subnav.stats"), stats_path,
63
67
  class: "sqw-subnav__link#{" sqw-subnav__link--active" if controller_name == "stats"}" %>
64
- <%= link_to "History", history_path,
68
+ <%= link_to t("solid_stack_web.layout.subnav.history"), history_path,
65
69
  class: "sqw-subnav__link#{" sqw-subnav__link--active" if controller_name == "history"}" %>
66
- <%= link_to "Processes", processes_path,
70
+ <%= link_to t("solid_stack_web.layout.subnav.processes"), processes_path,
67
71
  class: "sqw-subnav__link#{" sqw-subnav__link--active" if controller_name == "processes"}" %>
68
- <%= link_to "Audit", audit_path,
72
+ <%= link_to t("solid_stack_web.layout.subnav.audit"), audit_path,
69
73
  class: "sqw-subnav__link#{" sqw-subnav__link--active" if controller_name == "audit"}" %>
70
74
  </div>
71
75
  </nav>
@@ -80,4 +84,4 @@
80
84
  <%= yield %>
81
85
  </main>
82
86
  </body>
83
- </html>
87
+ </html>
@@ -1,16 +1,16 @@
1
1
  <div class="sqw-page-header sqw-page-header--split">
2
- <h1 class="sqw-page-title">Audit Log</h1>
2
+ <h1 class="sqw-page-title"><%= t("solid_stack_web.audit.title") %></h1>
3
3
  <% if @events&.any? %>
4
- <%= link_to "Export CSV",
4
+ <%= link_to t("solid_stack_web.audit.export_csv"),
5
5
  audit_path(format: :csv, audit_action: @action_filter, actor: @actor_filter, queue: @queue_filter),
6
6
  class: "sqw-btn sqw-btn--muted sqw-btn--sm", data: { turbo: false } %>
7
7
  <% end %>
8
8
  </div>
9
9
 
10
10
  <form class="sqw-filters" action="<%= audit_path %>" method="get">
11
- <select name="audit_action" class="sqw-select" aria-label="Filter by action"
11
+ <select name="audit_action" class="sqw-select" aria-label="<%= t("solid_stack_web.audit.aria_filter_action") %>"
12
12
  onchange="this.form.submit()">
13
- <option value="">All actions</option>
13
+ <option value=""><%= t("solid_stack_web.audit.all_actions") %></option>
14
14
  <% SolidStackWeb::AuditEvent::ACTIONS.each do |action| %>
15
15
  <option value="<%= action %>" <%= "selected" if @action_filter == action %>><%= action.tr("_", " ") %></option>
16
16
  <% end %>
@@ -18,20 +18,20 @@
18
18
 
19
19
  <% if @actor_filter.present? %>
20
20
  <span class="sqw-badge">
21
- Actor: <%= @actor_filter %>
22
- <%= link_to "×", audit_path(audit_action: @action_filter, queue: @queue_filter), class: "sqw-muted", aria: { label: "Clear actor filter" } %>
21
+ <%= t("solid_stack_web.audit.actor_label") %> <%= @actor_filter %>
22
+ <%= link_to "×", audit_path(audit_action: @action_filter, queue: @queue_filter), class: "sqw-muted", aria: { label: t("solid_stack_web.audit.aria_clear_actor") } %>
23
23
  </span>
24
24
  <% end %>
25
25
 
26
26
  <% if @queue_filter.present? %>
27
27
  <span class="sqw-badge">
28
- Queue: <%= @queue_filter %>
29
- <%= link_to "×", audit_path(audit_action: @action_filter, actor: @actor_filter), class: "sqw-muted", aria: { label: "Clear queue filter" } %>
28
+ <%= t("solid_stack_web.audit.queue_label") %> <%= @queue_filter %>
29
+ <%= link_to "×", audit_path(audit_action: @action_filter, actor: @actor_filter), class: "sqw-muted", aria: { label: t("solid_stack_web.audit.aria_clear_queue") } %>
30
30
  </span>
31
31
  <% end %>
32
32
 
33
33
  <% if @action_filter.present? || @actor_filter.present? || @queue_filter.present? %>
34
- <%= link_to "Clear all", audit_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
34
+ <%= link_to t("solid_stack_web.audit.clear_all"), audit_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
35
35
  <% end %>
36
36
  </form>
37
37
 
@@ -39,12 +39,12 @@
39
39
  <table class="sqw-table">
40
40
  <thead>
41
41
  <tr>
42
- <th scope="col">Time</th>
43
- <th scope="col">Action</th>
44
- <th scope="col">Actor</th>
45
- <th scope="col">Job Class</th>
46
- <th scope="col">Queue</th>
47
- <th scope="col">Count</th>
42
+ <th scope="col"><%= t("solid_stack_web.audit.col_time") %></th>
43
+ <th scope="col"><%= t("solid_stack_web.audit.col_action") %></th>
44
+ <th scope="col"><%= t("solid_stack_web.audit.col_actor") %></th>
45
+ <th scope="col"><%= t("solid_stack_web.audit.col_job_class") %></th>
46
+ <th scope="col"><%= t("solid_stack_web.audit.col_queue") %></th>
47
+ <th scope="col"><%= t("solid_stack_web.audit.col_count") %></th>
48
48
  </tr>
49
49
  </thead>
50
50
  <tbody>
@@ -78,9 +78,9 @@
78
78
  <% end %>
79
79
  </tbody>
80
80
  </table>
81
- <%== @pagy.series_nav(aria_label: "Pagination") if @pagy.pages > 1 %>
81
+ <%== @pagy.series_nav(aria_label: t("solid_stack_web.shared.pagination")) if @pagy.pages > 1 %>
82
82
  <% else %>
83
83
  <div class="sqw-empty" id="sqd-empty">
84
- <p class="sqw-muted">No audit events recorded yet.</p>
84
+ <p class="sqw-muted"><%= t("solid_stack_web.audit.empty") %></p>
85
85
  </div>
86
- <% end %>
86
+ <% end %>
@@ -1,48 +1,51 @@
1
1
  <div class="sqw-page-header sqw-page-header--split">
2
- <h1 class="sqw-page-title">Solid Cable</h1>
2
+ <h1 class="sqw-page-title"><%= t("solid_stack_web.cable.title") %></h1>
3
3
  <div class="sqw-header-actions">
4
4
  <%= form_with url: cable_purge_path, method: :delete, class: "sqw-inline-form",
5
- data: { turbo_confirm: "Purge these messages? This cannot be undone." } do |f| %>
5
+ data: { turbo_confirm: t("solid_stack_web.cable.confirm_purge") } do |f| %>
6
6
  <%= f.select :older_than,
7
- [["Older than 1 day", 1], ["Older than 7 days", 7], ["Older than 30 days", 30]],
7
+ [[t("solid_stack_web.cable.older_than_1_day"), 1],
8
+ [t("solid_stack_web.cable.older_than_7_days"), 7],
9
+ [t("solid_stack_web.cable.older_than_30_days"), 30]],
8
10
  {}, class: "sqw-select" %>
9
- <%= f.submit "Purge Old", class: "sqw-btn sqw-btn--danger sqw-btn--sm" %>
11
+ <%= f.submit t("solid_stack_web.cable.purge_old"), class: "sqw-btn sqw-btn--danger sqw-btn--sm" %>
10
12
  <% end %>
11
13
  </div>
12
14
  </div>
13
15
 
14
16
  <div class="sqw-stats-grid">
15
17
  <div class="sqw-stat sqw-stat--cable">
16
- <span class="sqw-stat__label">Total Messages</span>
18
+ <span class="sqw-stat__label"><%= t("solid_stack_web.cable.total_messages") %></span>
17
19
  <span class="sqw-stat__value"><%= @total_messages %></span>
18
20
  </div>
19
21
  <div class="sqw-stat sqw-stat--cable">
20
- <span class="sqw-stat__label">Channels</span>
22
+ <span class="sqw-stat__label"><%= t("solid_stack_web.cable.channels") %></span>
21
23
  <span class="sqw-stat__value"><%= @channels.size %></span>
22
24
  </div>
23
25
  </div>
24
26
 
25
27
  <div class="sqw-timeline-grid sqw-timeline-grid--single" data-controller="sparkline-tooltip">
26
28
  <div class="sqw-sparkline-wrap sqw-timeline-chart sqw-timeline-chart--cable">
27
- <span class="sqw-sparkline-label">Messages last 24 hours</span>
29
+ <span class="sqw-sparkline-label"><%= t("solid_stack_web.cable.messages_timeline") %></span>
28
30
  <div class="sqw-sparkline-positioner">
29
31
  <%= cable_messages_timeline_svg(@timeline) %>
30
32
  <div class="sqw-sparkline-tip" data-sparkline-tooltip-target="tip" hidden></div>
31
33
  </div>
32
34
  <div class="sqw-sparkline-axis">
33
- <span>24h ago</span>
34
- <span>12h ago</span>
35
- <span>now</span>
35
+ <span><%= t("solid_stack_web.cable.axis_24h_ago") %></span>
36
+ <span><%= t("solid_stack_web.cable.axis_12h_ago") %></span>
37
+ <span><%= t("solid_stack_web.cable.axis_now") %></span>
36
38
  </div>
37
39
  </div>
38
40
  </div>
39
41
 
40
42
  <form class="sqw-filters" action="<%= cable_path %>" method="get" data-controller="search">
41
43
  <input class="sqw-search-input" type="search" name="q" value="<%= @search %>"
42
- placeholder="Filter by channel…" autocomplete="off" aria-label="Filter by channel"
44
+ placeholder="<%= t("solid_stack_web.cable.placeholder_channel") %>" autocomplete="off"
45
+ aria-label="<%= t("solid_stack_web.cable.aria_filter_channel") %>"
43
46
  data-action="input->search#filter">
44
47
  <% if @search.present? %>
45
- <%= link_to "Clear", cable_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
48
+ <%= link_to t("solid_stack_web.shared.clear"), cable_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
46
49
  <% end %>
47
50
  </form>
48
51
 
@@ -50,9 +53,9 @@
50
53
  <table class="sqw-table">
51
54
  <thead>
52
55
  <tr>
53
- <th scope="col">Channel</th>
54
- <th scope="col">Messages</th>
55
- <th scope="col">Last Message</th>
56
+ <th scope="col"><%= t("solid_stack_web.cable.col_channel") %></th>
57
+ <th scope="col"><%= t("solid_stack_web.cable.col_messages") %></th>
58
+ <th scope="col"><%= t("solid_stack_web.cable.col_last_message") %></th>
56
59
  </tr>
57
60
  </thead>
58
61
  <tbody>
@@ -70,11 +73,11 @@
70
73
  <% else %>
71
74
  <div class="sqw-empty">
72
75
  <% if @search.present? %>
73
- <p class="sqw-empty__title">No channels matching &ldquo;<%= @search %>&rdquo;</p>
74
- <p class="sqw-empty__hint"><%= link_to "Clear search", cable_path %></p>
76
+ <p class="sqw-empty__title"><%= t("solid_stack_web.cable.no_channels_matching", search: @search) %></p>
77
+ <p class="sqw-empty__hint"><%= link_to t("solid_stack_web.cable.clear_search"), cable_path %></p>
75
78
  <% else %>
76
- <p class="sqw-empty__title">No cable messages</p>
77
- <p class="sqw-empty__hint">Messages will appear here once clients connect and broadcast over Action Cable.</p>
79
+ <p class="sqw-empty__title"><%= t("solid_stack_web.cable.empty_title") %></p>
80
+ <p class="sqw-empty__hint"><%= t("solid_stack_web.cable.empty_hint") %></p>
78
81
  <% end %>
79
82
  </div>
80
83
  <% end %>
@@ -1,21 +1,22 @@
1
1
  <div class="sqw-page-header sqw-page-header--split">
2
2
  <h1 class="sqw-page-title sqw-truncate" title="<%= @channel_name %>"><%= @channel_name %></h1>
3
3
  <div class="sqw-header-actions">
4
- <%= button_to "Purge Channel",
4
+ <%= button_to t("solid_stack_web.cable_messages.purge_channel"),
5
5
  cable_channel_purge_path(params[:channel_hash]),
6
6
  method: :delete,
7
7
  class: "sqw-btn sqw-btn--danger sqw-btn--sm",
8
- data: { turbo_confirm: "Delete all messages for this channel? This cannot be undone." } %>
9
- <%= link_to "← Channels", cable_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
8
+ data: { turbo_confirm: t("solid_stack_web.cable_messages.confirm_purge_channel") } %>
9
+ <%= link_to t("solid_stack_web.cable_messages.back_to_channels"), cable_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
10
10
  </div>
11
11
  </div>
12
12
 
13
13
  <form class="sqw-filters" action="<%= cable_channel_messages_path(params[:channel_hash]) %>" method="get" data-controller="search">
14
14
  <input class="sqw-search-input" type="search" name="q" value="<%= @search %>"
15
- placeholder="Filter by payload…" autocomplete="off" aria-label="Filter by payload"
15
+ placeholder="<%= t("solid_stack_web.cable_messages.placeholder_payload") %>" autocomplete="off"
16
+ aria-label="<%= t("solid_stack_web.cable_messages.aria_filter_payload") %>"
16
17
  data-action="input->search#filter">
17
18
  <% if @search.present? %>
18
- <%= link_to "Clear", cable_channel_messages_path(params[:channel_hash]), class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
19
+ <%= link_to t("solid_stack_web.shared.clear"), cable_channel_messages_path(params[:channel_hash]), class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
19
20
  <% end %>
20
21
  </form>
21
22
 
@@ -23,9 +24,9 @@
23
24
  <table class="sqw-table">
24
25
  <thead>
25
26
  <tr>
26
- <th scope="col">ID</th>
27
- <th scope="col">Payload</th>
28
- <th scope="col">Sent</th>
27
+ <th scope="col"><%= t("solid_stack_web.cable_messages.col_id") %></th>
28
+ <th scope="col"><%= t("solid_stack_web.cable_messages.col_payload") %></th>
29
+ <th scope="col"><%= t("solid_stack_web.cable_messages.col_sent") %></th>
29
30
  </tr>
30
31
  </thead>
31
32
  <tbody>
@@ -42,15 +43,15 @@
42
43
  <% end %>
43
44
  </tbody>
44
45
  </table>
45
- <%== @pagy.series_nav(aria_label: "Pagination") if @pagy.pages > 1 %>
46
+ <%== @pagy.series_nav(aria_label: t("solid_stack_web.shared.pagination")) if @pagy.pages > 1 %>
46
47
  <% else %>
47
48
  <div class="sqw-empty">
48
49
  <% if @search.present? %>
49
- <p class="sqw-empty__title">No messages matching &ldquo;<%= @search %>&rdquo;</p>
50
- <p class="sqw-empty__hint"><%= link_to "Clear search", cable_channel_messages_path(params[:channel_hash]) %></p>
50
+ <p class="sqw-empty__title"><%= t("solid_stack_web.cable_messages.no_messages_matching", search: @search) %></p>
51
+ <p class="sqw-empty__hint"><%= link_to t("solid_stack_web.cable_messages.clear_search"), cable_channel_messages_path(params[:channel_hash]) %></p>
51
52
  <% else %>
52
- <p class="sqw-empty__title">No messages for this channel</p>
53
- <p class="sqw-empty__hint">Messages may have been purged or the channel has gone quiet. <%= link_to "Back to channels →", cable_path %></p>
53
+ <p class="sqw-empty__title"><%= t("solid_stack_web.cable_messages.empty_title") %></p>
54
+ <p class="sqw-empty__hint"><%= t("solid_stack_web.cable_messages.empty_hint") %> <%= link_to t("solid_stack_web.cable_messages.back_to_channels_link"), cable_path %></p>
54
55
  <% end %>
55
56
  </div>
56
- <% end %>
57
+ <% end %>
@@ -1,14 +1,14 @@
1
1
  <div class="sqw-page-header">
2
- <h1 class="sqw-page-title">Solid Cache</h1>
2
+ <h1 class="sqw-page-title"><%= t("solid_stack_web.cache.title") %></h1>
3
3
  </div>
4
4
 
5
5
  <div class="sqw-stats-grid">
6
6
  <%= link_to cache_entries_path, class: "sqw-stat sqw-stat--cache" do %>
7
- <span class="sqw-stat__label">Total Entries</span>
7
+ <span class="sqw-stat__label"><%= t("solid_stack_web.cache.total_entries") %></span>
8
8
  <span class="sqw-stat__value"><%= @total_entries %></span>
9
9
  <% end %>
10
10
  <div class="sqw-stat sqw-stat--cache">
11
- <span class="sqw-stat__label">Total Size</span>
11
+ <span class="sqw-stat__label"><%= t("solid_stack_web.cache.total_size") %></span>
12
12
  <span class="sqw-stat__value"><%= number_to_human_size(@total_byte_size) %></span>
13
13
  </div>
14
14
  </div>
@@ -16,13 +16,13 @@
16
16
  <% if @total_entries > 0 %>
17
17
  <div class="sqw-size-grid">
18
18
  <section class="sqw-size-section">
19
- <h2 class="sqw-section-title">Size Distribution</h2>
19
+ <h2 class="sqw-section-title"><%= t("solid_stack_web.cache.size_distribution") %></h2>
20
20
  <table class="sqw-table">
21
21
  <thead>
22
22
  <tr>
23
- <th scope="col">Range</th>
24
- <th scope="col">Entries</th>
25
- <th scope="col"><span class="sqw-sr-only">Distribution</span></th>
23
+ <th scope="col"><%= t("solid_stack_web.cache.col_range") %></th>
24
+ <th scope="col"><%= t("solid_stack_web.cache.col_entries") %></th>
25
+ <th scope="col"><span class="sqw-sr-only"><%= t("solid_stack_web.cache.col_distribution") %></span></th>
26
26
  </tr>
27
27
  </thead>
28
28
  <tbody>
@@ -44,12 +44,12 @@
44
44
  </section>
45
45
 
46
46
  <section class="sqw-size-section">
47
- <h2 class="sqw-section-title">Largest Entries</h2>
47
+ <h2 class="sqw-section-title"><%= t("solid_stack_web.cache.largest_entries") %></h2>
48
48
  <table class="sqw-table">
49
49
  <thead>
50
50
  <tr>
51
- <th scope="col">Key</th>
52
- <th scope="col">Size</th>
51
+ <th scope="col"><%= t("solid_stack_web.cache.col_key") %></th>
52
+ <th scope="col"><%= t("solid_stack_web.cache.col_size") %></th>
53
53
  </tr>
54
54
  </thead>
55
55
  <tbody>
@@ -69,28 +69,28 @@
69
69
 
70
70
  <div class="sqw-timeline-grid" data-controller="sparkline-tooltip">
71
71
  <div class="sqw-sparkline-wrap sqw-timeline-chart">
72
- <span class="sqw-sparkline-label">Entries written — last 24 hours</span>
72
+ <span class="sqw-sparkline-label"><%= t("solid_stack_web.cache.entries_written") %></span>
73
73
  <div class="sqw-sparkline-positioner">
74
74
  <%= cache_entries_timeline_svg(@timeline) %>
75
75
  <div class="sqw-sparkline-tip" data-sparkline-tooltip-target="tip" hidden></div>
76
76
  </div>
77
77
  <div class="sqw-sparkline-axis">
78
- <span>24h ago</span>
79
- <span>12h ago</span>
80
- <span>now</span>
78
+ <span><%= t("solid_stack_web.cache.axis_24h_ago") %></span>
79
+ <span><%= t("solid_stack_web.cache.axis_12h_ago") %></span>
80
+ <span><%= t("solid_stack_web.cache.axis_now") %></span>
81
81
  </div>
82
82
  </div>
83
83
 
84
84
  <div class="sqw-sparkline-wrap sqw-timeline-chart">
85
- <span class="sqw-sparkline-label">Bytes written — last 24 hours</span>
85
+ <span class="sqw-sparkline-label"><%= t("solid_stack_web.cache.bytes_written") %></span>
86
86
  <div class="sqw-sparkline-positioner">
87
87
  <%= cache_bytes_timeline_svg(@timeline) %>
88
88
  <div class="sqw-sparkline-tip" data-sparkline-tooltip-target="tip" hidden></div>
89
89
  </div>
90
90
  <div class="sqw-sparkline-axis">
91
- <span>24h ago</span>
92
- <span>12h ago</span>
93
- <span>now</span>
91
+ <span><%= t("solid_stack_web.cache.axis_24h_ago") %></span>
92
+ <span><%= t("solid_stack_web.cache.axis_12h_ago") %></span>
93
+ <span><%= t("solid_stack_web.cache.axis_now") %></span>
94
94
  </div>
95
95
  </div>
96
- </div>
96
+ </div>
@@ -1,11 +1,11 @@
1
1
  <div class="sqw-page-header sqw-page-header--split">
2
- <h1 class="sqw-page-title">Cache Entries</h1>
2
+ <h1 class="sqw-page-title"><%= t("solid_stack_web.cache_entries.title") %></h1>
3
3
  <div class="sqw-header-actions">
4
- <%= button_to "Flush All",
4
+ <%= button_to t("solid_stack_web.cache_entries.flush_all"),
5
5
  cache_flush_path,
6
6
  method: :delete,
7
7
  class: "sqw-btn sqw-btn--danger sqw-btn--sm",
8
- data: { turbo_confirm: "Delete all cache entries? This cannot be undone." } %>
8
+ data: { turbo_confirm: t("solid_stack_web.cache_entries.confirm_flush_all") } %>
9
9
  </div>
10
10
  </div>
11
11
 
@@ -13,10 +13,11 @@
13
13
  <%= hidden_field_tag :column, @sort["column"] %>
14
14
  <%= hidden_field_tag :direction, @sort["direction"] %>
15
15
  <input class="sqw-search-input" type="search" name="q" value="<%= @search %>"
16
- placeholder="Filter by key…" autocomplete="off" aria-label="Filter by key"
16
+ placeholder="<%= t("solid_stack_web.cache_entries.placeholder_key") %>" autocomplete="off"
17
+ aria-label="<%= t("solid_stack_web.cache_entries.aria_filter_key") %>"
17
18
  data-action="input->search#filter">
18
19
  <% if @search.present? %>
19
- <%= link_to "Clear", cache_entries_path(column: @sort["column"], direction: @sort["direction"]),
20
+ <%= link_to t("solid_stack_web.shared.clear"), cache_entries_path(column: @sort["column"], direction: @sort["direction"]),
20
21
  class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
21
22
  <% end %>
22
23
  </form>
@@ -25,7 +26,7 @@
25
26
  <table class="sqw-table">
26
27
  <thead>
27
28
  <tr>
28
- <% [["key", "Key"], ["byte_size", "Size"], ["created_at", "Created"]].each do |col, label| %>
29
+ <% [["key", t("solid_stack_web.cache_entries.col_key")], ["byte_size", t("solid_stack_web.cache_entries.col_size")], ["created_at", t("solid_stack_web.cache_entries.col_created")]].each do |col, label| %>
29
30
  <th scope="col">
30
31
  <% next_dir = (@sort["column"] == col && @sort["direction"] == "desc") ? "asc" : "desc" %>
31
32
  <%= link_to cache_entries_path(q: @search, column: col, direction: next_dir) do %>
@@ -36,7 +37,7 @@
36
37
  <% end %>
37
38
  </th>
38
39
  <% end %>
39
- <th scope="col"><span class="sqw-sr-only">Actions</span></th>
40
+ <th scope="col"><span class="sqw-sr-only"><%= t("solid_stack_web.shared.actions") %></span></th>
40
41
  </tr>
41
42
  </thead>
42
43
  <tbody>
@@ -48,25 +49,25 @@
48
49
  <td><%= number_to_human_size(entry.byte_size) %></td>
49
50
  <td class="sqw-muted"><%= local_time(entry.created_at) %></td>
50
51
  <td class="sqw-actions">
51
- <%= button_to "Delete",
52
+ <%= button_to t("solid_stack_web.cache_entries.delete"),
52
53
  cache_entry_path(entry, q: @search, column: @sort["column"], direction: @sort["direction"]),
53
54
  method: :delete,
54
55
  class: "sqw-btn sqw-btn--danger sqw-btn--sm",
55
- data: { turbo_confirm: "Delete this cache entry?" } %>
56
+ data: { turbo_confirm: t("solid_stack_web.cache_entries.confirm_delete") } %>
56
57
  </td>
57
58
  </tr>
58
59
  <% end %>
59
60
  </tbody>
60
61
  </table>
61
- <%== @pagy.series_nav(aria_label: "Pagination") if @pagy.pages > 1 %>
62
+ <%== @pagy.series_nav(aria_label: t("solid_stack_web.shared.pagination")) if @pagy.pages > 1 %>
62
63
  <% else %>
63
64
  <div class="sqw-empty">
64
65
  <% if @search.present? %>
65
- <p class="sqw-empty__title">No entries matching &ldquo;<%= @search %>&rdquo;</p>
66
- <p class="sqw-empty__hint"><%= link_to "Clear search", cache_entries_path %></p>
66
+ <p class="sqw-empty__title"><%= t("solid_stack_web.cache_entries.no_entries_matching", search: @search) %></p>
67
+ <p class="sqw-empty__hint"><%= link_to t("solid_stack_web.cache_entries.clear_search"), cache_entries_path %></p>
67
68
  <% else %>
68
- <p class="sqw-empty__title">No cache entries</p>
69
- <p class="sqw-empty__hint">Entries will appear here once your application writes to the cache.</p>
69
+ <p class="sqw-empty__title"><%= t("solid_stack_web.cache_entries.empty_title") %></p>
70
+ <p class="sqw-empty__hint"><%= t("solid_stack_web.cache_entries.empty_hint") %></p>
70
71
  <% end %>
71
72
  </div>
72
- <% end %>
73
+ <% end %>
@@ -1,23 +1,23 @@
1
1
  <div class="sqw-page-header sqw-page-header--split">
2
2
  <h1 class="sqw-page-title sqw-truncate" title="<%= @entry.key %>"><%= @entry.key %></h1>
3
3
  <div class="sqw-header-actions">
4
- <%= button_to "Delete",
4
+ <%= button_to t("solid_stack_web.cache_entries.delete"),
5
5
  cache_entry_path(@entry),
6
6
  method: :delete,
7
7
  class: "sqw-btn sqw-btn--danger sqw-btn--sm",
8
- data: { turbo_confirm: "Delete this cache entry?" } %>
9
- <%= link_to "← Entries", cache_entries_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
8
+ data: { turbo_confirm: t("solid_stack_web.cache_entries.confirm_delete") } %>
9
+ <%= link_to t("solid_stack_web.cache_entries.back_to_entries"), cache_entries_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
10
10
  </div>
11
11
  </div>
12
12
 
13
13
  <div class="sqw-detail-card sqw-detail-section">
14
- <h2 class="sqw-section-title">Details</h2>
14
+ <h2 class="sqw-section-title"><%= t("solid_stack_web.cache_entries.show_section_details") %></h2>
15
15
  <dl class="sqw-dl">
16
- <dt>Key</dt>
16
+ <dt><%= t("solid_stack_web.cache_entries.show_key") %></dt>
17
17
  <dd class="sqw-monospace"><%= @entry.key %></dd>
18
- <dt>Size</dt>
18
+ <dt><%= t("solid_stack_web.cache_entries.show_size") %></dt>
19
19
  <dd><%= number_to_human_size(@entry.byte_size) %></dd>
20
- <dt>Created</dt>
20
+ <dt><%= t("solid_stack_web.cache_entries.show_created") %></dt>
21
21
  <dd class="sqw-muted"><%= local_time(@entry.created_at, format: :long) %></dd>
22
22
  </dl>
23
23
  </div>
@@ -25,7 +25,7 @@
25
25
  <div class="sqw-detail-card sqw-detail-section">
26
26
  <% formatted = SolidStackWeb.allow_value_preview ? format_cache_value(@entry.value) : nil %>
27
27
  <div class="sqw-value-preview__header">
28
- <h2 class="sqw-section-title">Value</h2>
28
+ <h2 class="sqw-section-title"><%= t("solid_stack_web.cache_entries.show_section_value") %></h2>
29
29
  <% if formatted %>
30
30
  <span class="sqw-badge sqw-badge--queue"><%= formatted[:label] %></span>
31
31
  <% end %>
@@ -35,11 +35,11 @@
35
35
  <% truncated = content.length > 4096 %>
36
36
  <pre class="sqw-code-block"><%= truncated ? content[0, 4096] : content %></pre>
37
37
  <% if truncated %>
38
- <p class="sqw-muted sqw-value-truncated">Showing first 4 KB of <%= number_to_human_size(@entry.byte_size) %> total.</p>
38
+ <p class="sqw-muted sqw-value-truncated"><%= t("solid_stack_web.cache_entries.show_truncated", size: number_to_human_size(@entry.byte_size)) %></p>
39
39
  <% end %>
40
40
  <% else %>
41
41
  <div class="sqw-empty">
42
- <p>Value preview is disabled. Set <code>config.allow_value_preview = true</code> in your initializer to enable.</p>
42
+ <p><%= t("solid_stack_web.cache_entries.show_preview_disabled") %></p>
43
43
  </div>
44
44
  <% end %>
45
- </div>
45
+ </div>