Files
sure/app/views/rules/index.html.erb
Brendon Scheiber 0c126b1674 feat(i18n): extract hardcoded English strings to locale files (#1806)
* Extract hardcoded strings to i18n

Replace numerous hardcoded English strings with I18n lookups (t / I18n.t) across controllers, views, helpers, and components, and convert model validation error messages to symbol keys. Added multiple locale files under config/locales for models and views. This centralizes user-facing notices/alerts, UI text, import/validation messages, and prepares the app for localization and easier translation maintenance.

* Update en.yml

* Update preview-cleanup.yml

* Revert "Update preview-cleanup.yml"

This reverts commit 1ba6d3c34c.

* test: align i18n assertions with translated messages

* Standardize balance error key and tweak locales

Replace SophtronAccount's :requires_balance error key with :no_balance and update related locale strings for sophtron, plaid, and simplefin accounts to use the new key and clearer copy. Also switch the QIF upload redirect notice to use a relative translation key (t('.qif_uploaded')), remove an unused SSO providers help line, and fix a trailing-newline/whitespace issue in the subscriptions locale. These changes standardize validation keys and improve translation consistency and messaging.

---------

Co-authored-by: KiloClaw <kiloclaw@openclaw.ai>
2026-05-17 09:52:49 +02:00

188 lines
8.6 KiB
Plaintext

<%= content_for :page_title, t(".page_title") %>
<%= content_for :page_actions do %>
<% if @rules.any? %>
<%= render DS::Menu.new do |menu| %>
<% menu.with_item(
variant: "button",
text: t("rules.clear_ai_cache.button"),
href: clear_ai_cache_rules_path,
icon: "refresh-cw",
method: :post,
confirm: CustomConfirm.new(
title: t("rules.clear_ai_cache.confirm_title"),
body: t("rules.clear_ai_cache.confirm_body"),
btn_text: t("rules.clear_ai_cache.confirm_button")
)) %>
<% menu.with_item(
variant: "button",
text: t(".delete_all_rules"),
href: destroy_all_rules_path,
icon: "trash-2",
method: :delete,
confirm: CustomConfirm.for_resource_deletion("all rules", high_severity: true)) %>
<% end %>
<%= render DS::Link.new(
text: t("rules.apply_all.button"),
variant: "secondary",
href: confirm_all_rules_path,
icon: "play",
frame: :modal
) %>
<% end %>
<%= render DS::Link.new(
text: t(".new_rule"),
variant: "primary",
href: new_rule_path(resource_type: "transaction"),
icon: "plus",
frame: :modal
) %>
<% end %>
<% if self_hosted? %>
<div class="flex items-center gap-2 mb-2 py-4">
<%= icon("circle-alert", size: "sm") %>
<p class="text-sm text-secondary">
<%= t(".ai_cost_warning") %>
</p>
</div>
<% end %>
<div class="bg-container rounded-xl shadow-border-xs p-4">
<% if @rules.any? %>
<div class="bg-container-inset rounded-xl">
<div class="flex justify-between px-4 py-2 text-xs uppercase">
<div class="flex items-center gap-1.5 font-medium text-secondary">
<p><%= t(".rules_heading") %></p>
<span class="text-subdued">&middot;</span>
<p><%= @rules.count %></p>
</div>
<div class="flex items-center gap-1">
<span class="text-secondary"><%= t(".sort_by") %></span>
<%= form_with url: rules_path, method: :get, local: true, class: "flex items-center", data: { controller: "auto-submit-form" } do |form| %>
<%= form.select :sort_by,
options_for_select([[t(".sort_name"), "name"], [t(".sort_updated_at"), "updated_at"]], @sort_by),
{},
class: "min-w-[120px] bg-transparent rounded border-none cursor-pointer text-primary uppercase text-xs w-auto",
data: { auto_submit_form_target: "auto", autosubmit_trigger_event: "change" } %>
<%= form.hidden_field :direction, value: @direction %>
<% end %>
<%= render DS::Link.new(
href: rules_path(direction: @direction == "asc" ? "desc" : "asc", sort_by: @sort_by),
variant: "icon",
icon: "arrow-up-down",
size: :sm,
title: t(".toggle_sort_direction")
) %>
</div>
</div>
<div class="p-1">
<div class="flex flex-col bg-container rounded-lg shadow-border-xs">
<%= render partial: "rule", collection: @rules, spacer_template: "shared/ruler" %>
</div>
</div>
</div>
<% else %>
<div class="flex justify-center items-center py-20">
<div class="text-center flex flex-col items-center max-w-[500px]">
<p class="text-sm text-primary font-medium mb-1"><%= t(".no_rules_title") %></p>
<p class="text-sm text-secondary mb-4"><%= t(".no_rules_description") %></p>
<div class="flex items-center gap-2">
<%= render DS::Link.new(
text: t(".new_rule"),
variant: "primary",
href: new_rule_path(resource_type: "transaction"),
icon: "plus",
frame: :modal
) %>
</div>
</div>
</div>
<% end %>
</div>
<!-- Recent Runs Section -->
<% if @recent_runs.any? %>
<div class="mt-6 bg-container rounded-xl shadow-border-xs p-4">
<div class="mb-4">
<h2 class="text-primary text-lg font-medium mb-1"><%= t("rules.recent_runs.title") %></h2>
<p class="text-sm text-secondary"><%= t("rules.recent_runs.description") %></p>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-surface-default border-b border-primary">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-secondary uppercase tracking-wider">
<%= t("rules.recent_runs.columns.date_time") %>
</th>
<th class="px-4 py-3 text-center text-xs font-medium text-secondary uppercase tracking-wider">
<%= t("rules.recent_runs.columns.execution_type") %>
</th>
<th class="px-4 py-3 text-center text-xs font-medium text-secondary uppercase tracking-wider">
<%= t("rules.recent_runs.columns.status") %>
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-secondary uppercase tracking-wider">
<%= t("rules.recent_runs.columns.rule_name") %>
</th>
<th class="px-4 py-3 text-center text-xs font-medium text-secondary uppercase tracking-wider">
<div class="flex flex-col leading-tight">
<div><%= t("rules.recent_runs.columns.transactions_counts.queued") %></div>
<div><%= t("rules.recent_runs.columns.transactions_counts.processed") %></div>
<div><%= t("rules.recent_runs.columns.transactions_counts.modified") %></div>
<div><%= t("rules.recent_runs.columns.transactions_counts.blocked", default: "Blocked") %></div>
</div>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
<% @recent_runs.each do |run| %>
<tr class="<%= "bg-red-50 theme-dark:bg-red-950/30" if run.failed? %>">
<td class="px-4 py-3 text-sm text-primary whitespace-nowrap">
<%= run.executed_at.strftime("%b %d, %Y %I:%M %p") %>
</td>
<td class="px-4 py-3 text-sm text-primary text-center">
<span class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium <%= run.execution_type == "manual" ? "bg-blue-50 text-blue-700 theme-dark:bg-blue-950/30 theme-dark:text-blue-400" : "bg-purple-50 text-purple-700 theme-dark:bg-purple-950/30 theme-dark:text-purple-400" %>">
<%= t("rules.recent_runs.execution_types.#{run.execution_type}") %>
</span>
</td>
<td class="px-4 py-3 text-sm text-center">
<div class="flex items-center justify-center gap-2">
<% if run.pending? %>
<span class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-yellow-50 text-yellow-700 theme-dark:bg-yellow-950/30 theme-dark:text-yellow-400">
<%= t("rules.recent_runs.statuses.#{run.status}") %>
</span>
<% elsif run.success? %>
<span class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-green-50 text-green-700 theme-dark:bg-green-950/30 theme-dark:text-green-400">
<%= t("rules.recent_runs.statuses.#{run.status}") %>
</span>
<% else %>
<span class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-red-50 text-red-700 theme-dark:bg-red-950/30 theme-dark:text-red-400">
<%= t("rules.recent_runs.statuses.#{run.status}") %>
</span>
<% end %>
<% if run.failed? && run.error_message.present? %>
<div data-controller="tooltip" data-tooltip-content-value="<%= run.error_message %>">
<%= icon("info", size: "sm", class: "text-red-500") %>
</div>
<% end %>
</div>
</td>
<td class="px-4 py-3 text-sm text-primary">
<%= run.rule_name.presence || run.rule&.name.presence || t("rules.recent_runs.unnamed_rule") %>
</td>
<td class="px-4 py-3 text-sm text-primary text-center tabular-nums">
<%= "#{number_with_delimiter(run.transactions_queued)} / #{number_with_delimiter(run.transactions_processed)} / #{number_with_delimiter(run.transactions_modified)} / #{number_with_delimiter(run.transactions_blocked)}" %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% if @pagy.pages > 1 %>
<div class="mt-4">
<%= render "shared/pagination", pagy: @pagy %>
</div>
<% end %>
</div>
<% end %>