diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fa1b7e..eef358c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- i18n support — all UI labels, flash messages, table headers, empty states, confirmations, and sparkline tooltips are now backed by locale YAML files; ships with English (`en`) and Spanish (`es`) out of the box +- Locale switcher — a `" onchange="this.form.submit()"> - + <% SolidStackWeb::AuditEvent::ACTIONS.each do |action| %> <% end %> @@ -18,20 +18,20 @@ <% if @actor_filter.present? %> - Actor: <%= @actor_filter %> - <%= link_to "×", audit_path(audit_action: @action_filter, queue: @queue_filter), class: "sqw-muted", aria: { label: "Clear actor filter" } %> + <%= t("solid_stack_web.audit.actor_label") %> <%= @actor_filter %> + <%= link_to "×", audit_path(audit_action: @action_filter, queue: @queue_filter), class: "sqw-muted", aria: { label: t("solid_stack_web.audit.aria_clear_actor") } %> <% end %> <% if @queue_filter.present? %> - Queue: <%= @queue_filter %> - <%= link_to "×", audit_path(audit_action: @action_filter, actor: @actor_filter), class: "sqw-muted", aria: { label: "Clear queue filter" } %> + <%= t("solid_stack_web.audit.queue_label") %> <%= @queue_filter %> + <%= link_to "×", audit_path(audit_action: @action_filter, actor: @actor_filter), class: "sqw-muted", aria: { label: t("solid_stack_web.audit.aria_clear_queue") } %> <% end %> <% if @action_filter.present? || @actor_filter.present? || @queue_filter.present? %> - <%= link_to "Clear all", audit_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> + <%= link_to t("solid_stack_web.audit.clear_all"), audit_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> <% end %> @@ -39,12 +39,12 @@ - - - - - - + + + + + + @@ -78,9 +78,9 @@ <% end %>
TimeActionActorJob ClassQueueCount<%= t("solid_stack_web.audit.col_time") %><%= t("solid_stack_web.audit.col_action") %><%= t("solid_stack_web.audit.col_actor") %><%= t("solid_stack_web.audit.col_job_class") %><%= t("solid_stack_web.audit.col_queue") %><%= t("solid_stack_web.audit.col_count") %>
- <%== @pagy.series_nav(aria_label: "Pagination") if @pagy.pages > 1 %> + <%== @pagy.series_nav(aria_label: t("solid_stack_web.shared.pagination")) if @pagy.pages > 1 %> <% else %>
-

No audit events recorded yet.

+

<%= t("solid_stack_web.audit.empty") %>

-<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/solid_stack_web/cable/index.html.erb b/app/views/solid_stack_web/cable/index.html.erb index b5722b6..9b41294 100644 --- a/app/views/solid_stack_web/cable/index.html.erb +++ b/app/views/solid_stack_web/cable/index.html.erb @@ -1,48 +1,51 @@
-

Solid Cable

+

<%= t("solid_stack_web.cable.title") %>

<%= form_with url: cable_purge_path, method: :delete, class: "sqw-inline-form", - data: { turbo_confirm: "Purge these messages? This cannot be undone." } do |f| %> + data: { turbo_confirm: t("solid_stack_web.cable.confirm_purge") } do |f| %> <%= f.select :older_than, - [["Older than 1 day", 1], ["Older than 7 days", 7], ["Older than 30 days", 30]], + [[t("solid_stack_web.cable.older_than_1_day"), 1], + [t("solid_stack_web.cable.older_than_7_days"), 7], + [t("solid_stack_web.cable.older_than_30_days"), 30]], {}, class: "sqw-select" %> - <%= f.submit "Purge Old", class: "sqw-btn sqw-btn--danger sqw-btn--sm" %> + <%= f.submit t("solid_stack_web.cable.purge_old"), class: "sqw-btn sqw-btn--danger sqw-btn--sm" %> <% end %>
- Total Messages + <%= t("solid_stack_web.cable.total_messages") %> <%= @total_messages %>
- Channels + <%= t("solid_stack_web.cable.channels") %> <%= @channels.size %>
- Messages — last 24 hours + <%= t("solid_stack_web.cable.messages_timeline") %>
<%= cable_messages_timeline_svg(@timeline) %>
- 24h ago - 12h ago - now + <%= t("solid_stack_web.cable.axis_24h_ago") %> + <%= t("solid_stack_web.cable.axis_12h_ago") %> + <%= t("solid_stack_web.cable.axis_now") %>
" autocomplete="off" + aria-label="<%= t("solid_stack_web.cable.aria_filter_channel") %>" data-action="input->search#filter"> <% if @search.present? %> - <%= link_to "Clear", cable_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> + <%= link_to t("solid_stack_web.shared.clear"), cable_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> <% end %>
@@ -50,9 +53,9 @@ - - - + + + @@ -70,11 +73,11 @@ <% else %>
<% if @search.present? %> -

No channels matching “<%= @search %>”

-

<%= link_to "Clear search", cable_path %>

+

<%= t("solid_stack_web.cable.no_channels_matching", search: @search) %>

+

<%= link_to t("solid_stack_web.cable.clear_search"), cable_path %>

<% else %> -

No cable messages

-

Messages will appear here once clients connect and broadcast over Action Cable.

+

<%= t("solid_stack_web.cable.empty_title") %>

+

<%= t("solid_stack_web.cable.empty_hint") %>

<% end %>
<% end %> diff --git a/app/views/solid_stack_web/cable_messages/index.html.erb b/app/views/solid_stack_web/cable_messages/index.html.erb index 174236f..7bdc77b 100644 --- a/app/views/solid_stack_web/cable_messages/index.html.erb +++ b/app/views/solid_stack_web/cable_messages/index.html.erb @@ -1,21 +1,22 @@

<%= @channel_name %>

- <%= button_to "Purge Channel", + <%= button_to t("solid_stack_web.cable_messages.purge_channel"), cable_channel_purge_path(params[:channel_hash]), method: :delete, class: "sqw-btn sqw-btn--danger sqw-btn--sm", - data: { turbo_confirm: "Delete all messages for this channel? This cannot be undone." } %> - <%= link_to "← Channels", cable_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> + data: { turbo_confirm: t("solid_stack_web.cable_messages.confirm_purge_channel") } %> + <%= link_to t("solid_stack_web.cable_messages.back_to_channels"), cable_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
" autocomplete="off" + aria-label="<%= t("solid_stack_web.cable_messages.aria_filter_payload") %>" data-action="input->search#filter"> <% if @search.present? %> - <%= link_to "Clear", cable_channel_messages_path(params[:channel_hash]), class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> + <%= link_to t("solid_stack_web.shared.clear"), cable_channel_messages_path(params[:channel_hash]), class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> <% end %> @@ -23,9 +24,9 @@
ChannelMessagesLast Message<%= t("solid_stack_web.cable.col_channel") %><%= t("solid_stack_web.cable.col_messages") %><%= t("solid_stack_web.cable.col_last_message") %>
- - - + + + @@ -42,15 +43,15 @@ <% end %>
IDPayloadSent<%= t("solid_stack_web.cable_messages.col_id") %><%= t("solid_stack_web.cable_messages.col_payload") %><%= t("solid_stack_web.cable_messages.col_sent") %>
- <%== @pagy.series_nav(aria_label: "Pagination") if @pagy.pages > 1 %> + <%== @pagy.series_nav(aria_label: t("solid_stack_web.shared.pagination")) if @pagy.pages > 1 %> <% else %>
<% if @search.present? %> -

No messages matching “<%= @search %>”

-

<%= link_to "Clear search", cable_channel_messages_path(params[:channel_hash]) %>

+

<%= t("solid_stack_web.cable_messages.no_messages_matching", search: @search) %>

+

<%= link_to t("solid_stack_web.cable_messages.clear_search"), cable_channel_messages_path(params[:channel_hash]) %>

<% else %> -

No messages for this channel

-

Messages may have been purged or the channel has gone quiet. <%= link_to "Back to channels →", cable_path %>

+

<%= t("solid_stack_web.cable_messages.empty_title") %>

+

<%= t("solid_stack_web.cable_messages.empty_hint") %> <%= link_to t("solid_stack_web.cable_messages.back_to_channels_link"), cable_path %>

<% end %>
-<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/solid_stack_web/cache/index.html.erb b/app/views/solid_stack_web/cache/index.html.erb index fdb6605..c4e91e2 100644 --- a/app/views/solid_stack_web/cache/index.html.erb +++ b/app/views/solid_stack_web/cache/index.html.erb @@ -1,14 +1,14 @@
-

Solid Cache

+

<%= t("solid_stack_web.cache.title") %>

<%= link_to cache_entries_path, class: "sqw-stat sqw-stat--cache" do %> - Total Entries + <%= t("solid_stack_web.cache.total_entries") %> <%= @total_entries %> <% end %>
- Total Size + <%= t("solid_stack_web.cache.total_size") %> <%= number_to_human_size(@total_byte_size) %>
@@ -16,13 +16,13 @@ <% if @total_entries > 0 %>
-

Size Distribution

+

<%= t("solid_stack_web.cache.size_distribution") %>

- - - + + + @@ -44,12 +44,12 @@
-

Largest Entries

+

<%= t("solid_stack_web.cache.largest_entries") %>

RangeEntriesDistribution<%= t("solid_stack_web.cache.col_range") %><%= t("solid_stack_web.cache.col_entries") %><%= t("solid_stack_web.cache.col_distribution") %>
- - + + @@ -69,28 +69,28 @@
- Entries written — last 24 hours + <%= t("solid_stack_web.cache.entries_written") %>
<%= cache_entries_timeline_svg(@timeline) %>
- 24h ago - 12h ago - now + <%= t("solid_stack_web.cache.axis_24h_ago") %> + <%= t("solid_stack_web.cache.axis_12h_ago") %> + <%= t("solid_stack_web.cache.axis_now") %>
- Bytes written — last 24 hours + <%= t("solid_stack_web.cache.bytes_written") %>
<%= cache_bytes_timeline_svg(@timeline) %>
- 24h ago - 12h ago - now + <%= t("solid_stack_web.cache.axis_24h_ago") %> + <%= t("solid_stack_web.cache.axis_12h_ago") %> + <%= t("solid_stack_web.cache.axis_now") %>
-
\ No newline at end of file + diff --git a/app/views/solid_stack_web/cache_entries/index.html.erb b/app/views/solid_stack_web/cache_entries/index.html.erb index 27936c5..9e65773 100644 --- a/app/views/solid_stack_web/cache_entries/index.html.erb +++ b/app/views/solid_stack_web/cache_entries/index.html.erb @@ -1,11 +1,11 @@
-

Cache Entries

+

<%= t("solid_stack_web.cache_entries.title") %>

- <%= button_to "Flush All", + <%= button_to t("solid_stack_web.cache_entries.flush_all"), cache_flush_path, method: :delete, class: "sqw-btn sqw-btn--danger sqw-btn--sm", - data: { turbo_confirm: "Delete all cache entries? This cannot be undone." } %> + data: { turbo_confirm: t("solid_stack_web.cache_entries.confirm_flush_all") } %>
@@ -13,10 +13,11 @@ <%= hidden_field_tag :column, @sort["column"] %> <%= hidden_field_tag :direction, @sort["direction"] %> " autocomplete="off" + aria-label="<%= t("solid_stack_web.cache_entries.aria_filter_key") %>" data-action="input->search#filter"> <% if @search.present? %> - <%= link_to "Clear", cache_entries_path(column: @sort["column"], direction: @sort["direction"]), + <%= link_to t("solid_stack_web.shared.clear"), cache_entries_path(column: @sort["column"], direction: @sort["direction"]), class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> <% end %> @@ -25,7 +26,7 @@
KeySize<%= t("solid_stack_web.cache.col_key") %><%= t("solid_stack_web.cache.col_size") %>
- <% [["key", "Key"], ["byte_size", "Size"], ["created_at", "Created"]].each do |col, label| %> + <% [["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| %> <% end %> - + @@ -48,25 +49,25 @@ <% end %>
<% next_dir = (@sort["column"] == col && @sort["direction"] == "desc") ? "asc" : "desc" %> <%= link_to cache_entries_path(q: @search, column: col, direction: next_dir) do %> @@ -36,7 +37,7 @@ <% end %> Actions<%= t("solid_stack_web.shared.actions") %>
<%= number_to_human_size(entry.byte_size) %> <%= local_time(entry.created_at) %> - <%= button_to "Delete", + <%= button_to t("solid_stack_web.cache_entries.delete"), cache_entry_path(entry, q: @search, column: @sort["column"], direction: @sort["direction"]), method: :delete, class: "sqw-btn sqw-btn--danger sqw-btn--sm", - data: { turbo_confirm: "Delete this cache entry?" } %> + data: { turbo_confirm: t("solid_stack_web.cache_entries.confirm_delete") } %>
- <%== @pagy.series_nav(aria_label: "Pagination") if @pagy.pages > 1 %> + <%== @pagy.series_nav(aria_label: t("solid_stack_web.shared.pagination")) if @pagy.pages > 1 %> <% else %>
<% if @search.present? %> -

No entries matching “<%= @search %>”

-

<%= link_to "Clear search", cache_entries_path %>

+

<%= t("solid_stack_web.cache_entries.no_entries_matching", search: @search) %>

+

<%= link_to t("solid_stack_web.cache_entries.clear_search"), cache_entries_path %>

<% else %> -

No cache entries

-

Entries will appear here once your application writes to the cache.

+

<%= t("solid_stack_web.cache_entries.empty_title") %>

+

<%= t("solid_stack_web.cache_entries.empty_hint") %>

<% end %>
-<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/solid_stack_web/cache_entries/show.html.erb b/app/views/solid_stack_web/cache_entries/show.html.erb index 52e7f93..5fa3c4e 100644 --- a/app/views/solid_stack_web/cache_entries/show.html.erb +++ b/app/views/solid_stack_web/cache_entries/show.html.erb @@ -1,23 +1,23 @@

<%= @entry.key %>

- <%= button_to "Delete", + <%= button_to t("solid_stack_web.cache_entries.delete"), cache_entry_path(@entry), method: :delete, class: "sqw-btn sqw-btn--danger sqw-btn--sm", - data: { turbo_confirm: "Delete this cache entry?" } %> - <%= link_to "← Entries", cache_entries_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> + data: { turbo_confirm: t("solid_stack_web.cache_entries.confirm_delete") } %> + <%= link_to t("solid_stack_web.cache_entries.back_to_entries"), cache_entries_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
-

Details

+

<%= t("solid_stack_web.cache_entries.show_section_details") %>

-
Key
+
<%= t("solid_stack_web.cache_entries.show_key") %>
<%= @entry.key %>
-
Size
+
<%= t("solid_stack_web.cache_entries.show_size") %>
<%= number_to_human_size(@entry.byte_size) %>
-
Created
+
<%= t("solid_stack_web.cache_entries.show_created") %>
<%= local_time(@entry.created_at, format: :long) %>
@@ -25,7 +25,7 @@
<% formatted = SolidStackWeb.allow_value_preview ? format_cache_value(@entry.value) : nil %>
-

Value

+

<%= t("solid_stack_web.cache_entries.show_section_value") %>

<% if formatted %> <%= formatted[:label] %> <% end %> @@ -35,11 +35,11 @@ <% truncated = content.length > 4096 %>
<%= truncated ? content[0, 4096] : content %>
<% if truncated %> -

Showing first 4 KB of <%= number_to_human_size(@entry.byte_size) %> total.

+

<%= t("solid_stack_web.cache_entries.show_truncated", size: number_to_human_size(@entry.byte_size)) %>

<% end %> <% else %>
-

Value preview is disabled. Set config.allow_value_preview = true in your initializer to enable.

+

<%= t("solid_stack_web.cache_entries.show_preview_disabled") %>

<% end %> -
\ No newline at end of file +
diff --git a/app/views/solid_stack_web/dashboard/index.html.erb b/app/views/solid_stack_web/dashboard/index.html.erb index f6bbc99..b922ee8 100644 --- a/app/views/solid_stack_web/dashboard/index.html.erb +++ b/app/views/solid_stack_web/dashboard/index.html.erb @@ -1,104 +1,104 @@ <%= turbo_frame_tag "sqw-dashboard", target: "_top", data: { controller: "refresh", refresh_interval_value: SolidStackWeb.dashboard_refresh_interval } do %>
-

Overview

+

<%= t("solid_stack_web.dashboard.title") %>

- Solid Queue - <%= link_to "View Jobs →", jobs_path, class: "sqw-gem-card__link" %> + <%= t("solid_stack_web.dashboard.solid_queue") %> + <%= link_to t("solid_stack_web.dashboard.view_jobs"), jobs_path, class: "sqw-gem-card__link" %>
- Throughput — last 12 hours + <%= t("solid_stack_web.dashboard.throughput_label") %>
<%= throughput_sparkline_svg(@throughput) %>
- 12h ago - 6h ago - now + <%= t("solid_stack_web.dashboard.axis.twelve_h_ago") %> + <%= t("solid_stack_web.dashboard.axis.six_h_ago") %> + <%= t("solid_stack_web.dashboard.axis.now") %>
- Failures — last 12 hours + <%= t("solid_stack_web.dashboard.failures_label") %>
<%= failed_job_sparkline_svg(@failures) %>
- 12h ago - 6h ago - now + <%= t("solid_stack_web.dashboard.axis.twelve_h_ago") %> + <%= t("solid_stack_web.dashboard.axis.six_h_ago") %> + <%= t("solid_stack_web.dashboard.axis.now") %>
- Solid Cache - <%= link_to "View Cache →", cache_path, class: "sqw-gem-card__link" %> + <%= t("solid_stack_web.dashboard.solid_cache") %> + <%= link_to t("solid_stack_web.dashboard.view_cache"), cache_path, class: "sqw-gem-card__link" %>
<%= link_to cache_entries_path, class: "sqw-inline-stat sqw-inline-stat--cache" do %> - Entries + <%= t("solid_stack_web.dashboard.entries") %> <%= @cache_stats[:entries] %> <% end %>
- Size + <%= t("solid_stack_web.dashboard.size") %> <%= number_to_human_size(@cache_stats[:byte_size]) %>
<% if @cache_stats[:oldest_entry] %>
- Oldest + <%= t("solid_stack_web.dashboard.oldest") %> <%= local_time(@cache_stats[:oldest_entry], format: :relative) %>
<% end %> @@ -107,32 +107,32 @@
- Solid Cable - <%= link_to "View Cable →", cable_path, class: "sqw-gem-card__link" %> + <%= t("solid_stack_web.dashboard.solid_cable") %> + <%= link_to t("solid_stack_web.dashboard.view_cable"), cable_path, class: "sqw-gem-card__link" %>
<%= link_to cable_path, class: "sqw-inline-stat sqw-inline-stat--cable" do %> - Messages + <%= t("solid_stack_web.dashboard.messages") %> <%= @cable_stats[:messages] %> <% end %>
- Channels + <%= t("solid_stack_web.dashboard.channels") %> <%= @cable_stats[:channels] %>
- Msg/hr + <%= t("solid_stack_web.dashboard.msg_per_hr") %> <%= @cable_stats[:messages_per_hour] %>
<% if @cable_stats[:oldest_message] %>
- Oldest + <%= t("solid_stack_web.dashboard.oldest") %> <%= local_time(@cable_stats[:oldest_message], format: :relative) %>
<% end %>
<% if @cable_stats[:top_channels].any? %>
- Top channels + <%= t("solid_stack_web.dashboard.top_channels") %> <% @cable_stats[:top_channels].each do |channel, count| %>
<%= channel %> diff --git a/app/views/solid_stack_web/errors/internal_server_error.html.erb b/app/views/solid_stack_web/errors/internal_server_error.html.erb index 2764909..6bdd9f9 100644 --- a/app/views/solid_stack_web/errors/internal_server_error.html.erb +++ b/app/views/solid_stack_web/errors/internal_server_error.html.erb @@ -1,8 +1,8 @@
-

Something Went Wrong

+

<%= t("solid_stack_web.errors.server_error_title") %>

-

500 — An unexpected error occurred.

-

<%= link_to "Back to Dashboard", root_path, class: "sqw-btn sqw-btn--secondary" %>

-
\ No newline at end of file +

<%= t("solid_stack_web.errors.server_error_message") %>

+

<%= link_to t("solid_stack_web.shared.back_to_dashboard"), root_path, class: "sqw-btn sqw-btn--secondary" %>

+
diff --git a/app/views/solid_stack_web/errors/not_found.html.erb b/app/views/solid_stack_web/errors/not_found.html.erb index 02b7333..60a3cc9 100644 --- a/app/views/solid_stack_web/errors/not_found.html.erb +++ b/app/views/solid_stack_web/errors/not_found.html.erb @@ -1,8 +1,8 @@
-

Not Found

+

<%= t("solid_stack_web.errors.not_found_title") %>

-

404 — The record you’re looking for doesn’t exist or has been removed.

-

<%= link_to "Back to Dashboard", root_path, class: "sqw-btn sqw-btn--secondary" %>

-
\ No newline at end of file +

<%= t("solid_stack_web.errors.not_found_message") %>

+

<%= link_to t("solid_stack_web.shared.back_to_dashboard"), root_path, class: "sqw-btn sqw-btn--secondary" %>

+
diff --git a/app/views/solid_stack_web/failed_jobs/destroy.turbo_stream.erb b/app/views/solid_stack_web/failed_jobs/destroy.turbo_stream.erb index 3461b90..40f44d4 100644 --- a/app/views/solid_stack_web/failed_jobs/destroy.turbo_stream.erb +++ b/app/views/solid_stack_web/failed_jobs/destroy.turbo_stream.erb @@ -6,7 +6,7 @@ <% else %> <%= turbo_stream.replace "sqw-jobs-table" do %>
-

No failed jobs.

+

<%= t("solid_stack_web.failed_jobs.empty_title") %>

<% end %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/solid_stack_web/failed_jobs/errors/index.html.erb b/app/views/solid_stack_web/failed_jobs/errors/index.html.erb index cf67cab..a9bca5a 100644 --- a/app/views/solid_stack_web/failed_jobs/errors/index.html.erb +++ b/app/views/solid_stack_web/failed_jobs/errors/index.html.erb @@ -1,7 +1,7 @@
-

Error Summary

+

<%= t("solid_stack_web.failed_jobs.errors_title") %>

- <%= link_to "← Failed Jobs", failed_jobs_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> + <%= link_to t("solid_stack_web.failed_jobs.back_to_failed_jobs"), failed_jobs_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
@@ -9,10 +9,10 @@ - - - - + + + + @@ -33,7 +33,7 @@ @@ -42,7 +42,7 @@
Error ClassMessageCountActions<%= t("solid_stack_web.failed_jobs.errors_col_class") %><%= t("solid_stack_web.failed_jobs.errors_col_message") %><%= t("solid_stack_web.failed_jobs.errors_col_count") %><%= t("solid_stack_web.shared.actions") %>
<%= group.count %> - <%= link_to "View Jobs", failed_jobs_path(error_class: group.exception_class), + <%= link_to t("solid_stack_web.failed_jobs.view_jobs"), failed_jobs_path(error_class: group.exception_class), class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
<% else %>
-

No failed jobs

-

All clear — your jobs are running without errors.

+

<%= t("solid_stack_web.failed_jobs.empty_title") %>

+

<%= t("solid_stack_web.failed_jobs.empty_hint") %>

-<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/solid_stack_web/failed_jobs/index.html.erb b/app/views/solid_stack_web/failed_jobs/index.html.erb index e2a56f8..9cf4518 100644 --- a/app/views/solid_stack_web/failed_jobs/index.html.erb +++ b/app/views/solid_stack_web/failed_jobs/index.html.erb @@ -1,16 +1,16 @@
-

Failed Jobs

+

<%= t("solid_stack_web.failed_jobs.title") %>

<% if @error_class %>
- Filtered by <%= @error_class %> - — <%= link_to "Clear filter", failed_jobs_path %> + <%= t("solid_stack_web.failed_jobs.filtered_by") %> <%= @error_class %> + — <%= link_to t("solid_stack_web.failed_jobs.clear_filter"), failed_jobs_path %>
<% end %>
- <%= link_to "Error Summary", failed_job_errors_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> - <%= link_to "Export CSV", failed_jobs_path(format: :csv), + <%= link_to t("solid_stack_web.failed_jobs.error_summary"), failed_job_errors_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> + <%= link_to t("solid_stack_web.failed_jobs.export_csv"), failed_jobs_path(format: :csv), class: "sqw-btn sqw-btn--muted sqw-btn--sm", data: { turbo: false } %>
@@ -22,31 +22,31 @@ <% end %> <%= form_with url: failed_job_selection_path, method: :delete, id: "discard-selection-form", - data: { turbo_confirm: "Discard selected failed jobs? This cannot be undone." } do |f| %> + data: { turbo_confirm: t("solid_stack_web.failed_jobs.confirm_discard_selected") } do |f| %> <% end %> - <% sort_url = ->(p) { failed_jobs_path(error_class: @error_class, **p) } %> - <%= sort_header_th("Job Class", "class_name", sort_url, current_sort: @sort, current_dir: @direction) %> - <%= sort_header_th("Queue", "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %> - - <%= sort_header_th("Failed At", "created_at", sort_url, current_sort: @sort, current_dir: @direction) %> - + <%= sort_header_th(t("solid_stack_web.failed_jobs.col_job_class"), "class_name", sort_url, current_sort: @sort, current_dir: @direction) %> + <%= sort_header_th(t("solid_stack_web.failed_jobs.col_queue"), "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %> + + <%= sort_header_th(t("solid_stack_web.failed_jobs.col_failed_at"), "created_at", sort_url, current_sort: @sort, current_dir: @direction) %> + @@ -62,22 +62,22 @@ <% end %>
" data-selection-target="selectAll" data-action="change->selection#selectAll">ErrorActions<%= t("solid_stack_web.failed_jobs.col_error") %><%= t("solid_stack_web.shared.actions") %>
<%= execution.exception_class %> <%= local_time(execution.created_at) %> - <%= button_to "Retry", retry_failed_job_path(execution), + <%= button_to t("solid_stack_web.failed_jobs.retry"), retry_failed_job_path(execution), method: :post, class: "sqw-btn sqw-btn--sm" %> - <%= button_to "Discard", failed_job_path(execution), + <%= button_to t("solid_stack_web.failed_jobs.discard"), failed_job_path(execution), method: :delete, class: "sqw-btn sqw-btn--danger sqw-btn--sm", - data: { turbo_confirm: "Discard this job?" } %> + data: { turbo_confirm: t("solid_stack_web.failed_jobs.confirm_discard") } %>
- <%== @pagy.series_nav(aria_label: "Pagination") if @pagy.pages > 1 %> + <%== @pagy.series_nav(aria_label: t("solid_stack_web.shared.pagination")) if @pagy.pages > 1 %>
<% else %>
-

No failed jobs

-

All clear — your jobs are running without errors.

+

<%= t("solid_stack_web.failed_jobs.empty_title") %>

+

<%= t("solid_stack_web.failed_jobs.empty_hint") %>

<% end %> -
\ No newline at end of file +
diff --git a/app/views/solid_stack_web/failed_jobs/show.html.erb b/app/views/solid_stack_web/failed_jobs/show.html.erb index 2412b5d..99c231f 100644 --- a/app/views/solid_stack_web/failed_jobs/show.html.erb +++ b/app/views/solid_stack_web/failed_jobs/show.html.erb @@ -1,58 +1,58 @@
- <%= link_to "Failed Jobs", failed_jobs_path %> › Detail + <%= link_to t("solid_stack_web.failed_jobs.title"), failed_jobs_path %> › <%= t("solid_stack_web.failed_jobs.detail") %>

<%= @execution.job.class_name %>

- <%= button_to "Retry", retry_failed_job_path(@execution), + <%= button_to t("solid_stack_web.failed_jobs.retry"), retry_failed_job_path(@execution), method: :post, class: "sqw-btn sqw-btn--sm" %> - <%= button_to "Discard", failed_job_path(@execution), + <%= button_to t("solid_stack_web.failed_jobs.discard"), failed_job_path(@execution), method: :delete, class: "sqw-btn sqw-btn--danger", - data: { turbo_confirm: "Discard this job?" } %> + data: { turbo_confirm: t("solid_stack_web.failed_jobs.confirm_discard") } %>
-

Details

+

<%= t("solid_stack_web.failed_jobs.section_details") %>

-
Queue
+
<%= t("solid_stack_web.failed_jobs.field_queue") %>
<%= @execution.job.queue_name %>
-
Priority
+
<%= t("solid_stack_web.failed_jobs.field_priority") %>
<%= @execution.job.priority %>
-
Active Job ID
+
<%= t("solid_stack_web.failed_jobs.field_active_job_id") %>
<%= @execution.job.active_job_id.presence || "—" %>
-
Failed At
+
<%= t("solid_stack_web.failed_jobs.field_failed_at") %>
<%= local_time(@execution.created_at, format: :long) %>
-
Error
+
<%= t("solid_stack_web.failed_jobs.field_error") %>
<%= @execution.exception_class %>
-
Message
+
<%= t("solid_stack_web.failed_jobs.field_message") %>
<%= @execution.message %>
-

Backtrace

+

<%= t("solid_stack_web.failed_jobs.section_backtrace") %>

<%= Array(@execution.backtrace).first(10).join("\n").presence || "—" %>
-

Arguments

+

<%= t("solid_stack_web.failed_jobs.section_arguments") %>

<%= form_with url: failed_job_arguments_path(@execution), method: :patch do |f| %> <%= f.text_area :arguments, value: @arguments, rows: 12, - class: "sqw-code-input", aria: { label: "Job arguments JSON" }, + class: "sqw-code-input", aria: { label: t("solid_stack_web.failed_jobs.arguments_aria_label") }, spellcheck: false %>
- <%= f.submit "Update & Retry".html_safe, class: "sqw-btn sqw-btn--sm" %> + <%= f.submit t("solid_stack_web.failed_jobs.update_retry"), class: "sqw-btn sqw-btn--sm" %>
<% end %> -
\ No newline at end of file +
diff --git a/app/views/solid_stack_web/history/index.html.erb b/app/views/solid_stack_web/history/index.html.erb index 88a0e93..2709739 100644 --- a/app/views/solid_stack_web/history/index.html.erb +++ b/app/views/solid_stack_web/history/index.html.erb @@ -1,10 +1,10 @@ <%= turbo_frame_tag "sqw-history-table", data: { controller: "refresh", refresh_interval_value: SolidStackWeb.default_refresh_interval } do %>
-

Job History

+

<%= t("solid_stack_web.history.title") %>

<% if @jobs&.any? %> - <%= link_to "Export CSV", history_path(format: :csv, queue: @queue, q: @search, period: @period), + <%= link_to t("solid_stack_web.history.export_csv"), history_path(format: :csv, queue: @queue, q: @search, period: @period), class: "sqw-btn sqw-btn--muted sqw-btn--sm", data: { turbo: false } %> <% end %>
@@ -20,13 +20,14 @@ " autocomplete="off" + aria-label="<%= t("solid_stack_web.history.aria_filter") %>" data-action="input->search#filter"> <% if @search.present? %> - <%= link_to "Clear", history_path(queue: @queue, period: @period), class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> + <%= link_to t("solid_stack_web.shared.clear"), history_path(queue: @queue, period: @period), class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> <% end %> -
- <%= link_to "All", history_path(queue: @queue, q: @search), +
"> + <%= link_to t("solid_stack_web.shared.period_filter.all"), history_path(queue: @queue, q: @search), class: "sqw-period-btn #{"sqw-period-btn--active" if @period.nil?}" %> <%= link_to "1h", history_path(queue: @queue, q: @search, period: "1h"), class: "sqw-period-btn #{"sqw-period-btn--active" if @period == "1h"}" %> @@ -39,8 +40,8 @@ <% if @queue.present? %>

- Filtering by queue: <%= @queue %> — - <%= link_to "Clear filter", history_path(q: @search, period: @period) %> + <%= t("solid_stack_web.history.filtering_by_queue") %> <%= @queue %> — + <%= link_to t("solid_stack_web.history.clear_filter"), history_path(q: @search, period: @period) %>

<% end %> @@ -50,10 +51,10 @@ <% sort_url = ->(p) { history_path(queue: @queue, q: @search, period: @period, **p) } %> - <%= sort_header_th("Job Class", "class_name", sort_url, current_sort: @sort, current_dir: @direction) %> - <%= sort_header_th("Queue", "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %> - Duration - <%= sort_header_th("Finished At", "finished_at", sort_url, current_sort: @sort, current_dir: @direction) %> + <%= sort_header_th(t("solid_stack_web.history.col_job_class"), "class_name", sort_url, current_sort: @sort, current_dir: @direction) %> + <%= sort_header_th(t("solid_stack_web.history.col_queue"), "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %> + <%= t("solid_stack_web.history.col_duration") %> + <%= sort_header_th(t("solid_stack_web.history.col_finished_at"), "finished_at", sort_url, current_sort: @sort, current_dir: @direction) %> @@ -71,17 +72,17 @@ <% end %> - <%== @pagy.series_nav(aria_label: "Pagination") if @pagy.pages > 1 %> + <%== @pagy.series_nav(aria_label: t("solid_stack_web.shared.pagination")) if @pagy.pages > 1 %>
<% else %>
<% if @search.present? || @queue.present? || @period.present? %> -

No finished jobs match your filters

-

<%= link_to "Clear filters", history_path %>

+

<%= t("solid_stack_web.history.no_match_title") %>

+

<%= link_to t("solid_stack_web.history.no_match_hint"), history_path %>

<% else %> -

No finished jobs yet

-

Completed jobs will appear here once workers process them.

+

<%= t("solid_stack_web.history.empty_title") %>

+

<%= t("solid_stack_web.history.empty_hint") %>

<% end %>
<% end %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/solid_stack_web/jobs/_empty.html.erb b/app/views/solid_stack_web/jobs/_empty.html.erb index 119a236..52b4192 100644 --- a/app/views/solid_stack_web/jobs/_empty.html.erb +++ b/app/views/solid_stack_web/jobs/_empty.html.erb @@ -1,20 +1,20 @@
<% if @search.present? %> -

No <%= @status %> jobs matching “<%= @search %>”

-

<%= link_to "Clear search", jobs_path(status: @status) %>

+

<%= t("solid_stack_web.jobs.no_jobs_matching", status: @status, search: @search) %>

+

<%= link_to t("solid_stack_web.jobs.clear_search"), jobs_path(status: @status) %>

<% else %> -

No <%= @status %> jobs

+

<%= t("solid_stack_web.jobs.no_jobs", status: @status) %>

<% case @status %> <% when "ready" %> - Jobs will appear here once they are enqueued and ready to be picked up. + <%= t("solid_stack_web.jobs.hint_ready") %> <% when "scheduled" %> - No jobs are scheduled to run in the future. + <%= t("solid_stack_web.jobs.hint_scheduled") %> <% when "claimed" %> - No jobs are currently being processed by a worker. + <%= t("solid_stack_web.jobs.hint_claimed") %> <% when "blocked" %> - No jobs are blocked by concurrency controls. + <%= t("solid_stack_web.jobs.hint_blocked") %> <% end %>

<% end %> -
\ No newline at end of file +
diff --git a/app/views/solid_stack_web/jobs/index.html.erb b/app/views/solid_stack_web/jobs/index.html.erb index 90ffe46..712656b 100644 --- a/app/views/solid_stack_web/jobs/index.html.erb +++ b/app/views/solid_stack_web/jobs/index.html.erb @@ -1,30 +1,30 @@
-

Jobs

+

<%= t("solid_stack_web.jobs.title") %>

- <%= link_to "Export CSV", jobs_path(format: :csv, status: @status, q: @search, queue: @queue, period: @period, priority: @priority, sort: @sort, direction: @direction), + <%= link_to t("solid_stack_web.jobs.export_csv"), jobs_path(format: :csv, status: @status, q: @search, queue: @queue, period: @period, priority: @priority, sort: @sort, direction: @direction), class: "sqw-btn sqw-btn--muted sqw-btn--sm", data: { turbo: false } %> <% if @status == "scheduled" && @executions&.any? %> - <%= button_to "Run All Now (#{@pagy.count})", + <%= button_to t("solid_stack_web.jobs.run_all_now", count: @pagy.count), run_all_now_scheduled_jobs_path(period: @period), method: :post, class: "sqw-btn sqw-btn--sm", - data: { turbo_confirm: "Run all #{@pagy.count} scheduled jobs immediately?", + data: { turbo_confirm: t("solid_stack_web.jobs.confirm_run_all_now", count: @pagy.count), turbo_frame: "_top" } %> <% end %> <% if SolidStackWeb::Job::DISCARDABLE.include?(@status) && @executions&.any? %> - <%= button_to "Discard All (#{@pagy.count})", + <%= button_to t("solid_stack_web.jobs.discard_all", count: @pagy.count), discard_all_jobs_path(status: @status, q: @search, queue: @queue, period: @period, priority: @priority), method: :post, class: "sqw-btn sqw-btn--danger sqw-btn--sm", - data: { turbo_confirm: "Discard all #{@pagy.count} jobs? This cannot be undone.", + data: { turbo_confirm: t("solid_stack_web.jobs.confirm_discard_all", count: @pagy.count), turbo_frame: "_top" } %> <% end %>
- <% SolidStackWeb::Job::TAB_LABELS.each do |status, label| %> - <%= link_to label, jobs_path(status: status, q: @search, queue: @queue, period: @period, priority: @priority, sort: @sort, direction: @direction), + <% SolidStackWeb::Job::STATUSES.each do |status| %> + <%= link_to t("solid_stack_web.jobs.tab_#{status}"), jobs_path(status: status, q: @search, queue: @queue, period: @period, priority: @priority, sort: @sort, direction: @direction), class: "sqw-tab #{"sqw-tab--active" if @status == status}" %> <% end %>
@@ -40,31 +40,32 @@ <%= hidden_field_tag :sort, @sort %> <%= hidden_field_tag :direction, @direction %> " autocomplete="off" + aria-label="<%= t("solid_stack_web.jobs.aria_filter_job_class") %>" data-action="input->search#filter"> <% if @queue_options.size > 1 %> - <% end %> <% if @priority_options.size > 1 %> - <% end %> <% if @search.present? || @queue.present? || @priority.present? %> - <%= link_to "Clear", jobs_path(status: @status, period: @period, sort: @sort, direction: @direction), class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> + <%= link_to t("solid_stack_web.shared.clear"), jobs_path(status: @status, period: @period, sort: @sort, direction: @direction), class: "sqw-btn sqw-btn--muted sqw-btn--sm" %> <% end %> -
- <%= link_to "All", jobs_path(status: @status, q: @search, queue: @queue, priority: @priority, sort: @sort, direction: @direction), +
"> + <%= link_to t("solid_stack_web.shared.period_filter.all"), jobs_path(status: @status, q: @search, queue: @queue, priority: @priority, sort: @sort, direction: @direction), class: "sqw-period-btn #{"sqw-period-btn--active" if @period.nil?}" %> <%= link_to "1h", jobs_path(status: @status, q: @search, queue: @queue, priority: @priority, period: "1h", sort: @sort, direction: @direction), class: "sqw-period-btn #{"sqw-period-btn--active" if @period == "1h"}" %> @@ -80,7 +81,7 @@
"> <% if SolidStackWeb::Job::DISCARDABLE.include?(@status) %> <%= form_with url: job_selection_path, method: :delete, id: "job-selection-form", - data: { turbo_confirm: "Discard selected jobs? This cannot be undone." } do |f| %> + data: { turbo_confirm: t("solid_stack_web.jobs.confirm_discard_selected") } do |f| %> <%= f.hidden_field :status, value: @status %> <%= f.hidden_field :q, value: @search %> <%= f.hidden_field :queue, value: @queue %> @@ -91,10 +92,10 @@ <% end %> <% end %> @@ -102,18 +103,18 @@ <% if SolidStackWeb::Job::DISCARDABLE.include?(@status) %> - " data-selection-target="selectAll" data-action="change->selection#selectAll"> <% end %> <% sort_url = ->(p) { jobs_path(status: @status, q: @search, queue: @queue, period: @period, priority: @priority, **p) } %> - <%= sort_header_th("Job Class", "class_name", sort_url, current_sort: @sort, current_dir: @direction) %> - <%= sort_header_th("Queue", "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %> - <%= sort_header_th("Priority", "priority", sort_url, current_sort: @sort, current_dir: @direction) %> - <%= sort_header_th("Enqueued At", "created_at", sort_url, current_sort: @sort, current_dir: @direction) %> - <% if @status == "scheduled" %>Scheduled At<% end %> - <% if @status == "claimed" %>Wait Time<% end %> - Actions + <%= sort_header_th(t("solid_stack_web.jobs.col_job_class"), "class_name", sort_url, current_sort: @sort, current_dir: @direction) %> + <%= sort_header_th(t("solid_stack_web.jobs.col_queue"), "queue_name", sort_url, current_sort: @sort, current_dir: @direction) %> + <%= sort_header_th(t("solid_stack_web.jobs.col_priority"), "priority", sort_url, current_sort: @sort, current_dir: @direction) %> + <%= sort_header_th(t("solid_stack_web.jobs.col_enqueued_at"), "created_at", sort_url, current_sort: @sort, current_dir: @direction) %> + <% if @status == "scheduled" %><%= t("solid_stack_web.jobs.col_scheduled_at") %><% end %> + <% if @status == "claimed" %><%= t("solid_stack_web.jobs.col_wait_time") %><% end %> + <%= t("solid_stack_web.shared.actions") %> @@ -138,11 +139,11 @@ <% end %> <% if @status == "scheduled" %> - <%= button_to "Run Now", scheduled_job_path(execution), + <%= button_to t("solid_stack_web.jobs.run_now"), scheduled_job_path(execution), method: :patch, params: { offset: "now", period: @period }, class: "sqw-btn sqw-btn--sm", - data: { turbo_confirm: "Run this job immediately?" } %> + data: { turbo_confirm: t("solid_stack_web.jobs.confirm_run_now") } %> <% %w[1h 24h 7d].each do |offset| %> <%= button_to "+#{offset}", scheduled_job_path(execution), method: :patch, @@ -151,19 +152,19 @@ <% end %> <% end %> <% if %w[ready scheduled blocked].include?(@status) %> - <%= button_to "Discard", job_path(execution, status: @status, q: @search, queue: @queue, period: @period, priority: @priority, sort: @sort, direction: @direction), + <%= button_to t("solid_stack_web.jobs.discard"), job_path(execution, status: @status, q: @search, queue: @queue, period: @period, priority: @priority, sort: @sort, direction: @direction), method: :delete, class: "sqw-btn sqw-btn--danger sqw-btn--sm", - data: { turbo_confirm: "Discard this job?" } %> + data: { turbo_confirm: t("solid_stack_web.jobs.confirm_discard") } %> <% end %> <% end %> - <%== @pagy.series_nav(aria_label: "Pagination") if @pagy.pages > 1 %> + <%== @pagy.series_nav(aria_label: t("solid_stack_web.shared.pagination")) if @pagy.pages > 1 %>
<% else %> <%= render "empty" %> <% end %>
-<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/solid_stack_web/jobs/show.html.erb b/app/views/solid_stack_web/jobs/show.html.erb index 23a28e8..1299dd5 100644 --- a/app/views/solid_stack_web/jobs/show.html.erb +++ b/app/views/solid_stack_web/jobs/show.html.erb @@ -1,57 +1,57 @@
- <%= link_to "Jobs", jobs_path(status: params[:status]) %> › Detail + <%= link_to t("solid_stack_web.jobs.title"), jobs_path(status: params[:status]) %> › <%= t("solid_stack_web.jobs.detail") %>

<%= @execution.job.class_name %>

<% if SolidStackWeb::Job::DISCARDABLE.include?(@status) %>
- <%= button_to "Discard Job", job_path(@execution.id, status: @status), + <%= button_to t("solid_stack_web.jobs.discard_job"), job_path(@execution.id, status: @status), method: :delete, class: "sqw-btn sqw-btn--danger", - data: { turbo_confirm: "Discard this job?" } %> + data: { turbo_confirm: t("solid_stack_web.jobs.confirm_discard") } %>
<% end %>
-

Details

+

<%= t("solid_stack_web.jobs.section_details") %>

-
Status
-
<%= SolidStackWeb::Job::TAB_LABELS[@status] %>
+
<%= t("solid_stack_web.jobs.field_status") %>
+
<%= t("solid_stack_web.jobs.tab_#{@status}") %>
-
Queue
+
<%= t("solid_stack_web.jobs.field_queue") %>
<%= @execution.job.queue_name %>
-
Priority
+
<%= t("solid_stack_web.jobs.field_priority") %>
<%= @execution.job.priority %>
-
Active Job ID
+
<%= t("solid_stack_web.jobs.field_active_job_id") %>
<%= @execution.job.active_job_id.presence || "—" %>
-
Concurrency Key
+
<%= t("solid_stack_web.jobs.field_concurrency_key") %>
<%= @execution.job.concurrency_key.presence || "—" %>
<% if @status == "blocked" %> -
Blocked Until
+
<%= t("solid_stack_web.jobs.field_blocked_until") %>
<%= local_time(@execution.expires_at, format: :long) %>
<% end %> -
Enqueued At
+
<%= t("solid_stack_web.jobs.field_enqueued_at") %>
<%= local_time(@execution.job.created_at, format: :long) %>
-
Scheduled At
+
<%= t("solid_stack_web.jobs.field_scheduled_at") %>
<%= local_time(@execution.job.scheduled_at, format: :long) %>
-
Finished At
+
<%= t("solid_stack_web.jobs.field_finished_at") %>
<%= local_time(@execution.job.finished_at, format: :long) %>
-

Arguments

+

<%= t("solid_stack_web.jobs.section_arguments") %>

<%= @arguments ? JSON.pretty_generate(@arguments) : (@execution.job.arguments || "—") %>
-
\ No newline at end of file +
diff --git a/app/views/solid_stack_web/processes/index.html.erb b/app/views/solid_stack_web/processes/index.html.erb index 54a4505..4756c01 100644 --- a/app/views/solid_stack_web/processes/index.html.erb +++ b/app/views/solid_stack_web/processes/index.html.erb @@ -1,18 +1,18 @@ <%= turbo_frame_tag "sqw-processes", target: "_top", data: { controller: "refresh", refresh_interval_value: SolidStackWeb.default_refresh_interval } do %>
-

Processes

+

<%= t("solid_stack_web.processes.title") %>

<% if @processes.any? %> - - - - - + + + + + @@ -29,8 +29,8 @@
KindNamePIDHostLast Heartbeat<%= t("solid_stack_web.processes.col_kind") %><%= t("solid_stack_web.processes.col_name") %><%= t("solid_stack_web.processes.col_pid") %><%= t("solid_stack_web.processes.col_host") %><%= t("solid_stack_web.processes.col_last_heartbeat") %>
<% else %>
-

No active processes

-

Start a Solid Queue worker to begin processing jobs.

+

<%= t("solid_stack_web.processes.empty_title") %>

+

<%= t("solid_stack_web.processes.empty_hint") %>

<% end %> <% end %> diff --git a/app/views/solid_stack_web/queues/index.html.erb b/app/views/solid_stack_web/queues/index.html.erb index 8666e37..88c294f 100644 --- a/app/views/solid_stack_web/queues/index.html.erb +++ b/app/views/solid_stack_web/queues/index.html.erb @@ -1,16 +1,16 @@
-

Queues

+

<%= t("solid_stack_web.queues.title") %>

<% if @queues.any? %> - - - - - + + + + + @@ -24,17 +24,17 @@ @@ -44,7 +44,7 @@
NameSizeDepth (12h)StatusActions<%= t("solid_stack_web.queues.col_name") %><%= t("solid_stack_web.queues.col_size") %><%= t("solid_stack_web.queues.col_depth") %><%= t("solid_stack_web.queues.col_status") %><%= t("solid_stack_web.shared.actions") %>
<% if queue[:paused] %> - Paused + <%= t("solid_stack_web.queues.status_paused") %> <% else %> - Running + <%= t("solid_stack_web.queues.status_running") %> <% end %> <% if queue[:paused] %> - <%= button_to "Resume", queue_pause_path(queue[:name]), + <%= button_to t("solid_stack_web.queues.resume"), queue_pause_path(queue[:name]), method: :delete, class: "sqw-btn sqw-btn--sm" %> <% else %> - <%= button_to "Pause", queue_pause_path(queue[:name]), + <%= button_to t("solid_stack_web.queues.pause"), queue_pause_path(queue[:name]), method: :post, class: "sqw-btn sqw-btn--sm" %> <% end %>
<% else %>
-

No queues with ready jobs

-

Workers are idle or all jobs are in another state. <%= link_to "Check job statuses →", jobs_path %>

+

<%= t("solid_stack_web.queues.empty_title") %>

+

<%= t("solid_stack_web.queues.empty_hint") %> <%= link_to t("solid_stack_web.queues.check_jobs"), jobs_path %>

-<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/solid_stack_web/queues/show.html.erb b/app/views/solid_stack_web/queues/show.html.erb index 07c3bd9..be37359 100644 --- a/app/views/solid_stack_web/queues/show.html.erb +++ b/app/views/solid_stack_web/queues/show.html.erb @@ -1,31 +1,31 @@
- <%= link_to "Queues", queues_path %> › <%= @queue_name %> + <%= link_to t("solid_stack_web.queues.title"), queues_path %> › <%= @queue_name %>

<%= @queue_name %>

<% if @paused %> - Paused + <%= t("solid_stack_web.queues.status_paused") %> <% else %> - Running + <%= t("solid_stack_web.queues.status_running") %> <% end %>
<% if @paused %> - <%= button_to "Resume", queue_pause_path(@queue_name), + <%= button_to t("solid_stack_web.queues.resume"), queue_pause_path(@queue_name), method: :delete, class: "sqw-btn sqw-btn--sm" %> <% else %> - <%= button_to "Pause", queue_pause_path(@queue_name), + <%= button_to t("solid_stack_web.queues.pause"), queue_pause_path(@queue_name), method: :post, class: "sqw-btn sqw-btn--sm" %> <% end %> <% if @executions.any? %> - <%= button_to "Discard All Ready (#{@pagy.count})", + <%= button_to t("solid_stack_web.queues.discard_all_ready", count: @pagy.count), discard_all_jobs_path(status: "ready", queue: @queue_name), method: :post, class: "sqw-btn sqw-btn--danger sqw-btn--sm", - data: { turbo_confirm: "Discard all #{@pagy.count} ready jobs in #{@queue_name}? This cannot be undone.", + data: { turbo_confirm: t("solid_stack_web.queues.confirm_discard_all_ready", count: @pagy.count, queue: @queue_name), turbo_frame: "_top" } %> <% end %>
@@ -35,10 +35,10 @@ - - - - + + + + @@ -51,17 +51,17 @@ <% end %>
Job ClassPriorityEnqueued AtActions<%= t("solid_stack_web.queues.show_col_job_class") %><%= t("solid_stack_web.queues.show_col_priority") %><%= t("solid_stack_web.queues.show_col_enqueued_at") %><%= t("solid_stack_web.shared.actions") %>
<%= execution.job.priority %> <%= local_time(execution.created_at) %> - <%= button_to "Discard", job_path(execution, status: "ready", queue: @queue_name), + <%= button_to t("solid_stack_web.queues.discard"), job_path(execution, status: "ready", queue: @queue_name), method: :delete, class: "sqw-btn sqw-btn--danger sqw-btn--sm", - data: { turbo_confirm: "Discard this job?" } %> + data: { turbo_confirm: t("solid_stack_web.queues.confirm_discard") } %>
- <%== @pagy.series_nav(aria_label: "Pagination") if @pagy.pages > 1 %> + <%== @pagy.series_nav(aria_label: t("solid_stack_web.shared.pagination")) if @pagy.pages > 1 %> <% else %>
-

No ready jobs in <%= @queue_name %>.

+

<%= t("solid_stack_web.queues.no_ready_jobs", queue: @queue_name) %>

-<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/solid_stack_web/recurring_tasks/index.html.erb b/app/views/solid_stack_web/recurring_tasks/index.html.erb index 7a9c8c8..99a93d9 100644 --- a/app/views/solid_stack_web/recurring_tasks/index.html.erb +++ b/app/views/solid_stack_web/recurring_tasks/index.html.erb @@ -1,19 +1,19 @@
-

Recurring Tasks

+

<%= t("solid_stack_web.recurring_tasks.title") %>

<% if @recurring_tasks.any? %> - - - - - - - - + + + + + + + + @@ -44,16 +44,16 @@ <% end %> @@ -61,7 +61,7 @@
KeyScheduleJob / CommandQueueNext RunLast RunTypeActions<%= t("solid_stack_web.recurring_tasks.col_key") %><%= t("solid_stack_web.recurring_tasks.col_schedule") %><%= t("solid_stack_web.recurring_tasks.col_job_command") %><%= t("solid_stack_web.recurring_tasks.col_queue") %><%= t("solid_stack_web.recurring_tasks.col_next_run") %><%= t("solid_stack_web.recurring_tasks.col_last_run") %><%= t("solid_stack_web.recurring_tasks.col_type") %><%= t("solid_stack_web.shared.actions") %>
<% if task.static? %> - Static + <%= t("solid_stack_web.recurring_tasks.type_static") %> <% else %> - Dynamic + <%= t("solid_stack_web.recurring_tasks.type_dynamic") %> <% end %> - <%= button_to "Run Now", recurring_task_run_path(task.key), + <%= button_to t("solid_stack_web.recurring_tasks.run_now"), recurring_task_run_path(task.key), method: :post, class: "sqw-btn sqw-btn--sm", - data: { turbo_confirm: "Run \"#{task.key}\" immediately?" } %> + data: { turbo_confirm: t("solid_stack_web.recurring_tasks.confirm_run_now", key: task.key) } %>
<% else %>
-

No recurring tasks configured

-

Define recurring tasks in your Solid Queue configuration to see them here.

+

<%= t("solid_stack_web.recurring_tasks.empty_title") %>

+

<%= t("solid_stack_web.recurring_tasks.empty_hint") %>

-<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/solid_stack_web/shared/_locale_switcher.html.erb b/app/views/solid_stack_web/shared/_locale_switcher.html.erb new file mode 100644 index 0000000..cf831ed --- /dev/null +++ b/app/views/solid_stack_web/shared/_locale_switcher.html.erb @@ -0,0 +1,14 @@ +<% if SolidStackWeb.available_locales.size > 1 %> +
"> + <% request.query_parameters.except("locale").each do |k, v| %> + <%= hidden_field_tag k, v %> + <% end %> + +
+<% end %> \ No newline at end of file diff --git a/app/views/solid_stack_web/stats/index.html.erb b/app/views/solid_stack_web/stats/index.html.erb index 1afc0b9..188b0a4 100644 --- a/app/views/solid_stack_web/stats/index.html.erb +++ b/app/views/solid_stack_web/stats/index.html.erb @@ -1,5 +1,5 @@
-

Performance Stats

+

<%= t("solid_stack_web.stats.title") %>

<% if @stats.any? %> @@ -7,15 +7,15 @@ <% [ - ["class_name", "Job Class"], - ["count", "Executions"], - ["avg", "Avg"], - ["p50", "p50"], - ["p95", "p95"], - ["p99", "p99"], - ["stddev", "Std Dev"], - ["min", "Min"], - ["max", "Max"] + ["class_name", t("solid_stack_web.stats.col_job_class")], + ["count", t("solid_stack_web.stats.col_executions")], + ["avg", t("solid_stack_web.stats.col_avg")], + ["p50", t("solid_stack_web.stats.col_p50")], + ["p95", t("solid_stack_web.stats.col_p95")], + ["p99", t("solid_stack_web.stats.col_p99")], + ["stddev", t("solid_stack_web.stats.col_std_dev")], + ["min", t("solid_stack_web.stats.col_min")], + ["max", t("solid_stack_web.stats.col_max")] ].each do |col, label| %> > <% next_dir = (@sort == col && @direction == "desc") ? "asc" : "desc" %> @@ -47,7 +47,7 @@ <% else %>
-

No finished jobs yet

-

Performance stats appear here once jobs complete. <%= link_to "View active jobs →", jobs_path %>

+

<%= t("solid_stack_web.stats.empty_title") %>

+

<%= t("solid_stack_web.stats.empty_hint") %> <%= link_to t("solid_stack_web.stats.view_active_jobs"), jobs_path %>

-<% end %> \ No newline at end of file +<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000..26b67d3 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,395 @@ +en: + solid_stack_web: + layout: + title: "Solid Stack Dashboard" + skip_to_content: "Skip to main content" + logo: "Solid Stack" + theme_toggle: "Switch to dark mode" + nav: + main_navigation: "Main navigation" + queue: "Queue" + cache: "Cache" + cable: "Cable" + subnav: + cache_section: "Cache section" + cable_section: "Cable section" + queue_section: "Queue section" + overview: "Overview" + entries: "Entries" + jobs: "Jobs" + failed: "Failed" + queues: "Queues" + recurring: "Recurring" + stats: "Stats" + history: "History" + processes: "Processes" + audit: "Audit" + shared: + actions: "Actions" + pagination: "Pagination" + clear: "Clear" + back_to_dashboard: "Back to Dashboard" + select_all: "Select all" + export_csv: "Export CSV" + period_filter: + label: "Time period" + all: "All" + locale_switcher: + label: "Language" + dashboard: + title: "Overview" + solid_queue: "Solid Queue" + solid_cache: "Solid Cache" + solid_cable: "Solid Cable" + view_jobs: "View Jobs →" + view_cache: "View Cache →" + view_cable: "View Cable →" + ready: "Ready" + scheduled: "Scheduled" + running: "Running" + blocked: "Blocked" + failed: "Failed" + done_1h: "Done (1h)" + done_24h: "Done (24h)" + slow_24h: "Slow (24h)" + healthy: "Healthy" + stale: "Stale" + messages: "Messages" + channels: "Channels" + msg_per_hr: "Msg/hr" + oldest: "Oldest" + entries: "Entries" + size: "Size" + throughput_label: "Throughput — last 12 hours" + failures_label: "Failures — last 12 hours" + top_channels: "Top channels" + axis: + twelve_h_ago: "12h ago" + six_h_ago: "6h ago" + now: "now" + jobs: + title: "Jobs" + detail: "Detail" + export_csv: "Export CSV" + run_all_now: "Run All Now (%{count})" + discard_all: "Discard All (%{count})" + all_queues: "All queues" + all_priorities: "All priorities" + priority_option: "Priority %{n}" + selected_count: "%{count} selected" + selected_label: "selected" + placeholder_job_class: "Filter by job class…" + aria_filter_job_class: "Filter by job class" + aria_filter_queue: "Filter by queue" + aria_filter_priority: "Filter by priority" + col_job_class: "Job Class" + col_queue: "Queue" + col_priority: "Priority" + col_enqueued_at: "Enqueued At" + col_scheduled_at: "Scheduled At" + col_wait_time: "Wait Time" + run_now: "Run Now" + discard: "Discard" + discard_selected: "Discard Selected" + confirm_run_all_now: "Run all %{count} scheduled jobs immediately?" + confirm_run_now: "Run this job immediately?" + confirm_discard: "Discard this job?" + confirm_discard_all: "Discard all %{count} jobs? This cannot be undone." + confirm_discard_selected: "Discard selected jobs? This cannot be undone." + no_jobs_matching: "No %{status} jobs matching \"%{search}\"" + no_jobs: "No %{status} jobs" + hint_ready: "Jobs will appear here once they are enqueued and ready to be picked up." + hint_scheduled: "No jobs are scheduled to run in the future." + hint_claimed: "No jobs are currently being processed by a worker." + hint_blocked: "No jobs are blocked by concurrency controls." + clear_search: "Clear search" + section_details: "Details" + section_arguments: "Arguments" + discard_job: "Discard Job" + field_status: "Status" + field_queue: "Queue" + field_priority: "Priority" + field_active_job_id: "Active Job ID" + field_concurrency_key: "Concurrency Key" + field_blocked_until: "Blocked Until" + field_enqueued_at: "Enqueued At" + field_scheduled_at: "Scheduled At" + field_finished_at: "Finished At" + tab_ready: "Ready" + tab_scheduled: "Scheduled" + tab_claimed: "Running" + tab_blocked: "Blocked" + failed_jobs: + title: "Failed Jobs" + error_summary: "Error Summary" + export_csv: "Export CSV" + filtered_by: "Filtered by" + clear_filter: "Clear filter" + detail: "Detail" + col_job_class: "Job Class" + col_queue: "Queue" + col_error: "Error" + col_failed_at: "Failed At" + retry: "Retry" + discard: "Discard" + retry_selected: "Retry Selected" + discard_selected: "Discard Selected" + confirm_discard: "Discard this job?" + confirm_discard_selected: "Discard selected failed jobs? This cannot be undone." + empty_title: "No failed jobs" + empty_hint: "All clear — your jobs are running without errors." + section_details: "Details" + section_backtrace: "Backtrace" + section_arguments: "Arguments" + update_retry: "Update & Retry" + field_queue: "Queue" + field_priority: "Priority" + field_active_job_id: "Active Job ID" + field_failed_at: "Failed At" + field_error: "Error" + field_message: "Message" + arguments_aria_label: "Job arguments JSON" + errors_title: "Error Summary" + back_to_failed_jobs: "← Failed Jobs" + errors_col_class: "Error Class" + errors_col_message: "Message" + errors_col_count: "Count" + view_jobs: "View Jobs" + queues: + title: "Queues" + col_name: "Name" + col_size: "Size" + col_depth: "Depth (12h)" + col_status: "Status" + status_paused: "Paused" + status_running: "Running" + pause: "Pause" + resume: "Resume" + discard_all_ready: "Discard All Ready (%{count})" + confirm_discard_all_ready: "Discard all %{count} ready jobs in %{queue}? This cannot be undone." + empty_title: "No queues with ready jobs" + empty_hint: "Workers are idle or all jobs are in another state." + check_jobs: "Check job statuses →" + show_col_job_class: "Job Class" + show_col_priority: "Priority" + show_col_enqueued_at: "Enqueued At" + discard: "Discard" + confirm_discard: "Discard this job?" + no_ready_jobs: "No ready jobs in %{queue}." + processes: + title: "Processes" + col_kind: "Kind" + col_name: "Name" + col_pid: "PID" + col_host: "Host" + col_last_heartbeat: "Last Heartbeat" + empty_title: "No active processes" + empty_hint: "Start a Solid Queue worker to begin processing jobs." + recurring_tasks: + title: "Recurring Tasks" + col_key: "Key" + col_schedule: "Schedule" + col_job_command: "Job / Command" + col_queue: "Queue" + col_next_run: "Next Run" + col_last_run: "Last Run" + col_type: "Type" + type_static: "Static" + type_dynamic: "Dynamic" + run_now: "Run Now" + confirm_run_now: "Run \"%{key}\" immediately?" + empty_title: "No recurring tasks configured" + empty_hint: "Define recurring tasks in your Solid Queue configuration to see them here." + stats: + title: "Performance Stats" + col_job_class: "Job Class" + col_executions: "Executions" + col_avg: "Avg" + col_p50: "p50" + col_p95: "p95" + col_p99: "p99" + col_std_dev: "Std Dev" + col_min: "Min" + col_max: "Max" + empty_title: "No finished jobs yet" + empty_hint: "Performance stats appear here once jobs complete." + view_active_jobs: "View active jobs →" + history: + title: "Job History" + export_csv: "Export CSV" + placeholder_filter: "Filter by job class…" + aria_filter: "Filter by job class" + filtering_by_queue: "Filtering by queue:" + clear_filter: "Clear filter" + col_job_class: "Job Class" + col_queue: "Queue" + col_duration: "Duration" + col_finished_at: "Finished At" + no_match_title: "No finished jobs match your filters" + no_match_hint: "Clear filters" + empty_title: "No finished jobs yet" + empty_hint: "Completed jobs will appear here once workers process them." + audit: + title: "Audit Log" + export_csv: "Export CSV" + all_actions: "All actions" + aria_filter_action: "Filter by action" + actor_label: "Actor:" + queue_label: "Queue:" + aria_clear_actor: "Clear actor filter" + aria_clear_queue: "Clear queue filter" + clear_all: "Clear all" + col_time: "Time" + col_action: "Action" + col_actor: "Actor" + col_job_class: "Job Class" + col_queue: "Queue" + col_count: "Count" + empty: "No audit events recorded yet." + cache: + title: "Solid Cache" + total_entries: "Total Entries" + total_size: "Total Size" + size_distribution: "Size Distribution" + largest_entries: "Largest Entries" + col_range: "Range" + col_entries: "Entries" + col_distribution: "Distribution" + col_key: "Key" + col_size: "Size" + entries_written: "Entries written — last 24 hours" + bytes_written: "Bytes written — last 24 hours" + axis_24h_ago: "24h ago" + axis_12h_ago: "12h ago" + axis_now: "now" + cache_entries: + title: "Cache Entries" + flush_all: "Flush All" + back_to_entries: "← Entries" + placeholder_key: "Filter by key…" + aria_filter_key: "Filter by key" + col_key: "Key" + col_size: "Size" + col_created: "Created" + delete: "Delete" + confirm_flush_all: "Delete all cache entries? This cannot be undone." + confirm_delete: "Delete this cache entry?" + no_entries_matching: "No entries matching \"%{search}\"" + clear_search: "Clear search" + empty_title: "No cache entries" + empty_hint: "Entries will appear here once your application writes to the cache." + show_section_details: "Details" + show_section_value: "Value" + show_key: "Key" + show_size: "Size" + show_created: "Created" + show_truncated: "Showing first 4 KB of %{size} total." + show_preview_disabled: "Value preview is disabled. Set config.allow_value_preview = true in your initializer to enable." + cable: + title: "Solid Cable" + total_messages: "Total Messages" + channels: "Channels" + messages_timeline: "Messages — last 24 hours" + placeholder_channel: "Filter by channel…" + aria_filter_channel: "Filter by channel" + older_than_1_day: "Older than 1 day" + older_than_7_days: "Older than 7 days" + older_than_30_days: "Older than 30 days" + purge_old: "Purge Old" + confirm_purge: "Purge these messages? This cannot be undone." + col_channel: "Channel" + col_messages: "Messages" + col_last_message: "Last Message" + no_channels_matching: "No channels matching \"%{search}\"" + clear_search: "Clear search" + empty_title: "No cable messages" + empty_hint: "Messages will appear here once clients connect and broadcast over Action Cable." + axis_24h_ago: "24h ago" + axis_12h_ago: "12h ago" + axis_now: "now" + cable_messages: + purge_channel: "Purge Channel" + back_to_channels: "← Channels" + back_to_channels_link: "Back to channels →" + confirm_purge_channel: "Delete all messages for this channel? This cannot be undone." + placeholder_payload: "Filter by payload…" + aria_filter_payload: "Filter by payload" + clear_search: "Clear search" + col_id: "ID" + col_payload: "Payload" + col_sent: "Sent" + no_messages_matching: "No messages matching \"%{search}\"" + empty_title: "No messages for this channel" + empty_hint: "Messages may have been purged or the channel has gone quiet." + errors: + not_found_title: "Not Found" + not_found_message: "404 — The record you're looking for doesn't exist or has been removed." + server_error_title: "Something Went Wrong" + server_error_message: "500 — An unexpected error occurred." + flash: + job_discarded: "Job discarded." + jobs_discarded: + one: "1 job discarded." + other: "%{count} jobs discarded." + job_retried: "Job retried." + jobs_retried: + one: "1 job retried." + other: "%{count} jobs retried." + cache_entry_deleted: "Cache entry deleted." + arguments_updated: "Arguments updated and job queued for retry." + cache_flushed: "All cache entries flushed." + channel_purged: "All messages for this channel have been purged." + messages_purged: + one: "Messages older than 1 day purged." + other: "Messages older than %{count} days purged." + task_queued: '"%{key}" queued for immediate execution.' + jobs_run_immediately: + one: "1 job scheduled to run immediately." + other: "%{count} jobs scheduled to run immediately." + job_run_immediately: "Job scheduled to run immediately." + job_rescheduled: "Job rescheduled by +%{offset}." + audit_migration_required: "Audit log requires running `rails solid_stack_web:install:migrations && rails db:migrate`." + cannot_retry_jobs: "Could not retry jobs: %{error}" + cannot_discard_jobs: "Could not discard jobs: %{error}" + cannot_run_jobs: "Could not run jobs: %{error}" + cannot_reschedule_job: "Could not reschedule job: %{error}" + cannot_update_job: "Could not update job: %{error}" + invalid_json: "Invalid JSON — arguments were not saved." + task_not_found: "Recurring task not found." + cannot_run_task: "Could not run task: %{error}" + cannot_enqueue_task: 'Could not enqueue "%{key}" — it may have just run.' + cannot_discard: "Cannot discard %{status} jobs." + invalid_offset: "Invalid offset." + helpers: + cache_entry_this_hour: + one: "1 entry this hour" + other: "%{count} entries this hour" + cache_entry_hours_ago: + one: "1 entry %{hours}h ago" + other: "%{count} entries %{hours}h ago" + cache_size_this_hour: "%{size} written this hour" + cache_size_hours_ago: "%{size} written %{hours}h ago" + cable_message_this_hour: + one: "1 message this hour" + other: "%{count} messages this hour" + cable_message_hours_ago: + one: "1 message %{hours}h ago" + other: "%{count} messages %{hours}h ago" + throughput_last_hour: + one: "1 job in the last hour" + other: "%{count} jobs in the last hour" + throughput_hours_ago: + one: "1 job (%{from}h–%{to}h ago)" + other: "%{count} jobs (%{from}h–%{to}h ago)" + queue_depth_now: + one: "1 ready job now" + other: "%{count} ready jobs now" + queue_depth_hours_ago: + one: "1 ready job %{hours}h ago" + other: "%{count} ready jobs %{hours}h ago" + failure_last_hour: + one: "1 failure in the last hour" + other: "%{count} failures in the last hour" + failure_hours_ago: + one: "1 failure (%{from}h–%{to}h ago)" + other: "%{count} failures (%{from}h–%{to}h ago)" \ No newline at end of file diff --git a/config/locales/es.yml b/config/locales/es.yml new file mode 100644 index 0000000..f8322fa --- /dev/null +++ b/config/locales/es.yml @@ -0,0 +1,395 @@ +es: + solid_stack_web: + layout: + title: "Panel de Solid Stack" + skip_to_content: "Saltar al contenido principal" + logo: "Solid Stack" + theme_toggle: "Cambiar a modo oscuro" + nav: + main_navigation: "Navegación principal" + queue: "Cola" + cache: "Caché" + cable: "Cable" + subnav: + cache_section: "Sección de caché" + cable_section: "Sección de cable" + queue_section: "Sección de cola" + overview: "Resumen" + entries: "Entradas" + jobs: "Trabajos" + failed: "Fallidos" + queues: "Colas" + recurring: "Recurrentes" + stats: "Estadísticas" + history: "Historial" + processes: "Procesos" + audit: "Auditoría" + shared: + actions: "Acciones" + pagination: "Paginación" + clear: "Limpiar" + back_to_dashboard: "Volver al panel" + select_all: "Seleccionar todos" + export_csv: "Exportar CSV" + period_filter: + label: "Período de tiempo" + all: "Todo" + locale_switcher: + label: "Idioma" + dashboard: + title: "Resumen" + solid_queue: "Solid Queue" + solid_cache: "Solid Cache" + solid_cable: "Solid Cable" + view_jobs: "Ver trabajos →" + view_cache: "Ver caché →" + view_cable: "Ver cable →" + ready: "Listo" + scheduled: "Programado" + running: "Ejecutando" + blocked: "Bloqueado" + failed: "Fallido" + done_1h: "Hecho (1h)" + done_24h: "Hecho (24h)" + slow_24h: "Lento (24h)" + healthy: "Saludable" + stale: "Obsoleto" + messages: "Mensajes" + channels: "Canales" + msg_per_hr: "Msg/hr" + oldest: "Más antiguo" + entries: "Entradas" + size: "Tamaño" + throughput_label: "Rendimiento — últimas 12 horas" + failures_label: "Fallos — últimas 12 horas" + top_channels: "Principales canales" + axis: + twelve_h_ago: "hace 12h" + six_h_ago: "hace 6h" + now: "ahora" + jobs: + title: "Trabajos" + detail: "Detalle" + export_csv: "Exportar CSV" + run_all_now: "Ejecutar todos ahora (%{count})" + discard_all: "Descartar todos (%{count})" + all_queues: "Todas las colas" + all_priorities: "Todas las prioridades" + priority_option: "Prioridad %{n}" + selected_count: "%{count} seleccionados" + selected_label: "seleccionados" + placeholder_job_class: "Filtrar por clase de trabajo…" + aria_filter_job_class: "Filtrar por clase de trabajo" + aria_filter_queue: "Filtrar por cola" + aria_filter_priority: "Filtrar por prioridad" + col_job_class: "Clase de trabajo" + col_queue: "Cola" + col_priority: "Prioridad" + col_enqueued_at: "Encolado a las" + col_scheduled_at: "Programado a las" + col_wait_time: "Tiempo de espera" + run_now: "Ejecutar ahora" + discard: "Descartar" + discard_selected: "Descartar seleccionados" + confirm_run_all_now: "¿Ejecutar todos los %{count} trabajos programados inmediatamente?" + confirm_run_now: "¿Ejecutar este trabajo inmediatamente?" + confirm_discard: "¿Descartar este trabajo?" + confirm_discard_all: "¿Descartar todos los %{count} trabajos? Esta acción no se puede deshacer." + confirm_discard_selected: "¿Descartar los trabajos seleccionados? Esta acción no se puede deshacer." + no_jobs_matching: "No hay trabajos %{status} que coincidan con \"%{search}\"" + no_jobs: "No hay trabajos %{status}" + hint_ready: "Los trabajos aparecerán aquí una vez encolados y listos para ser procesados." + hint_scheduled: "No hay trabajos programados para ejecutarse en el futuro." + hint_claimed: "Ningún trabajador está procesando trabajos actualmente." + hint_blocked: "No hay trabajos bloqueados por controles de concurrencia." + clear_search: "Limpiar búsqueda" + section_details: "Detalles" + section_arguments: "Argumentos" + discard_job: "Descartar trabajo" + field_status: "Estado" + field_queue: "Cola" + field_priority: "Prioridad" + field_active_job_id: "ID de Active Job" + field_concurrency_key: "Clave de concurrencia" + field_blocked_until: "Bloqueado hasta" + field_enqueued_at: "Encolado a las" + field_scheduled_at: "Programado a las" + field_finished_at: "Finalizado a las" + tab_ready: "Listo" + tab_scheduled: "Programado" + tab_claimed: "Ejecutando" + tab_blocked: "Bloqueado" + failed_jobs: + title: "Trabajos fallidos" + error_summary: "Resumen de errores" + export_csv: "Exportar CSV" + filtered_by: "Filtrado por" + clear_filter: "Limpiar filtro" + detail: "Detalle" + col_job_class: "Clase de trabajo" + col_queue: "Cola" + col_error: "Error" + col_failed_at: "Falló a las" + retry: "Reintentar" + discard: "Descartar" + retry_selected: "Reintentar seleccionados" + discard_selected: "Descartar seleccionados" + confirm_discard: "¿Descartar este trabajo?" + confirm_discard_selected: "¿Descartar los trabajos fallidos seleccionados? Esta acción no se puede deshacer." + empty_title: "No hay trabajos fallidos" + empty_hint: "Todo en orden — sus trabajos se están ejecutando sin errores." + section_details: "Detalles" + section_backtrace: "Rastreo" + section_arguments: "Argumentos" + update_retry: "Actualizar y reintentar" + field_queue: "Cola" + field_priority: "Prioridad" + field_active_job_id: "ID de Active Job" + field_failed_at: "Falló a las" + field_error: "Error" + field_message: "Mensaje" + arguments_aria_label: "JSON de argumentos del trabajo" + errors_title: "Resumen de errores" + back_to_failed_jobs: "← Trabajos fallidos" + errors_col_class: "Clase de error" + errors_col_message: "Mensaje" + errors_col_count: "Cantidad" + view_jobs: "Ver trabajos" + queues: + title: "Colas" + col_name: "Nombre" + col_size: "Tamaño" + col_depth: "Profundidad (12h)" + col_status: "Estado" + status_paused: "Pausado" + status_running: "Ejecutando" + pause: "Pausar" + resume: "Reanudar" + discard_all_ready: "Descartar todos los listos (%{count})" + confirm_discard_all_ready: "¿Descartar todos los %{count} trabajos listos en %{queue}? Esta acción no se puede deshacer." + empty_title: "No hay colas con trabajos listos" + empty_hint: "Los trabajadores están inactivos o todos los trabajos están en otro estado." + check_jobs: "Ver estados de trabajos →" + show_col_job_class: "Clase de trabajo" + show_col_priority: "Prioridad" + show_col_enqueued_at: "Encolado a las" + discard: "Descartar" + confirm_discard: "¿Descartar este trabajo?" + no_ready_jobs: "No hay trabajos listos en %{queue}." + processes: + title: "Procesos" + col_kind: "Tipo" + col_name: "Nombre" + col_pid: "PID" + col_host: "Host" + col_last_heartbeat: "Último latido" + empty_title: "No hay procesos activos" + empty_hint: "Inicie un trabajador de Solid Queue para comenzar a procesar trabajos." + recurring_tasks: + title: "Tareas recurrentes" + col_key: "Clave" + col_schedule: "Programación" + col_job_command: "Trabajo / Comando" + col_queue: "Cola" + col_next_run: "Próxima ejecución" + col_last_run: "Última ejecución" + col_type: "Tipo" + type_static: "Estático" + type_dynamic: "Dinámico" + run_now: "Ejecutar ahora" + confirm_run_now: "¿Ejecutar \"%{key}\" inmediatamente?" + empty_title: "No hay tareas recurrentes configuradas" + empty_hint: "Defina tareas recurrentes en su configuración de Solid Queue para verlas aquí." + stats: + title: "Estadísticas de rendimiento" + col_job_class: "Clase de trabajo" + col_executions: "Ejecuciones" + col_avg: "Promedio" + col_p50: "p50" + col_p95: "p95" + col_p99: "p99" + col_std_dev: "Desv. estándar" + col_min: "Mínimo" + col_max: "Máximo" + empty_title: "Aún no hay trabajos finalizados" + empty_hint: "Las estadísticas de rendimiento aparecen aquí cuando se completan los trabajos." + view_active_jobs: "Ver trabajos activos →" + history: + title: "Historial de trabajos" + export_csv: "Exportar CSV" + placeholder_filter: "Filtrar por clase de trabajo…" + aria_filter: "Filtrar por clase de trabajo" + filtering_by_queue: "Filtrando por cola:" + clear_filter: "Limpiar filtro" + col_job_class: "Clase de trabajo" + col_queue: "Cola" + col_duration: "Duración" + col_finished_at: "Finalizado a las" + no_match_title: "Ningún trabajo finalizado coincide con sus filtros" + no_match_hint: "Limpiar filtros" + empty_title: "Aún no hay trabajos finalizados" + empty_hint: "Los trabajos completados aparecerán aquí cuando los trabajadores los procesen." + audit: + title: "Registro de auditoría" + export_csv: "Exportar CSV" + all_actions: "Todas las acciones" + aria_filter_action: "Filtrar por acción" + actor_label: "Actor:" + queue_label: "Cola:" + aria_clear_actor: "Limpiar filtro de actor" + aria_clear_queue: "Limpiar filtro de cola" + clear_all: "Limpiar todo" + col_time: "Hora" + col_action: "Acción" + col_actor: "Actor" + col_job_class: "Clase de trabajo" + col_queue: "Cola" + col_count: "Cantidad" + empty: "Aún no se han registrado eventos de auditoría." + cache: + title: "Solid Cache" + total_entries: "Total de entradas" + total_size: "Tamaño total" + size_distribution: "Distribución de tamaños" + largest_entries: "Entradas más grandes" + col_range: "Rango" + col_entries: "Entradas" + col_distribution: "Distribución" + col_key: "Clave" + col_size: "Tamaño" + entries_written: "Entradas escritas — últimas 24 horas" + bytes_written: "Bytes escritos — últimas 24 horas" + axis_24h_ago: "hace 24h" + axis_12h_ago: "hace 12h" + axis_now: "ahora" + cache_entries: + title: "Entradas de caché" + flush_all: "Vaciar todo" + back_to_entries: "← Entradas" + placeholder_key: "Filtrar por clave…" + aria_filter_key: "Filtrar por clave" + col_key: "Clave" + col_size: "Tamaño" + col_created: "Creado" + delete: "Eliminar" + confirm_flush_all: "¿Eliminar todas las entradas de caché? Esta acción no se puede deshacer." + confirm_delete: "¿Eliminar esta entrada de caché?" + no_entries_matching: "No hay entradas que coincidan con \"%{search}\"" + clear_search: "Limpiar búsqueda" + empty_title: "No hay entradas de caché" + empty_hint: "Las entradas aparecerán aquí cuando su aplicación escriba en el caché." + show_section_details: "Detalles" + show_section_value: "Valor" + show_key: "Clave" + show_size: "Tamaño" + show_created: "Creado" + show_truncated: "Mostrando los primeros 4 KB de %{size} en total." + show_preview_disabled: "La vista previa de valores está deshabilitada. Configure config.allow_value_preview = true en su inicializador para habilitarla." + cable: + title: "Solid Cable" + total_messages: "Total de mensajes" + channels: "Canales" + messages_timeline: "Mensajes — últimas 24 horas" + placeholder_channel: "Filtrar por canal…" + aria_filter_channel: "Filtrar por canal" + older_than_1_day: "Más de 1 día" + older_than_7_days: "Más de 7 días" + older_than_30_days: "Más de 30 días" + purge_old: "Purgar antiguos" + confirm_purge: "¿Purgar estos mensajes? Esta acción no se puede deshacer." + col_channel: "Canal" + col_messages: "Mensajes" + col_last_message: "Último mensaje" + no_channels_matching: "No hay canales que coincidan con \"%{search}\"" + clear_search: "Limpiar búsqueda" + empty_title: "No hay mensajes de cable" + empty_hint: "Los mensajes aparecerán aquí cuando los clientes se conecten y transmitan por Action Cable." + axis_24h_ago: "hace 24h" + axis_12h_ago: "hace 12h" + axis_now: "ahora" + cable_messages: + purge_channel: "Purgar canal" + back_to_channels: "← Canales" + back_to_channels_link: "Volver a canales →" + confirm_purge_channel: "¿Eliminar todos los mensajes de este canal? Esta acción no se puede deshacer." + placeholder_payload: "Filtrar por carga…" + aria_filter_payload: "Filtrar por carga" + clear_search: "Limpiar búsqueda" + col_id: "ID" + col_payload: "Carga" + col_sent: "Enviado" + no_messages_matching: "No hay mensajes que coincidan con \"%{search}\"" + empty_title: "No hay mensajes para este canal" + empty_hint: "Los mensajes pueden haber sido purgados o el canal se ha silenciado." + errors: + not_found_title: "No encontrado" + not_found_message: "404 — El registro que busca no existe o ha sido eliminado." + server_error_title: "Algo salió mal" + server_error_message: "500 — Se produjo un error inesperado." + flash: + job_discarded: "Trabajo descartado." + jobs_discarded: + one: "1 trabajo descartado." + other: "%{count} trabajos descartados." + job_retried: "Trabajo reintentado." + jobs_retried: + one: "1 trabajo reintentado." + other: "%{count} trabajos reintentados." + cache_entry_deleted: "Entrada de caché eliminada." + arguments_updated: "Argumentos actualizados y trabajo encolado para reintento." + cache_flushed: "Todas las entradas de caché vaciadas." + channel_purged: "Todos los mensajes de este canal han sido purgados." + messages_purged: + one: "Mensajes de más de 1 día purgados." + other: "Mensajes de más de %{count} días purgados." + task_queued: '"%{key}" encolado para ejecución inmediata.' + jobs_run_immediately: + one: "1 trabajo programado para ejecutarse inmediatamente." + other: "%{count} trabajos programados para ejecutarse inmediatamente." + job_run_immediately: "Trabajo programado para ejecutarse inmediatamente." + job_rescheduled: "Trabajo reprogramado en +%{offset}." + audit_migration_required: "El registro de auditoría requiere ejecutar `rails solid_stack_web:install:migrations && rails db:migrate`." + cannot_retry_jobs: "No se pudieron reintentar los trabajos: %{error}" + cannot_discard_jobs: "No se pudieron descartar los trabajos: %{error}" + cannot_run_jobs: "No se pudieron ejecutar los trabajos: %{error}" + cannot_reschedule_job: "No se pudo reprogramar el trabajo: %{error}" + cannot_update_job: "No se pudo actualizar el trabajo: %{error}" + invalid_json: "JSON inválido — los argumentos no fueron guardados." + task_not_found: "Tarea recurrente no encontrada." + cannot_run_task: "No se pudo ejecutar la tarea: %{error}" + cannot_enqueue_task: 'No se pudo encolar "%{key}" — puede que se acabe de ejecutar.' + cannot_discard: "No se pueden descartar trabajos %{status}." + invalid_offset: "Desplazamiento inválido." + helpers: + cache_entry_this_hour: + one: "1 entrada esta hora" + other: "%{count} entradas esta hora" + cache_entry_hours_ago: + one: "1 entrada hace %{hours}h" + other: "%{count} entradas hace %{hours}h" + cache_size_this_hour: "%{size} escritos esta hora" + cache_size_hours_ago: "%{size} escritos hace %{hours}h" + cable_message_this_hour: + one: "1 mensaje esta hora" + other: "%{count} mensajes esta hora" + cable_message_hours_ago: + one: "1 mensaje hace %{hours}h" + other: "%{count} mensajes hace %{hours}h" + throughput_last_hour: + one: "1 trabajo en la última hora" + other: "%{count} trabajos en la última hora" + throughput_hours_ago: + one: "1 trabajo (hace %{from}h–%{to}h)" + other: "%{count} trabajos (hace %{from}h–%{to}h)" + queue_depth_now: + one: "1 trabajo listo ahora" + other: "%{count} trabajos listos ahora" + queue_depth_hours_ago: + one: "1 trabajo listo hace %{hours}h" + other: "%{count} trabajos listos hace %{hours}h" + failure_last_hour: + one: "1 fallo en la última hora" + other: "%{count} fallos en la última hora" + failure_hours_ago: + one: "1 fallo (hace %{from}h–%{to}h)" + other: "%{count} fallos (hace %{from}h–%{to}h)" \ No newline at end of file diff --git a/lib/solid_stack_web.rb b/lib/solid_stack_web.rb index fb8cbba..5c0da4d 100644 --- a/lib/solid_stack_web.rb +++ b/lib/solid_stack_web.rb @@ -8,7 +8,8 @@ class << self :alert_failure_threshold, :alert_queue_thresholds, :alert_slow_job_count_threshold, :alert_stale_process_threshold, :dashboard_refresh_interval, :default_refresh_interval, - :search_results_limit, :allow_value_preview + :search_results_limit, :allow_value_preview, + :available_locales def page_size @page_size || 25 @@ -62,6 +63,10 @@ def allow_value_preview @allow_value_preview || false end + def available_locales + @available_locales || %i[en es] + end + # Returns the path at which the engine is mounted in the host application, # derived automatically from the host's routes. Host apps can use this to # build links to the dashboard without hardcoding the mount path. diff --git a/lib/solid_stack_web/engine.rb b/lib/solid_stack_web/engine.rb index bf9e2cb..2c7c149 100644 --- a/lib/solid_stack_web/engine.rb +++ b/lib/solid_stack_web/engine.rb @@ -11,6 +11,7 @@ class Engine < ::Rails::Engine isolate_namespace SolidStackWeb config.i18n.load_path += Gem.find_files("pagy/locales/en.yml") + config.i18n.load_path += Dir[root.join("config/locales/*.yml").to_s] initializer "solid_stack_web.assets" do |app| if app.config.respond_to?(:assets)