From b88f44ab1c44f4eae8f7457c1c6ac5d71be035e8 Mon Sep 17 00:00:00 2001 From: Emiliano Frascaro Date: Fri, 24 Jul 2026 00:38:50 +0200 Subject: [PATCH] Completa i18n IT/EN/FR/DE/ES su sito pubblico, area autenticata e admin. Tutte le view marketing, legali, viewer, dashboard e admin usano t(); mailer utente-facing e flash localizzati; admin con LocaleResolver e selettore lingua. Co-authored-by: Cursor --- .../app/controllers/admin/auth_controller.rb | 6 +- .../app/controllers/admin/base_controller.rb | 24 +- .../controllers/admin/billing_controller.rb | 2 +- .../admin/billing_invoices_controller.rb | 10 +- .../app/controllers/admin/clubs_controller.rb | 4 +- .../controllers/admin/locales_controller.rb | 11 + .../app/controllers/admin/ops_controller.rb | 6 +- .../controllers/admin/passwords_controller.rb | 8 +- .../controllers/admin/sessions_controller.rb | 10 +- .../controllers/admin/youtube_controller.rb | 2 +- .../public/club_billing_controller.rb | 14 +- .../public/club_matches_controller.rb | 6 +- .../public/club_recordings_controller.rb | 2 +- .../controllers/public/clubs_controller.rb | 36 +- .../public/invitations_controller.rb | 10 +- .../controllers/public/matches_controller.rb | 10 +- .../public/password_resets_controller.rb | 12 +- .../public/registrations_controller.rb | 6 +- .../controllers/public/replay_controller.rb | 6 +- .../controllers/public/sessions_controller.rb | 6 +- .../public/team_roster_members_controller.rb | 6 +- .../controllers/public/teams_controller.rb | 16 +- .../controllers/public/web_base_controller.rb | 6 +- backend/app/helpers/admin_helper.rb | 2 +- backend/app/helpers/application_helper.rb | 28 + backend/app/helpers/public/billing_helper.rb | 14 +- backend/app/helpers/public/live_helper.rb | 29 +- backend/app/helpers/public/regia_helper.rb | 52 +- .../app/helpers/public/team_pages_helper.rb | 2 +- backend/app/helpers/roster_helper.rb | 4 +- backend/app/mailers/billing/invoice_mailer.rb | 19 +- .../app/mailers/recordings/replay_mailer.rb | 22 +- backend/app/mailers/user_mailer.rb | 4 +- .../models/concerns/club_billing_profile.rb | 42 +- backend/app/models/match.rb | 8 +- backend/app/models/recording.rb | 12 +- backend/app/models/team_roster_member.rb | 25 +- .../billing/stripe/plan_change_messages.rb | 23 +- backend/app/views/admin/auth/new.html.erb | 10 +- .../app/views/admin/billing/index.html.erb | 34 +- .../admin/billing_invoices/edit.html.erb | 28 +- .../admin/billing_invoices/index.html.erb | 43 +- .../views/admin/billing_invoices/new.html.erb | 18 +- .../admin/club_recordings/index.html.erb | 2 +- .../views/admin/clubs/_comped_form.html.erb | 28 +- backend/app/views/admin/clubs/index.html.erb | 27 +- backend/app/views/admin/clubs/show.html.erb | 28 +- .../app/views/admin/dashboard/index.html.erb | 86 +- backend/app/views/admin/ops/index.html.erb | 38 +- .../app/views/admin/passwords/edit.html.erb | 12 +- .../app/views/admin/sessions/_links.html.erb | 18 +- .../app/views/admin/sessions/index.html.erb | 16 +- .../app/views/admin/sessions/show.html.erb | 24 +- backend/app/views/admin/teams/show.html.erb | 24 +- .../admin/youtube/platform_token.html.erb | 21 +- .../invoice_mailer/invoice_pdf.html.erb | 13 +- .../invoice_mailer/invoice_pdf.text.erb | 15 +- backend/app/views/layouts/admin.html.erb | 21 +- backend/app/views/layouts/marketing.html.erb | 2 +- .../app/views/layouts/marketing_live.html.erb | 2 +- backend/app/views/layouts/public.html.erb | 14 +- backend/app/views/layouts/regia.html.erb | 4 +- .../public/club_billing/profile.html.erb | 14 +- .../views/public/club_matches/new.html.erb | 14 +- .../public/club_recordings/index.html.erb | 2 +- .../app/views/public/clubs/_youtube.html.erb | 34 +- .../app/views/public/clubs/billing.html.erb | 13 +- backend/app/views/public/clubs/edit.html.erb | 14 +- backend/app/views/public/clubs/new.html.erb | 40 +- backend/app/views/public/clubs/show.html.erb | 46 +- .../views/public/invitations/show.html.erb | 25 +- .../public/live/_player_overlays.html.erb | 2 +- backend/app/views/public/live/index.html.erb | 102 +-- backend/app/views/public/live/show.html.erb | 58 +- .../public/matches/_schedule_fields.html.erb | 12 +- .../app/views/public/matches/edit.html.erb | 8 +- .../app/views/public/matches/index.html.erb | 34 +- backend/app/views/public/matches/new.html.erb | 8 +- .../app/views/public/pages/cookies.html.erb | 134 ++- backend/app/views/public/pages/faq.html.erb | 60 +- .../app/views/public/pages/features.html.erb | 54 +- .../app/views/public/pages/pallavolo.html.erb | 57 +- .../app/views/public/pages/pricing.html.erb | 36 +- .../app/views/public/pages/privacy.html.erb | 210 ++--- backend/app/views/public/pages/terms.html.erb | 126 ++- .../public/password_resets/edit.html.erb | 12 +- .../views/public/password_resets/new.html.erb | 12 +- .../app/views/public/regia/_basket.html.erb | 2 +- .../app/views/public/regia/_generic.html.erb | 2 +- .../app/views/public/regia/_timed.html.erb | 2 +- .../app/views/public/regia/_timer.html.erb | 6 +- .../app/views/public/regia/_volley.html.erb | 6 +- backend/app/views/public/regia/show.html.erb | 31 +- .../views/public/registrations/new.html.erb | 31 +- .../app/views/public/replay/index.html.erb | 36 +- backend/app/views/public/replay/show.html.erb | 42 +- .../app/views/public/sessions/new.html.erb | 16 +- .../views/public/team_pages/index.html.erb | 55 +- .../app/views/public/team_pages/show.html.erb | 44 +- .../public/team_roster_members/edit.html.erb | 4 +- .../public/teams/_streaming_staff.html.erb | 41 +- .../app/views/public/teams/billing.html.erb | 30 +- .../app/views/public/teams/details.html.erb | 47 +- backend/app/views/public/teams/edit.html.erb | 35 +- .../app/views/public/teams/invite.html.erb | 26 +- backend/app/views/public/teams/new.html.erb | 18 +- .../views/recordings/_club_archive.html.erb | 81 +- .../replay_expiring_soon.html.erb | 12 +- .../replay_expiring_soon.text.erb | 8 +- .../replay_mailer/replay_ready.html.erb | 12 +- .../replay_mailer/replay_ready.text.erb | 10 +- .../views/shared/_billing_documents.html.erb | 32 +- .../shared/_billing_profile_fields.html.erb | 48 +- .../views/shared/_branding_fields.html.erb | 28 +- .../shared/_club_subscription_status.html.erb | 28 +- .../views/shared/_language_switcher.html.erb | 4 +- backend/app/views/shared/_plan_cards.html.erb | 30 +- .../views/shared/_plan_change_info.html.erb | 4 +- .../views/shared/_roster_member_form.html.erb | 30 +- .../views/shared/_roster_person_card.html.erb | 6 +- .../shared/_stripe_secure_payment.html.erb | 5 +- .../shared/_subscription_cancel.html.erb | 21 +- .../views/user_mailer/password_reset.html.erb | 10 +- .../views/user_mailer/password_reset.text.erb | 10 +- backend/config/locales/admin.de.yml | 311 +++++++ backend/config/locales/admin.en.yml | 311 +++++++ backend/config/locales/admin.es.yml | 311 +++++++ backend/config/locales/admin.fr.yml | 311 +++++++ backend/config/locales/admin.it.yml | 311 +++++++ backend/config/locales/app.de.yml | 786 ++++++++++++++++++ backend/config/locales/app.en.yml | 786 ++++++++++++++++++ backend/config/locales/app.es.yml | 786 ++++++++++++++++++ backend/config/locales/app.fr.yml | 786 ++++++++++++++++++ backend/config/locales/app.it.yml | 786 ++++++++++++++++++ backend/config/locales/legal.de.yml | 197 +++++ backend/config/locales/legal.en.yml | 197 +++++ backend/config/locales/legal.es.yml | 197 +++++ backend/config/locales/legal.fr.yml | 197 +++++ backend/config/locales/legal.it.yml | 197 +++++ backend/config/locales/mailers.de.yml | 43 + backend/config/locales/mailers.en.yml | 43 + backend/config/locales/mailers.es.yml | 43 + backend/config/locales/mailers.fr.yml | 43 + backend/config/locales/mailers.it.yml | 43 + backend/config/locales/pages.de.yml | 122 +++ backend/config/locales/pages.en.yml | 122 +++ backend/config/locales/pages.es.yml | 122 +++ backend/config/locales/pages.fr.yml | 122 +++ backend/config/locales/pages.it.yml | 122 +++ backend/config/locales/ui.de.yml | 75 +- backend/config/locales/ui.en.yml | 75 +- backend/config/locales/ui.es.yml | 75 +- backend/config/locales/ui.fr.yml | 75 +- backend/config/locales/ui.it.yml | 75 +- backend/config/routes.rb | 1 + backend/public/confirm-forms.js | 119 ++- backend/public/regia.js | 151 +++- .../spec/requests/public/i18n_pages_spec.rb | 26 + 158 files changed, 9444 insertions(+), 1536 deletions(-) create mode 100644 backend/app/controllers/admin/locales_controller.rb create mode 100644 backend/config/locales/admin.de.yml create mode 100644 backend/config/locales/admin.en.yml create mode 100644 backend/config/locales/admin.es.yml create mode 100644 backend/config/locales/admin.fr.yml create mode 100644 backend/config/locales/admin.it.yml create mode 100644 backend/config/locales/app.de.yml create mode 100644 backend/config/locales/app.en.yml create mode 100644 backend/config/locales/app.es.yml create mode 100644 backend/config/locales/app.fr.yml create mode 100644 backend/config/locales/app.it.yml create mode 100644 backend/config/locales/legal.de.yml create mode 100644 backend/config/locales/legal.en.yml create mode 100644 backend/config/locales/legal.es.yml create mode 100644 backend/config/locales/legal.fr.yml create mode 100644 backend/config/locales/legal.it.yml create mode 100644 backend/config/locales/mailers.de.yml create mode 100644 backend/config/locales/mailers.en.yml create mode 100644 backend/config/locales/mailers.es.yml create mode 100644 backend/config/locales/mailers.fr.yml create mode 100644 backend/config/locales/mailers.it.yml create mode 100644 backend/config/locales/pages.de.yml create mode 100644 backend/config/locales/pages.en.yml create mode 100644 backend/config/locales/pages.es.yml create mode 100644 backend/config/locales/pages.fr.yml create mode 100644 backend/config/locales/pages.it.yml create mode 100644 backend/spec/requests/public/i18n_pages_spec.rb diff --git a/backend/app/controllers/admin/auth_controller.rb b/backend/app/controllers/admin/auth_controller.rb index 3dc8b6c..4ba21b1 100644 --- a/backend/app/controllers/admin/auth_controller.rb +++ b/backend/app/controllers/admin/auth_controller.rb @@ -10,16 +10,16 @@ module Admin account = AdminAccount.find_by(username: params[:username]&.strip) if account&.authenticate(params[:password]) session[:admin_account_id] = account.id - redirect_to session.delete(:admin_return_to) || admin_root_path, notice: "Accesso effettuato" + redirect_to session.delete(:admin_return_to) || admin_root_path, notice: t("admin.flash.login_success") else - flash.now[:alert] = "Username o password non validi" + flash.now[:alert] = t("admin.flash.invalid_credentials") render :new, status: :unauthorized end end def destroy reset_session - redirect_to admin_login_path, notice: "Disconnesso" + redirect_to admin_login_path, notice: t("admin.flash.logout_success") end end end diff --git a/backend/app/controllers/admin/base_controller.rb b/backend/app/controllers/admin/base_controller.rb index 0d63075..737a817 100644 --- a/backend/app/controllers/admin/base_controller.rb +++ b/backend/app/controllers/admin/base_controller.rb @@ -3,20 +3,40 @@ module Admin layout "admin" protect_from_forgery with: :exception + before_action :set_admin_locale before_action :require_admin_login include ::AdminHelper helper ApplicationHelper helper RecordingsArchiveHelper - helper_method :current_admin_account, :admin_logged_in? + helper_method :current_admin_account, :admin_logged_in?, :current_locale, :language_options, :current_language_option private + def set_admin_locale + I18n.locale = LocaleResolver.resolve( + cookie_jar: request.cookie_jar, + accept_language: request.get_header("HTTP_ACCEPT_LANGUAGE") + ) + end + + def current_locale + I18n.locale + end + + def language_options + LocaleResolver.language_options + end + + def current_language_option + LocaleResolver.current_language_option + end + def require_admin_login return if admin_logged_in? session[:admin_return_to] = request.fullpath unless request.path == admin_login_path - redirect_to admin_login_path, alert: "Accedi per continuare" + redirect_to admin_login_path, alert: t("admin.flash.login_required") end def current_admin_account diff --git a/backend/app/controllers/admin/billing_controller.rb b/backend/app/controllers/admin/billing_controller.rb index 727c0a0..0903165 100644 --- a/backend/app/controllers/admin/billing_controller.rb +++ b/backend/app/controllers/admin/billing_controller.rb @@ -11,7 +11,7 @@ module Admin payment = Billing::Payment.find(params[:payment_id]) Billing::AttachPaymentInvoice.call(payment: payment, pdf: params[:pdf]) redirect_to admin_billing_path(billing_redirect_params(payment)), - notice: "Fattura caricata e inviata a #{payment.club.billing_email}." + notice: t("admin.flash.invoice_uploaded", email: payment.club.billing_email) rescue Billing::AttachPaymentInvoice::Error, Billing::IssueInvoice::Error => e payment ||= Billing::Payment.find_by(id: params[:payment_id]) redirect_to admin_billing_path(payment ? billing_redirect_params(payment) : {}), diff --git a/backend/app/controllers/admin/billing_invoices_controller.rb b/backend/app/controllers/admin/billing_invoices_controller.rb index 185fca1..de0c255 100644 --- a/backend/app/controllers/admin/billing_invoices_controller.rb +++ b/backend/app/controllers/admin/billing_invoices_controller.rb @@ -8,7 +8,7 @@ module Admin end def new - redirect_to admin_billing_path(club_id: @club.id), alert: "Carica il PDF dalla lista «Pagamenti da fatturare»." + redirect_to admin_billing_path(club_id: @club.id), alert: t("admin.flash.invoice_upload_hint") end def create @@ -18,7 +18,7 @@ module Admin if @invoice.save redirect_to edit_admin_club_billing_invoice_path(@club, @invoice), - notice: "Bozza creata. Carica il PDF dalla pagina Fatturazione o qui sotto." + notice: t("admin.flash.invoice_draft_created") else @payment = @invoice.billing_payment flash.now[:alert] = @invoice.errors.full_messages.join(", ") @@ -36,9 +36,9 @@ module Admin if issuing? Billing::IssueInvoice.call(invoice: @invoice, pdf: params.dig(:billing_invoice, :pdf)) redirect_to admin_billing_path(club_id: @club.id), - notice: "Fattura #{@invoice.number} emessa e inviata a #{@club.billing_email}." + notice: t("admin.flash.invoice_issued", number: @invoice.number, email: @club.billing_email) elsif @invoice.save - redirect_to admin_billing_path(club_id: @club.id), notice: "Fattura #{@invoice.number} aggiornata." + redirect_to admin_billing_path(club_id: @club.id), notice: t("admin.flash.invoice_updated", number: @invoice.number) else @payment = @invoice.billing_payment flash.now[:alert] = @invoice.errors.full_messages.join(", ") @@ -61,7 +61,7 @@ module Admin end def issuing? - params[:commit].to_s == "Emetti e invia via email" + params[:commit_action].to_s == "issue" end def invoice_params diff --git a/backend/app/controllers/admin/clubs_controller.rb b/backend/app/controllers/admin/clubs_controller.rb index 09c8d52..7bb4d8c 100644 --- a/backend/app/controllers/admin/clubs_controller.rb +++ b/backend/app/controllers/admin/clubs_controller.rb @@ -20,14 +20,14 @@ module Admin reason: params[:reason], admin: current_admin_account ) - redirect_back_or_club notice: "Abbonamento omaggio #{Plan[params[:plan_slug]].name} attivato per #{@club.name}." + redirect_back_or_club notice: t("admin.flash.comped_granted", plan: Plan[params[:plan_slug]].name, club: @club.name) rescue Billing::AdminCompedSubscription::Error, ActiveRecord::RecordInvalid => e redirect_back_or_club alert: e.message end def revoke_comped Billing::AdminCompedSubscription.revoke(club: @club, admin: current_admin_account) - redirect_back_or_club notice: "Abbonamento omaggio revocato per #{@club.name}." + redirect_back_or_club notice: t("admin.flash.comped_revoked", club: @club.name) rescue Billing::AdminCompedSubscription::Error => e redirect_back_or_club alert: e.message end diff --git a/backend/app/controllers/admin/locales_controller.rb b/backend/app/controllers/admin/locales_controller.rb new file mode 100644 index 0000000..0220494 --- /dev/null +++ b/backend/app/controllers/admin/locales_controller.rb @@ -0,0 +1,11 @@ +module Admin + class LocalesController < BaseController + skip_before_action :require_admin_login + + def update + locale = LocaleResolver.persist!(cookies, params[:locale]) + I18n.locale = locale + redirect_back fallback_location: admin_root_path + end + end +end diff --git a/backend/app/controllers/admin/ops_controller.rb b/backend/app/controllers/admin/ops_controller.rb index 219c7fe..b065faa 100644 --- a/backend/app/controllers/admin/ops_controller.rb +++ b/backend/app/controllers/admin/ops_controller.rb @@ -9,19 +9,19 @@ module Admin def acknowledge incident = Ops::Incident.find(params[:id]) incident.acknowledge! - redirect_to admin_ops_path, notice: "Incidente preso in carico" + redirect_to admin_ops_path, notice: t("admin.flash.ops_acknowledged") end def resolve incident = Ops::Incident.find(params[:id]) incident.resolve! - redirect_to admin_ops_path, notice: "Incidente risolto" + redirect_to admin_ops_path, notice: t("admin.flash.ops_resolved") end def mute incident = Ops::Incident.find(params[:id]) incident.mute!(duration: 24.hours) - redirect_to admin_ops_path, notice: "Notifiche sospese per 24 ore" + redirect_to admin_ops_path, notice: t("admin.flash.ops_muted") end private diff --git a/backend/app/controllers/admin/passwords_controller.rb b/backend/app/controllers/admin/passwords_controller.rb index 4d73f39..60b34d3 100644 --- a/backend/app/controllers/admin/passwords_controller.rb +++ b/backend/app/controllers/admin/passwords_controller.rb @@ -5,22 +5,22 @@ module Admin def update unless current_admin_account.authenticate(params[:current_password]) - flash.now[:alert] = "Password attuale non corretta" + flash.now[:alert] = t("admin.flash.password_current_incorrect") return render :edit, status: :unprocessable_entity end if params[:password].blank? || params[:password].length < 8 - flash.now[:alert] = "La nuova password deve avere almeno 8 caratteri" + flash.now[:alert] = t("admin.flash.password_too_short") return render :edit, status: :unprocessable_entity end if params[:password] != params[:password_confirmation] - flash.now[:alert] = "Le password non coincidono" + flash.now[:alert] = t("admin.flash.password_mismatch") return render :edit, status: :unprocessable_entity end if current_admin_account.update(password: params[:password]) - redirect_to admin_root_path, notice: "Password aggiornata" + redirect_to admin_root_path, notice: t("admin.flash.password_updated") else flash.now[:alert] = current_admin_account.errors.full_messages.to_sentence render :edit, status: :unprocessable_entity diff --git a/backend/app/controllers/admin/sessions_controller.rb b/backend/app/controllers/admin/sessions_controller.rb index 2f1704f..16410c6 100644 --- a/backend/app/controllers/admin/sessions_controller.rb +++ b/backend/app/controllers/admin/sessions_controller.rb @@ -12,28 +12,28 @@ module Admin def stop @session = StreamSession.find(params[:id]) if @session.terminal? - redirect_to admin_session_path(@session), alert: "Sessione già terminata (#{@session.status})." + redirect_to admin_session_path(@session), alert: t("admin.flash.session_already_terminated", status: @session.status) return end Sessions::Stop.new(@session).call - redirect_to admin_session_path(@session), notice: "Sessione terminata." + redirect_to admin_session_path(@session), notice: t("admin.flash.session_stopped") rescue StandardError => e Rails.logger.error("[admin] stop session #{@session.id}: #{e.class} #{e.message}") - redirect_to admin_session_path(@session), alert: "Errore durante la chiusura: #{e.message}" + redirect_to admin_session_path(@session), alert: t("admin.flash.session_stop_error", error: e.message) end def regia_link @session = StreamSession.find(params[:id]) if @session.terminal? - redirect_to admin_session_path(@session), alert: "Sessione terminata: impossibile generare il link regia." + redirect_to admin_session_path(@session), alert: t("admin.flash.session_terminated_no_regia") return end access = Sessions::RegiaAccess.new(@session) token = access.issue_token! redirect_to admin_session_path(@session), - notice: "Link regia generato.", + notice: t("admin.flash.session_regia_generated"), flash: { regia_url: access.url(token), regia_expires_at: @session.regia_token_expires_at&.iso8601 diff --git a/backend/app/controllers/admin/youtube_controller.rb b/backend/app/controllers/admin/youtube_controller.rb index c76e14e..d40b172 100644 --- a/backend/app/controllers/admin/youtube_controller.rb +++ b/backend/app/controllers/admin/youtube_controller.rb @@ -2,7 +2,7 @@ module Admin class YoutubeController < Admin::BaseController def platform if ENV["YOUTUBE_CLIENT_ID"].blank? - redirect_to admin_root_path, alert: "Configura YOUTUBE_CLIENT_ID e YOUTUBE_CLIENT_SECRET in .env" + redirect_to admin_root_path, alert: t("admin.flash.youtube_not_configured") return end diff --git a/backend/app/controllers/public/club_billing_controller.rb b/backend/app/controllers/public/club_billing_controller.rb index 802e35d..4ea6f84 100644 --- a/backend/app/controllers/public/club_billing_controller.rb +++ b/backend/app/controllers/public/club_billing_controller.rb @@ -15,9 +15,9 @@ module Public @club, plan: premium_checkout_return_params[:plan], interval: premium_checkout_return_params[:interval] - ), notice: "Dati salvati. Procedi con il pagamento." + ), notice: t("flash.club_billing.profile_saved_proceed_payment") else - redirect_to public_club_billing_path(@club), notice: "Dati di fatturazione aggiornati." + redirect_to public_club_billing_path(@club), notice: t("flash.club_billing.profile_updated") end else flash.now[:alert] = @club.errors.full_messages.join(", ") @@ -27,25 +27,25 @@ module Public def cancel_subscription unless MatchLiveTv.stripe_enabled? - redirect_to public_club_billing_path(@club), alert: "Stripe non configurato." + redirect_to public_club_billing_path(@club), alert: t("flash.club_billing.stripe_not_configured") return end Billing::Stripe::CancelSubscription.call(club: @club) sub = @club.subscription.reload - date = sub.current_period_end ? helpers.l_local(sub.current_period_end.to_date) : "fine periodo" + date = sub.current_period_end ? helpers.l_local(sub.current_period_end.to_date) : t("billing.subscription_status.end_of_period") redirect_to public_club_billing_path(@club), - notice: "Abbonamento disdetto. Resta attivo fino al #{date}; da quel giorno passerai al piano Free." + notice: t("flash.club_billing.subscription_canceled", date: date) rescue ArgumentError, RuntimeError => e redirect_to public_club_billing_path(@club), alert: e.message rescue ::Stripe::StripeError => e - redirect_to public_club_billing_path(@club), alert: "Errore Stripe: #{e.message}" + redirect_to public_club_billing_path(@club), alert: t("flash.club_billing.stripe_error", message: e.message) end def download_invoice invoice = @club.billing_invoices.find(params[:invoice_id]) unless invoice.pdf.attached? - redirect_to public_club_billing_path(@club), alert: "PDF fattura non disponibile." + redirect_to public_club_billing_path(@club), alert: t("flash.club_billing.invoice_pdf_unavailable") return end diff --git a/backend/app/controllers/public/club_matches_controller.rb b/backend/app/controllers/public/club_matches_controller.rb index 804e8f8..1774914 100644 --- a/backend/app/controllers/public/club_matches_controller.rb +++ b/backend/app/controllers/public/club_matches_controller.rb @@ -14,7 +14,7 @@ module Public @selected_team = @schedulable_teams.find { |t| t.id.to_s == params[:team_id].to_s } unless @selected_team @match = default_match_for_form - flash.now[:alert] = "Seleziona una squadra valida." + flash.now[:alert] = t("flash.club_matches.invalid_team") return render :new, status: :unprocessable_entity end @@ -22,7 +22,7 @@ module Public match_params.merge(sport: @selected_team.sport, sets_to_win: 3) ) redirect_to public_live_index_path(club_id: @club.id), - notice: "Partita programmata: #{@selected_team.name} vs #{@match.opponent_name}." + notice: t("flash.club_matches.scheduled", team: @selected_team.name, opponent: @match.opponent_name) rescue ActiveRecord::RecordInvalid => e @match = @selected_team.matches.build(match_params) flash.now[:alert] = e.record.errors.full_messages.join(", ") @@ -44,7 +44,7 @@ module Public return if @schedulable_teams.any? redirect_to public_live_index_path(club_id: @club.id), - alert: "Non hai permessi per programmare partite in questa società." + alert: t("flash.club_matches.no_schedulable_teams") end def resolve_selected_team diff --git a/backend/app/controllers/public/club_recordings_controller.rb b/backend/app/controllers/public/club_recordings_controller.rb index 2a804f6..9c9012e 100644 --- a/backend/app/controllers/public/club_recordings_controller.rb +++ b/backend/app/controllers/public/club_recordings_controller.rb @@ -22,7 +22,7 @@ module Public return if current_user.owned_clubs.exists?(id: @club.id) return if current_user.manageable_teams.joins(:club).exists?(clubs: { id: @club.id }) - redirect_to public_clubs_path, alert: "Non autorizzato" + redirect_to public_clubs_path, alert: t("flash.club_recordings.not_authorized") end end end diff --git a/backend/app/controllers/public/clubs_controller.rb b/backend/app/controllers/public/clubs_controller.rb index 3a113a2..08a2093 100644 --- a/backend/app/controllers/public/clubs_controller.rb +++ b/backend/app/controllers/public/clubs_controller.rb @@ -12,7 +12,7 @@ module Public def create if current_user.owned_clubs.exists? - redirect_to public_club_path(current_user.primary_club), alert: "Hai già registrato una società" + redirect_to public_club_path(current_user.primary_club), alert: t("flash.clubs.already_registered") return end @@ -22,7 +22,7 @@ module Public ClubMembership.create!(user: current_user, club: club, role: "owner") - first_team_name = params.dig(:first_team, :name).presence || "Prima squadra" + first_team_name = params.dig(:first_team, :name).presence || t("club.new.default_first_team_name") club.teams.create!( name: first_team_name, sport: club.sport @@ -34,14 +34,14 @@ module Public if plan.in?(%w[premium_light premium_full]) && MatchLiveTv.stripe_enabled? unless club.billing_profile_complete? redirect_to public_club_billing_profile_path(club, plan: plan, interval: checkout_interval_param), - alert: "Completa i dati di fatturazione prima di attivare un piano premium." + alert: t("flash.clubs.complete_billing_first") return end interval = checkout_interval_param redirect_to public_club_checkout_path(club, plan: plan, interval: interval) else - redirect_to public_club_path(club), notice: "Società e prima squadra create." + redirect_to public_club_path(club), notice: t("flash.clubs.created") end rescue ActiveRecord::RecordInvalid => e flash.now[:alert] = e.record.errors.full_messages.join(", ") @@ -66,7 +66,7 @@ module Public @club.assign_attributes(club_params) attach_branding_logo(@club) @club.save! - redirect_to public_club_path(@club), notice: "Società aggiornata." + redirect_to public_club_path(@club), notice: t("flash.clubs.updated") rescue ActiveRecord::RecordInvalid => e flash.now[:alert] = e.record.errors.full_messages.join(", ") render :edit, status: :unprocessable_entity @@ -88,12 +88,12 @@ module Public require_club_owner!(@club) if @club.subscription&.admin_comped? redirect_to public_club_billing_path(@club), - alert: "Il piano è un abbonamento omaggio gestito da Match Live TV. Per passare a un abbonamento a pagamento contatta il supporto." + alert: t("flash.clubs.comped_change_denied") return end unless MatchLiveTv.stripe_enabled? - redirect_to public_club_billing_path(@club), alert: "Pagamenti non ancora configurati sul server" + redirect_to public_club_billing_path(@club), alert: t("flash.clubs.stripe_not_configured") return end @@ -105,15 +105,15 @@ module Public if sub&.stripe_subscription_id.present? && sub.active? && sub.plan.slug == plan_slug && sub.billing_interval == interval && !sub.plan_change_pending? redirect_to public_club_billing_path(@club), - notice: "Sei già su #{target_plan.name} (#{Billing::Stripe::PriceCatalog.label(plan_slug: plan_slug, interval: interval)})." + notice: t("flash.clubs.already_on_plan", plan: target_plan.name, price: Billing::Stripe::PriceCatalog.label(plan_slug: plan_slug, interval: interval)) return end if sub&.plan_change_pending? && sub.pending_plan&.slug == plan_slug && sub.pending_billing_interval == interval - when_label = sub.current_period_end.present? ? I18n.l(sub.current_period_end, format: :long) : "il prossimo rinnovo" + when_label = sub.current_period_end.present? ? I18n.l(sub.current_period_end, format: :long) : t("billing.messages.next_renewal") redirect_to public_club_billing_path(@club), - notice: "Il passaggio a #{target_plan.name} è già programmato dal #{when_label}." + notice: t("flash.clubs.change_already_scheduled", plan: target_plan.name, date: when_label) return end @@ -134,7 +134,7 @@ module Public redirect_to public_club_billing_path(@club), alert: e.message rescue ::Stripe::StripeError => e Rails.logger.warn("[Stripe checkout] #{e.message}") - redirect_to public_club_billing_path(@club), alert: "Errore Stripe: #{e.message}" + redirect_to public_club_billing_path(@club), alert: t("flash.clubs.stripe_error", message: e.message) end def youtube_connect @@ -143,7 +143,7 @@ module Public entitlements.assert_can_connect_youtube! if ENV["YOUTUBE_CLIENT_ID"].blank? - redirect_to public_club_path(@club), alert: "YouTube OAuth non configurato sul server" + redirect_to public_club_path(@club), alert: t("flash.clubs.youtube_oauth_not_configured") return end @@ -161,13 +161,13 @@ module Public def youtube_disconnect require_club_owner!(@club) @club.youtube_credential&.destroy! - redirect_to public_club_path(@club), notice: "Canale YouTube della società scollegato" + redirect_to public_club_path(@club), notice: t("flash.clubs.youtube_disconnected") end def portal require_club_owner!(@club) unless MatchLiveTv.stripe_enabled? - redirect_to public_club_billing_path(@club), alert: "Portale pagamenti non configurato" + redirect_to public_club_billing_path(@club), alert: t("flash.clubs.portal_not_configured") return end @@ -204,7 +204,7 @@ module Public @club, plan: plan_slug, interval: params[:interval].presence - ), alert: "Completa i dati di fatturazione prima di attivare un piano premium." + ), alert: t("flash.clubs.complete_billing_first") end def checkout_interval_param @@ -236,10 +236,10 @@ module Public def apply_checkout_flash! case params[:checkout] when "success" - plan_name = @subscription&.plan&.name || "premium" - flash.now[:notice] = "Pagamento completato! Piano attivo: #{plan_name}." + plan_name = @subscription&.plan&.name || "Premium" + flash.now[:notice] = t("flash.clubs.checkout_success", plan: plan_name) when "canceled" - flash.now[:alert] = "Pagamento annullato. Nessun addebito è stato effettuato." + flash.now[:alert] = t("flash.clubs.checkout_canceled") end end end diff --git a/backend/app/controllers/public/invitations_controller.rb b/backend/app/controllers/public/invitations_controller.rb index 066d5c3..fe822ee 100644 --- a/backend/app/controllers/public/invitations_controller.rb +++ b/backend/app/controllers/public/invitations_controller.rb @@ -4,24 +4,24 @@ module Public @token = params[:token] @invitation = TeamInvitation.pending.find_by(token_digest: Digest::SHA256.hexdigest(@token.to_s)) unless @invitation - redirect_to public_pricing_path, alert: "Invito non valido o scaduto" + redirect_to public_pricing_path, alert: t("flash.invitations.invalid_or_expired") end end def accept invitation = TeamInvitation.pending.find_by(token_digest: Digest::SHA256.hexdigest(params[:token].to_s)) - return redirect_to public_pricing_path, alert: "Invito non valido" unless invitation + return redirect_to public_pricing_path, alert: t("flash.invitations.invalid") unless invitation if logged_in? if current_user.email.downcase != invitation.email.downcase - redirect_to public_pricing_path, alert: "Questo invito è per #{invitation.email}" + redirect_to public_pricing_path, alert: t("flash.invitations.wrong_email", email: invitation.email) return end invitation.accept!(current_user) - redirect_to public_team_details_path(invitation.team), notice: "Sei entrato nella squadra!" + redirect_to public_team_details_path(invitation.team), notice: t("flash.invitations.joined_team") else session[:pending_invite_token] = params[:token] - redirect_to public_signup_path, notice: "Registrati con #{invitation.email} per accettare l'invito" + redirect_to public_signup_path, notice: t("flash.invitations.signup_to_accept", email: invitation.email) end end end diff --git a/backend/app/controllers/public/matches_controller.rb b/backend/app/controllers/public/matches_controller.rb index 7fa2396..23b8f6a 100644 --- a/backend/app/controllers/public/matches_controller.rb +++ b/backend/app/controllers/public/matches_controller.rb @@ -25,7 +25,7 @@ module Public match_params.merge(sport: @team.sport, sets_to_win: match_params[:sets_to_win].presence || 3) ) redirect_to public_team_matches_path(@team), - notice: "Partita programmata: #{@match.opponent_name}. Avvia lo streaming dall’app quando sei in palestra." + notice: t("flash.matches.scheduled", opponent: @match.opponent_name) rescue ActiveRecord::RecordInvalid => e @match = @team.matches.build(match_params) flash.now[:alert] = e.record.errors.full_messages.join(", ") @@ -37,7 +37,7 @@ module Public def update @match.update!(match_params) - redirect_to public_team_matches_path(@team), notice: "Partita aggiornata." + redirect_to public_team_matches_path(@team), notice: t("flash.matches.updated") rescue ActiveRecord::RecordInvalid => e flash.now[:alert] = e.record.errors.full_messages.join(", ") render :edit, status: :unprocessable_entity @@ -47,12 +47,12 @@ module Public active = active_session_for(@match) if active&.resumable? redirect_to public_team_matches_path(@team), - alert: "Chiudi la diretta prima di eliminare questa partita." + alert: t("flash.matches.close_live_before_delete") return end @match.destroy! - redirect_to public_team_matches_path(@team), notice: "Partita eliminata." + redirect_to public_team_matches_path(@team), notice: t("flash.matches.deleted") end private @@ -66,7 +66,7 @@ module Public return if current_user.can_schedule_for?(@team) redirect_to public_team_details_path(@team), - alert: "Solo un responsabile trasmissione o il titolare della società possono programmare le partite." + alert: t("flash.matches.schedule_denied") end def set_match diff --git a/backend/app/controllers/public/password_resets_controller.rb b/backend/app/controllers/public/password_resets_controller.rb index 9bc15af..6ed84ac 100644 --- a/backend/app/controllers/public/password_resets_controller.rb +++ b/backend/app/controllers/public/password_resets_controller.rb @@ -11,14 +11,14 @@ module Public end redirect_to public_login_path, - notice: "Se l'email è registrata, riceverai a breve un link per reimpostare la password." + notice: t("flash.password_resets.email_sent") end def edit @user = User.find_by_password_reset_token(params[:token]) if @user.nil? || @user.password_reset_expired? redirect_to new_public_password_reset_path, - alert: "Link non valido o scaduto. Richiedi un nuovo reset password." + alert: t("flash.password_resets.invalid_or_expired_link") return end @token = params[:token] @@ -28,25 +28,25 @@ module Public @user = User.find_by_password_reset_token(params[:token]) if @user.nil? || @user.password_reset_expired? redirect_to new_public_password_reset_path, - alert: "Link non valido o scaduto. Richiedi un nuovo reset password." + alert: t("flash.password_resets.invalid_or_expired_link") return end if params[:password].blank? || params[:password].length < 8 - flash.now[:alert] = "La password deve avere almeno 8 caratteri" + flash.now[:alert] = t("flash.password_resets.password_min_length") @token = params[:token] return render :edit, status: :unprocessable_entity end if params[:password] != params[:password_confirmation] - flash.now[:alert] = "Le password non coincidono" + flash.now[:alert] = t("flash.password_resets.password_mismatch") @token = params[:token] return render :edit, status: :unprocessable_entity end @user.update!(password: params[:password]) @user.clear_password_reset! - redirect_to public_login_path, notice: "Password aggiornata. Ora puoi accedere." + redirect_to public_login_path, notice: t("flash.password_resets.password_updated") end end end diff --git a/backend/app/controllers/public/registrations_controller.rb b/backend/app/controllers/public/registrations_controller.rb index 76cf638..8d22eb9 100644 --- a/backend/app/controllers/public/registrations_controller.rb +++ b/backend/app/controllers/public/registrations_controller.rb @@ -8,7 +8,7 @@ module Public def create unless params[:accept_terms] == "1" @user = User.new(user_params.merge(role: "coach")) - flash.now[:alert] = "Devi accettare l’informativa privacy e i termini di servizio per registrarti." + flash.now[:alert] = t("flash.registrations.accept_terms_required") return render :new, status: :unprocessable_entity end @@ -20,10 +20,10 @@ module Public invitation = TeamInvitation.pending.find_by(token_digest: Digest::SHA256.hexdigest(token)) if invitation && invitation.email.downcase == @user.email.downcase invitation.accept!(@user) - return redirect_to public_team_details_path(invitation.team), notice: "Benvenuto nella squadra!" + return redirect_to public_team_details_path(invitation.team), notice: t("flash.registrations.welcome_to_team") end end - redirect_to public_new_club_path, notice: "Account creato. Ora registra la tua società." + redirect_to public_new_club_path, notice: t("flash.registrations.account_created") else flash.now[:alert] = @user.errors.full_messages.join(", ") render :new, status: :unprocessable_entity diff --git a/backend/app/controllers/public/replay_controller.rb b/backend/app/controllers/public/replay_controller.rb index c417660..4a0aa2b 100644 --- a/backend/app/controllers/public/replay_controller.rb +++ b/backend/app/controllers/public/replay_controller.rb @@ -42,7 +42,7 @@ module Public def download unless download_allowed? - redirect_to public_replay_path(@session), alert: "Download non disponibile" + redirect_to public_replay_path(@session), alert: t("flash.replay.download_unavailable") return end @@ -105,13 +105,13 @@ module Public @recording = Recording.not_deleted.find_by(stream_session: @session) return if @recording - redirect_to public_live_path(@session), alert: "Replay non disponibile" + redirect_to public_live_path(@session), alert: t("flash.replay.not_available") end def authorize_replay_access! return if Recordings::AccessPolicy.new(@recording, viewer: current_user).allowed? - redirect_to public_live_index_path, alert: "Replay non disponibile" + redirect_to public_live_index_path, alert: t("flash.replay.not_available") end end end diff --git a/backend/app/controllers/public/sessions_controller.rb b/backend/app/controllers/public/sessions_controller.rb index 5c31cbc..e3955a7 100644 --- a/backend/app/controllers/public/sessions_controller.rb +++ b/backend/app/controllers/public/sessions_controller.rb @@ -17,16 +17,16 @@ module Public else public_new_club_path end - redirect_to dest, notice: "Bentornato!" + redirect_to dest, notice: t("flash.sessions.welcome_back") else - flash.now[:alert] = "Email o password non validi" + flash.now[:alert] = t("flash.sessions.invalid_credentials") render :new, status: :unauthorized end end def destroy reset_session - redirect_to public_pricing_path, notice: "Disconnesso" + redirect_to public_pricing_path, notice: t("flash.sessions.logged_out") end end end diff --git a/backend/app/controllers/public/team_roster_members_controller.rb b/backend/app/controllers/public/team_roster_members_controller.rb index f87a902..cfcec5d 100644 --- a/backend/app/controllers/public/team_roster_members_controller.rb +++ b/backend/app/controllers/public/team_roster_members_controller.rb @@ -9,7 +9,7 @@ module Public member = @team.roster_members.build(member_params) attach_photo(member) member.save! - redirect_to public_team_details_path(@team), notice: "#{member.full_name} aggiunto all'organico." + redirect_to public_team_details_path(@team), notice: t("flash.team_roster_members.added", name: member.full_name) rescue ActiveRecord::RecordInvalid => e redirect_to public_team_details_path(@team), alert: e.record.errors.full_messages.join(", ") end @@ -22,7 +22,7 @@ module Public @member.assign_attributes(member_params) attach_photo(@member) @member.save! - redirect_to public_team_details_path(@team), notice: "Scheda aggiornata." + redirect_to public_team_details_path(@team), notice: t("flash.team_roster_members.updated") rescue ActiveRecord::RecordInvalid => e @club = @team.club flash.now[:alert] = e.record.errors.full_messages.join(", ") @@ -32,7 +32,7 @@ module Public def destroy name = @member.full_name @member.destroy! - redirect_to public_team_details_path(@team), notice: "#{name} rimosso dall'organico." + redirect_to public_team_details_path(@team), notice: t("flash.team_roster_members.removed", name: name) end private diff --git a/backend/app/controllers/public/teams_controller.rb b/backend/app/controllers/public/teams_controller.rb index bb2ee0f..c9b3a5f 100644 --- a/backend/app/controllers/public/teams_controller.rb +++ b/backend/app/controllers/public/teams_controller.rb @@ -19,7 +19,7 @@ module Public require_club_owner!(@club) team = @club.teams.create!(team_params) attach_branding_logo(team) - redirect_to public_club_path(@club), notice: "Squadra «#{team.name}» aggiunta." + redirect_to public_club_path(@club), notice: t("flash.teams.added", name: team.name) rescue ActiveRecord::RecordInvalid => e flash.now[:alert] = e.record.errors.full_messages.join(", ") render :new, status: :unprocessable_entity @@ -49,7 +49,7 @@ module Public attach_branding_logo(@team) attach_team_photo(@team) @team.save! - redirect_to public_team_details_path(@team), notice: "Squadra aggiornata." + redirect_to public_team_details_path(@team), notice: t("flash.teams.updated") rescue ActiveRecord::RecordInvalid => e @club = @team.club flash.now[:alert] = e.record.errors.full_messages.join(", ") @@ -66,7 +66,7 @@ module Public membership = current_user.user_teams.find_or_initialize_by(team: @team) membership.role = "member" if membership.new_record? Teams::StaffAssignment.call(team: @team, user: current_user, membership: membership) - redirect_to public_team_details_path(@team), notice: "Il tuo account è ora responsabile trasmissione." + redirect_to public_team_details_path(@team), notice: t("flash.teams.now_staff") rescue Teams::StaffAssignmentError, Teams::EntitlementError => e redirect_to public_team_details_path(@team), alert: e.message end @@ -75,7 +75,7 @@ module Public require_club_owner_for_team!(@team) membership = current_user.user_teams.find_by(team: @team) membership&.update!(staff_kind: nil) - redirect_to public_team_details_path(@team), notice: "Ruolo staff rimosso dal tuo account." + redirect_to public_team_details_path(@team), notice: t("flash.teams.staff_role_removed") end def create_invitation @@ -95,7 +95,7 @@ module Public expires_at: 7.days.from_now ) @invite_url = join_public_invitation_url(token: token) - flash.now[:notice] = "Link invito generato (valido 7 giorni)" + flash.now[:notice] = t("flash.teams.invite_link_generated") render :invite rescue Teams::EntitlementError, Teams::StaffAssignmentError => e redirect_to public_team_invite_path(@team), alert: e.message @@ -105,14 +105,14 @@ module Public require_club_owner_for_team!(@team) ut = @team.user_teams.find_by!(user_id: params[:user_id], role: "member") ut.destroy! - redirect_to public_team_details_path(@team), notice: "Accesso revocato" + redirect_to public_team_details_path(@team), notice: t("flash.teams.access_revoked") end def destroy_invitation require_club_owner_for_team!(@team) inv = @team.team_invitations.pending.find(params[:invitation_id]) inv.destroy! - redirect_to public_team_details_path(@team), notice: "Invito annullato" + redirect_to public_team_details_path(@team), notice: t("flash.teams.invitation_canceled") end def youtube_connect @@ -125,7 +125,7 @@ module Public def youtube_disconnect require_club_owner_for_team!(@team) @team.club.youtube_credential&.destroy! - redirect_to public_club_path(@team.club), notice: "Canale YouTube della società scollegato" + redirect_to public_club_path(@team.club), notice: t("flash.teams.youtube_disconnected") end private diff --git a/backend/app/controllers/public/web_base_controller.rb b/backend/app/controllers/public/web_base_controller.rb index 5806c61..1751ede 100644 --- a/backend/app/controllers/public/web_base_controller.rb +++ b/backend/app/controllers/public/web_base_controller.rb @@ -7,15 +7,15 @@ module Public def require_login! return if logged_in? - redirect_to public_login_path, alert: "Accedi per continuare" + redirect_to public_login_path, alert: t("flash.web_base.login_required") end def require_club_owner!(club) - redirect_to public_prezzi_path, alert: "Accesso non consentito" unless club.owned_by?(current_user) + redirect_to public_prezzi_path, alert: t("flash.web_base.access_denied") unless club.owned_by?(current_user) end def require_club_owner_for_team!(team) - redirect_to public_prezzi_path, alert: "Accesso non consentito" unless team.club&.owned_by?(current_user) + redirect_to public_prezzi_path, alert: t("flash.web_base.access_denied") unless team.club&.owned_by?(current_user) end end end diff --git a/backend/app/helpers/admin_helper.rb b/backend/app/helpers/admin_helper.rb index e0a799a..95d4ddd 100644 --- a/backend/app/helpers/admin_helper.rb +++ b/backend/app/helpers/admin_helper.rb @@ -23,7 +23,7 @@ module AdminHelper def admin_session_watch_links(session) links = [] if session.matchlivetv_platform? - links << { label: "Pagina live", url: session.watch_page_url } + links << { label: I18n.t("admin.common.live_page"), url: session.watch_page_url } end youtube = session.youtube_watch_url links << { label: "YouTube", url: youtube } if youtube.present? diff --git a/backend/app/helpers/application_helper.rb b/backend/app/helpers/application_helper.rb index 02e7150..af292d4 100644 --- a/backend/app/helpers/application_helper.rb +++ b/backend/app/helpers/application_helper.rb @@ -31,6 +31,34 @@ module ApplicationHelper I18n.locale.to_s end + def confirm_dialog_i18n_json + { + eyebrow: t("common.confirm_eyebrow"), + title: t("common.confirm_title"), + deleteTitle: t("common.delete_title"), + confirmOk: t("common.confirm_ok"), + deleteOk: t("common.delete_ok"), + cancel: t("common.cancel"), + multiDeleteTitle: t("common.multi_delete_title"), + multiDeleteMessage: t("common.multi_delete_message"), + deleteYoutubeTitle: t("common.delete_youtube_title"), + deleteYoutubeMessage: t("common.delete_youtube_message"), + deleteYoutubeOk: t("common.delete_youtube_ok"), + deleteSiteTitle: t("common.delete_site_title"), + deleteSiteMessageWithYoutube: t("common.delete_site_message_with_youtube"), + deleteSiteMessageOnly: t("common.delete_site_message_only"), + deleteSiteOk: t("common.delete_site_ok"), + choiceSiteLabel: t("common.choice_site_label"), + choiceSiteHintWithYoutube: t("common.choice_site_hint_with_youtube"), + choiceSiteHintOnly: t("common.choice_site_hint_only"), + choiceYoutubeLabel: t("common.choice_youtube_label"), + choiceYoutubeHintWithSite: t("common.choice_youtube_hint_with_site"), + choiceYoutubeHintOnly: t("common.choice_youtube_hint_only"), + choiceBothLabel: t("common.choice_both_label"), + choiceBothHint: t("common.choice_both_hint") + }.to_json + end + def sport_catalog_options(selected = nil) options = Sports::Catalog.as_api_list.map { |entry| [entry[:label], entry[:key]] } options_for_select(options, Sports::Catalog.normalize_key(selected)) diff --git a/backend/app/helpers/public/billing_helper.rb b/backend/app/helpers/public/billing_helper.rb index 96e1479..f1f9d3e 100644 --- a/backend/app/helpers/public/billing_helper.rb +++ b/backend/app/helpers/public/billing_helper.rb @@ -2,25 +2,25 @@ module Public module BillingHelper def plan_billing_action(current_slug:, target_plan:, stripe_subscription_active:, current_interval: nil, subscription: nil) if target_plan.slug == "free" - return { kind: :current, label: "Piano attuale" } if current_slug == "free" + return { kind: :current, label: I18n.t("billing.actions.current_plan") } if current_slug == "free" return { kind: :none } if stripe_subscription_active - return { kind: :contact, label: "Per passare al piano Free, contatta il supporto." } + return { kind: :contact, label: I18n.t("billing.actions.contact_for_free") } end unless MatchLiveTv.stripe_enabled? - return { kind: :disabled, label: "Stripe non configurato" } + return { kind: :disabled, label: I18n.t("billing.actions.stripe_not_configured") } end intervals = Billing::Stripe::PriceCatalog.available_intervals(plan_slug: target_plan.slug) - return { kind: :disabled, label: "Prezzi Stripe non configurati" } if intervals.empty? + return { kind: :disabled, label: I18n.t("billing.actions.stripe_prices_not_configured") } if intervals.empty? active_interval = current_interval.presence || Billing::Stripe::PriceCatalog::DEFAULT_INTERVAL if current_slug == target_plan.slug && stripe_subscription_active && !subscription&.plan_change_pending? other_intervals = intervals - [active_interval] if other_intervals.empty? - label = "Piano attuale — #{Billing::Stripe::PriceCatalog.label(plan_slug: target_plan.slug, interval: active_interval)}" + label = "#{I18n.t('billing.actions.current_plan')} — #{Billing::Stripe::PriceCatalog.label(plan_slug: target_plan.slug, interval: active_interval)}" return { kind: :current, label: label } end @@ -87,9 +87,9 @@ module Public def billing_profile_incomplete_message(club) missing = club.billing_profile_errors - return "Completa i dati di fatturazione prima di attivare un piano premium." if missing.empty? + return I18n.t("billing.actions.profile_incomplete_default") if missing.empty? - "Mancano: #{missing.join(", ")}." + I18n.t("billing.actions.profile_missing", fields: missing.join(", ")) end end diff --git a/backend/app/helpers/public/live_helper.rb b/backend/app/helpers/public/live_helper.rb index 19c0e82..6980724 100644 --- a/backend/app/helpers/public/live_helper.rb +++ b/backend/app/helpers/public/live_helper.rb @@ -8,16 +8,16 @@ module Public when "basket", "timed" live_score_period_label(match, score_state) when "timer" - "Cronometro" + t("score.timer_label") when "generic" - "Punteggio" + t("score.generic_label") else - "Set #{score_state.current_set} · Set vinti #{score_state.home_sets}-#{score_state.away_sets}" + t("score.sets_label", set: score_state.current_set, home: score_state.home_sets, away: score_state.away_sets) end end def live_score_period_label(match, score_state) - return "—" unless score_state + return t("score.fallback") unless score_state score_state.current_period_label end @@ -35,16 +35,16 @@ module Public def live_scheduled_label(datetime) return nil unless datetime - datetime.in_time_zone.strftime("%d/%m/%Y alle %H:%M") + datetime.in_time_zone.strftime(t("score.scheduled_date_format")) end def live_scheduled_relative(datetime) return nil unless datetime if datetime.to_date == Time.zone.today - "Oggi alle #{datetime.strftime('%H:%M')}" + t("score.scheduled_today", time: datetime.strftime("%H:%M")) elsif datetime.to_date == Time.zone.tomorrow - "Domani alle #{datetime.strftime('%H:%M')}" + t("score.scheduled_tomorrow", time: datetime.strftime("%H:%M")) else live_scheduled_label(datetime) end @@ -52,7 +52,7 @@ module Public def live_match_card_heading(match, link_team: true) team = match.team - club_name = team.club&.name.presence || "Società" + club_name = team.club&.name.presence || t("score.default_club_name") team_slug = team.respond_to?(:slug) ? team.slug : nil team_label = if link_team && team_slug.present? link_to(team.name, public_team_page_path(team_slug), class: "live-card__team-link") @@ -69,7 +69,7 @@ module Public def live_match_page_heading(match) team = match.team - club_name = team.club&.name.presence || "Società" + club_name = team.club&.name.presence || t("score.default_club_name") content_tag(:div, class: "live-page-heading") do safe_join([ content_tag(:p, club_name, class: "live-page-heading__club"), @@ -79,7 +79,7 @@ module Public end def live_score_points_label(match, score_state) - return "—" unless score_state + return t("score.fallback") unless score_state board = match.effective_board_type home = case board @@ -91,7 +91,7 @@ module Public else score_state.away_points end - "#{match.team.name} #{home} - #{away} #{match.opponent_name}" + t("score.points_label", team: match.team.name, home: home, away: away, opponent: match.opponent_name) end private @@ -101,8 +101,11 @@ module Public set_no = data[:set] || data["set"] home = data[:home] || data["home"] away = data[:away] || data["away"] - label = set_no.present? ? "Set #{set_no}" : "Set" - "#{label} #{home}-#{away}" + if set_no.present? + t("score.partial_entry", set: set_no, home: home, away: away) + else + t("score.partial_entry_no_number", home: home, away: away) + end end end end diff --git a/backend/app/helpers/public/regia_helper.rb b/backend/app/helpers/public/regia_helper.rb index 4657b96..6a6a64d 100644 --- a/backend/app/helpers/public/regia_helper.rb +++ b/backend/app/helpers/public/regia_helper.rb @@ -12,14 +12,60 @@ module Public when "basket", "timed" live_score_period_label(match, score) when "timer" - "Cronometro" + t("regia.board.stopwatch") when "generic" - "Punteggio" + t("regia.board.generic_score") else - "Set #{score.current_set}" + t("regia.board.set_label", number: score.current_set) end end + def regia_js_i18n_json + { + periodUpdated: t("regia.js.period_updated"), + nowInPeriodTemplate: t("regia.js.now_in_period", period: "%{period}"), + setWonTitle: t("regia.modal.set_won_title"), + setWonBodyTemplate: t("regia.js.set_won_body", winner: "%{winner}"), + matchWonTitle: t("regia.js.match_won_title"), + matchWonBodyTemplate: t("regia.js.match_won_body", winner: "%{winner}"), + closeSetTitle: t("regia.board.close_set"), + closeSetConfirm: t("regia.js.close_set_confirm"), + scoreUpdateError: t("regia.js.score_update_error"), + genericError: t("regia.js.generic_error"), + setsLineTemplate: t("regia.js.sets_line", current: "%{current}", home: "%{home}", away: "%{away}"), + setPartialLabelTemplate: t("regia.board.set_label", number: "%{number}"), + freeScore: t("regia.board.free_score"), + stopwatch: t("regia.board.stopwatch"), + quarterLabelTemplate: t("regia.js.quarter_label", n: "%{n}"), + halfLabelTemplate: t("regia.js.half_label", n: "%{n}"), + overtimeBasketTemplate: t("regia.js.overtime_basket", n: "%{n}"), + overtimeOther: t("regia.js.overtime_other"), + unknownPeriod: t("regia.js.unknown_period"), + partialsPrefix: t("regia.board.partials_prefix"), + resumed: t("regia.js.resumed"), + pausedCover: t("regia.js.paused_cover"), + closed: t("regia.js.closed"), + resumeError: t("regia.js.resume_error"), + pauseError: t("regia.js.pause_error"), + closeError: t("regia.js.close_error"), + closeConfirm: t("regia.js.close_confirm"), + linkUnavailable: t("regia.js.link_unavailable"), + linkCopied: t("regia.js.link_copied"), + copyPrompt: t("regia.js.copy_prompt"), + shareRegiaText: t("regia.js.share_regia_text"), + shareLiveText: t("regia.js.share_live_text"), + previewWaiting: t("regia.preview_waiting"), + streamEnded: t("regia.preview_ended"), + resumeLabel: t("regia.resume"), + pauseLabel: t("regia.pause"), + endedBadge: t("regia.status.ended"), + pausedBadge: t("regia.status.paused"), + liveBadge: t("regia.status.live"), + waitingBadge: t("regia.status.waiting"), + subtitlePrefix: t("regia.subtitle_prefix") + }.to_json + end + def format_clock_secs(secs, count_up: false) total = secs.to_i return count_up ? "0:00" : "—" if total <= 0 && !count_up diff --git a/backend/app/helpers/public/team_pages_helper.rb b/backend/app/helpers/public/team_pages_helper.rb index a636c29..fd45abc 100644 --- a/backend/app/helpers/public/team_pages_helper.rb +++ b/backend/app/helpers/public/team_pages_helper.rb @@ -1,7 +1,7 @@ module Public module TeamPagesHelper def team_page_meta_description(team, club) - "Segui #{team.name} (#{club.name}): dirette live, calendario partite e replay su Match Live TV." + t("team_pages.show.meta_description", team: team.name, club: club.name) end def filter_params_for_canonical diff --git a/backend/app/helpers/roster_helper.rb b/backend/app/helpers/roster_helper.rb index f9fbe8e..e57304a 100644 --- a/backend/app/helpers/roster_helper.rb +++ b/backend/app/helpers/roster_helper.rb @@ -19,14 +19,14 @@ module RosterHelper def roster_category_options TeamRosterMember::DISPLAY_ORDER.map do |cat| - [TeamRosterMember::CATEGORY_LABELS[cat], cat] + [TeamRosterMember.category_label(cat), cat] end end def roster_player_role_options(team, selected = nil) roles = Sports::PlayerRoles.for_team(team) options_for_select( - [["— Seleziona ruolo —", ""]] + roles.map { |r| [r, r] }, + [[t("roster.role_select_blank"), ""]] + roles.map { |r| [r, r] }, selected ) end diff --git a/backend/app/mailers/billing/invoice_mailer.rb b/backend/app/mailers/billing/invoice_mailer.rb index ca3f2e9..aabef33 100644 --- a/backend/app/mailers/billing/invoice_mailer.rb +++ b/backend/app/mailers/billing/invoice_mailer.rb @@ -4,15 +4,18 @@ module Billing @invoice = params[:invoice] @club = @invoice.club - attachments["fattura-#{@invoice.number.parameterize}.pdf"] = { - mime_type: "application/pdf", - content: @invoice.pdf.download - } + I18n.with_locale(I18n.locale) do + prefix = t("mailers.invoice.attachment_prefix") + attachments["#{prefix}-#{@invoice.number.parameterize}.pdf"] = { + mime_type: "application/pdf", + content: @invoice.pdf.download + } - mail( - to: @club.billing_email, - subject: "Fattura #{@invoice.display_number} — Match Live TV" - ) + mail( + to: @club.billing_email, + subject: t("mailers.invoice.subject", number: @invoice.display_number) + ) + end end end end diff --git a/backend/app/mailers/recordings/replay_mailer.rb b/backend/app/mailers/recordings/replay_mailer.rb index 87dfe96..0b858cb 100644 --- a/backend/app/mailers/recordings/replay_mailer.rb +++ b/backend/app/mailers/recordings/replay_mailer.rb @@ -9,11 +9,14 @@ module Recordings @recipient = recipient @replay_url = recording.replay_url @expires_at = recording.expires_at + @archive_url = "#{MatchLiveTv.app_public_url.chomp('/')}/clubs/#{@club.id}/replays" - mail( - to: recipient.email, - subject: "Replay pronto — #{recording.title_or_default}" - ) + I18n.with_locale(I18n.locale) do + mail( + to: recipient.email, + subject: t("mailers.replay_ready.subject", title: recording.title_or_default) + ) + end end def replay_expiring_soon(recording:, recipient:) @@ -24,11 +27,14 @@ module Recordings @replay_url = recording.replay_url @expires_at = recording.expires_at @days_left = ((recording.expires_at - Time.current) / 1.day).ceil + @archive_url = "#{MatchLiveTv.app_public_url.chomp('/')}/clubs/#{@club.id}/replays" - mail( - to: recipient.email, - subject: "Replay in scadenza tra #{@days_left} giorni — #{recording.title_or_default}" - ) + I18n.with_locale(I18n.locale) do + mail( + to: recipient.email, + subject: t("mailers.replay_expiring.subject", days: @days_left, title: recording.title_or_default) + ) + end end end end diff --git a/backend/app/mailers/user_mailer.rb b/backend/app/mailers/user_mailer.rb index 0d53825..59552d7 100644 --- a/backend/app/mailers/user_mailer.rb +++ b/backend/app/mailers/user_mailer.rb @@ -6,6 +6,8 @@ class UserMailer < ApplicationMailer @reset_url = public_password_reset_url(token: token) @expiry_hours = MatchLiveTv.password_reset_expiry_hours - mail to: user.email, subject: "Reimposta la password — Match Live TV" + I18n.with_locale(I18n.locale) do + mail to: user.email, subject: t("mailers.password_reset.subject") + end end end diff --git a/backend/app/models/concerns/club_billing_profile.rb b/backend/app/models/concerns/club_billing_profile.rb index bee7527..3c558ca 100644 --- a/backend/app/models/concerns/club_billing_profile.rb +++ b/backend/app/models/concerns/club_billing_profile.rb @@ -1,14 +1,16 @@ module ClubBillingProfile extend ActiveSupport::Concern - BILLING_ENTITY_TYPES = { - "company" => "Società / ASD con P.IVA", - "individual" => "Persona fisica", - "nonprofit" => "Associazione senza scopo di lucro" - }.freeze + BILLING_ENTITY_TYPE_KEYS = %w[company individual nonprofit].freeze + + class_methods do + def billing_entity_types + ClubBillingProfile::BILLING_ENTITY_TYPE_KEYS.index_with { |key| I18n.t("billing.entity_types.#{key}") } + end + end included do - validates :billing_entity_type, inclusion: { in: BILLING_ENTITY_TYPES.keys }, allow_nil: true + validates :billing_entity_type, inclusion: { in: BILLING_ENTITY_TYPE_KEYS }, allow_nil: true validates :billing_email, format: { with: URI::MailTo::EMAIL_REGEXP }, allow_blank: true validates :billing_recipient_code, length: { is: 7 }, allow_blank: true validates :billing_province, length: { is: 2 }, allow_blank: true @@ -23,28 +25,28 @@ module ClubBillingProfile # Campi minimi per intestazione fattura e invio (email PDF + SDI/PEC). def billing_profile_errors errors = [] - errors << "Tipo intestatario" if billing_entity_type.blank? - errors << "Ragione sociale o nome intestatario" if billing_legal_name.blank? - errors << "Email di fatturazione" if billing_email.blank? - errors << "Indirizzo" if billing_address_line.blank? - errors << "Città" if billing_city.blank? - errors << "Provincia (sigla 2 lettere)" if billing_province.blank? || billing_province.to_s.length != 2 - errors << "CAP" if billing_postal_code.blank? - errors << "Paese (ISO)" if billing_country.blank? || billing_country.to_s.length != 2 + errors << I18n.t("billing.profile_errors.entity_type") if billing_entity_type.blank? + errors << I18n.t("billing.profile_errors.legal_name") if billing_legal_name.blank? + errors << I18n.t("billing.profile_errors.email") if billing_email.blank? + errors << I18n.t("billing.profile_errors.address") if billing_address_line.blank? + errors << I18n.t("billing.profile_errors.city") if billing_city.blank? + errors << I18n.t("billing.profile_errors.province") if billing_province.blank? || billing_province.to_s.length != 2 + errors << I18n.t("billing.profile_errors.postal_code") if billing_postal_code.blank? + errors << I18n.t("billing.profile_errors.country") if billing_country.blank? || billing_country.to_s.length != 2 errors.concat(billing_tax_id_errors) - errors << "Codice destinatario SDI (7 caratteri) o PEC" if billing_recipient_code.blank? && billing_pec.blank? - errors << "Codice destinatario SDI (7 caratteri)" if billing_recipient_code.present? && billing_recipient_code.length != 7 + errors << I18n.t("billing.profile_errors.sdi_or_pec") if billing_recipient_code.blank? && billing_pec.blank? + errors << I18n.t("billing.profile_errors.sdi_invalid") if billing_recipient_code.present? && billing_recipient_code.length != 7 errors end def billing_tax_id_errors case billing_entity_type when "company" - billing_vat_number.blank? ? ["Partita IVA"] : [] + billing_vat_number.blank? ? [I18n.t("billing.profile_errors.vat_number")] : [] when "individual" - billing_fiscal_code.blank? ? ["Codice Fiscale"] : [] + billing_fiscal_code.blank? ? [I18n.t("billing.profile_errors.fiscal_code")] : [] else - billing_vat_number.blank? && billing_fiscal_code.blank? ? ["P.IVA o Codice Fiscale"] : [] + billing_vat_number.blank? && billing_fiscal_code.blank? ? [I18n.t("billing.profile_errors.vat_or_fiscal")] : [] end end @@ -56,7 +58,7 @@ module ClubBillingProfile # Righe per intestazione fattura in admin (etichetta, valore). def billing_profile_invoice_lines lines = [] - lines << ["Tipo", BILLING_ENTITY_TYPES[billing_entity_type]] if billing_entity_type.present? + lines << ["Tipo", self.class.billing_entity_types[billing_entity_type]] if billing_entity_type.present? lines << ["Intestatario", billing_legal_name] lines << ["P.IVA", billing_vat_number] if billing_vat_number.present? lines << ["Codice fiscale", billing_fiscal_code] if billing_fiscal_code.present? diff --git a/backend/app/models/match.rb b/backend/app/models/match.rb index d56fa4d..3a0f3e8 100644 --- a/backend/app/models/match.rb +++ b/backend/app/models/match.rb @@ -47,11 +47,11 @@ class Match < ApplicationRecord def public_status_label active = active_stream_session - return "In corso / da riprendere" if active - return "Programmata" if scheduled_upcoming? - return "Pronta" if scheduled_at.present? + return I18n.t("matches.status.active") if active + return I18n.t("matches.status.scheduled") if scheduled_upcoming? + return I18n.t("matches.status.ready") if scheduled_at.present? - "Senza orario" + I18n.t("matches.status.no_time") end scope :search_teams_or_opponents, lambda { |query| diff --git a/backend/app/models/recording.rb b/backend/app/models/recording.rb index 7ba9483..c9ae0cd 100644 --- a/backend/app/models/recording.rb +++ b/backend/app/models/recording.rb @@ -164,13 +164,13 @@ class Recording < ApplicationRecord end def status_label - return "Eliminato" if deleted? - return "Scaduto" if status == "expired" - return "Errore" if status == "failed" - return "In elaborazione" if status == "processing" - return "Scade presto" if expires_at.present? && expires_at <= 7.days.from_now + return I18n.t("recordings.status.deleted") if deleted? + return I18n.t("recordings.status.expired") if status == "expired" + return I18n.t("recordings.status.failed") if status == "failed" + return I18n.t("recordings.status.processing") if status == "processing" + return I18n.t("recordings.status.expiring_soon") if expires_at.present? && expires_at <= 7.days.from_now - "Disponibile" + I18n.t("recordings.status.available") end private diff --git a/backend/app/models/team_roster_member.rb b/backend/app/models/team_roster_member.rb index f506510..eb3dfc0 100644 --- a/backend/app/models/team_roster_member.rb +++ b/backend/app/models/team_roster_member.rb @@ -2,12 +2,10 @@ class TeamRosterMember < ApplicationRecord CATEGORIES = %w[staff coach manager player].freeze # Ordine di visualizzazione in pagina organico DISPLAY_ORDER = %w[coach manager player staff].freeze - CATEGORY_LABELS = { - "staff" => "Staff", - "coach" => "Allenatori", - "manager" => "Dirigenti", - "player" => "Giocatori" - }.freeze + + def self.category_label(category) + I18n.t("roster.category.#{category}", default: category.to_s.capitalize) + end VOLLEYBALL_PLAYER_ROLES = Sports::PlayerRoles::BY_BOARD["volley"].freeze @@ -27,7 +25,7 @@ class TeamRosterMember < ApplicationRecord scope :by_category, ->(cat) { where(category: cat).ordered } def category_label - CATEGORY_LABELS[category] || category + self.class.category_label(category) end def initials @@ -58,10 +56,11 @@ class TeamRosterMember < ApplicationRecord def default_role_label case category - when "coach" then "Allenatore" - when "manager" then "Dirigente" - when "staff" then "Staff" - when "player" then jersey_number.present? ? "Giocatore ##{jersey_number}" : "Giocatore" + when "coach" then I18n.t("roster.role.coach") + when "manager" then I18n.t("roster.role.manager") + when "staff" then I18n.t("roster.role.staff") + when "player" + jersey_number.present? ? I18n.t("roster.role.player_numbered", number: jersey_number) : I18n.t("roster.role.player") else category end end @@ -70,12 +69,12 @@ class TeamRosterMember < ApplicationRecord allowed = Sports::PlayerRoles.for_team(team) return if role_label.in?(allowed) - errors.add(:role_label, "non è un ruolo valido per questo sport") + errors.add(:role_label, I18n.t("roster.errors.role_invalid")) end def photo_file_type return if photo_file.content_type.in?(%w[image/png image/jpeg image/webp]) - errors.add(:photo_file, "deve essere PNG, JPEG o WebP") + errors.add(:photo_file, I18n.t("roster.errors.photo_invalid_type")) end end diff --git a/backend/app/services/billing/stripe/plan_change_messages.rb b/backend/app/services/billing/stripe/plan_change_messages.rb index ffb8c35..ec7af36 100644 --- a/backend/app/services/billing/stripe/plan_change_messages.rb +++ b/backend/app/services/billing/stripe/plan_change_messages.rb @@ -10,38 +10,37 @@ module Billing if result.upgrade? amount = format_amount(result.amount_cents, result.currency) if amount.present? - "Upgrade a #{plan.name} (#{interval_label}) completato. Addebito immediato: #{amount}." + I18n.t("billing.messages.upgrade_completed_with_amount", plan: plan.name, interval: interval_label, amount: amount) else - "Upgrade a #{plan.name} (#{interval_label}) completato. L'addebito della differenza è in elaborazione sulla carta salvata." + I18n.t("billing.messages.upgrade_completed_pending_charge", plan: plan.name, interval: interval_label) end else - when_label = result.effective_at.present? ? I18n.l(result.effective_at, format: :long) : "il prossimo rinnovo" - current_name = current_plan&.name || "il piano attuale" - "Passaggio a #{plan.name} (#{interval_label}) programmato: resti su #{current_name} fino al #{when_label}. " \ - "Nessun addebito aggiuntivo ora; dal prossimo ciclo pagherai il nuovo prezzo." + when_label = result.effective_at.present? ? I18n.l(result.effective_at, format: :long) : I18n.t("billing.messages.next_renewal") + current_name = current_plan&.name || I18n.t("billing.messages.current_plan_generic") + I18n.t("billing.messages.downgrade_scheduled", plan: plan.name, interval: interval_label, current_name: current_name, when: when_label) end end def button_label(plan:, interval:, kind:) price = PriceCatalog.label(plan_slug: plan.slug, interval: interval) if kind == :checkout - "Attiva #{plan.name} — #{price}" + I18n.t("billing.messages.activate_button", plan: plan.name, price: price) else - "Passa a #{plan.name} — #{price}" + I18n.t("billing.messages.switch_button", plan: plan.name, price: price) end end def billing_info_lines(subscription: nil) lines = [ - "Upgrade (piano superiore o passaggio alla fatturazione annuale): la differenza viene addebitata subito sulla carta salvata in Stripe.", - "Downgrade tra piani a pagamento (es. Full → Light): il nuovo piano è attivo dal prossimo rinnovo; fino ad allora restano le funzioni del piano attuale, senza addebito aggiuntivo ora." + I18n.t("billing.messages.upgrade_info_line"), + I18n.t("billing.messages.downgrade_info_line") ] if subscription&.plan_change_pending? && subscription.pending_plan.present? when_at = subscription.current_period_end - when_label = when_at.present? ? I18n.l(when_at, format: :long) : "il prossimo rinnovo" + when_label = when_at.present? ? I18n.l(when_at, format: :long) : I18n.t("billing.messages.next_renewal") lines.unshift( - "Hai già richiesto #{subscription.pending_plan.name} dal #{when_label}: fino ad allora resta attivo #{subscription.plan.name}." + I18n.t("billing.messages.already_requested_line", plan: subscription.pending_plan.name, when: when_label, current_plan: subscription.plan.name) ) end diff --git a/backend/app/views/admin/auth/new.html.erb b/backend/app/views/admin/auth/new.html.erb index ae1d6d9..bcee31f 100644 --- a/backend/app/views/admin/auth/new.html.erb +++ b/backend/app/views/admin/auth/new.html.erb @@ -1,26 +1,26 @@
-

Accesso admin

+

<%= t("admin.auth.new.title") %>

<% if flash[:alert] %>

<%= flash[:alert] %>

<% end %> <%= form_with url: admin_login_path, method: :post, local: true do %>

-
+

-
+

<% end %>

- Credenziali iniziali: admin / admin. Cambia la password dopo il primo accesso. + <%= t("admin.auth.new.initial_credentials_html") %>

diff --git a/backend/app/views/admin/billing/index.html.erb b/backend/app/views/admin/billing/index.html.erb index 7fa8644..99bfbcc 100644 --- a/backend/app/views/admin/billing/index.html.erb +++ b/backend/app/views/admin/billing/index.html.erb @@ -1,12 +1,12 @@ -

Pagamenti da fatturare

+

<%= t("admin.billing.index.title") %>

- Pagamenti Stripe pagati senza PDF fattura. Genera il PDF nei tuoi sistemi, poi caricalo qui: viene associato al pagamento e inviato via email al cliente. + <%= t("admin.billing.index.description") %>

<% if @clubs.many? %>

- Filtra società: - <%= link_to "Tutte", admin_billing_path, class: (@filter_club ? nil : "admin-nav-active") %> + <%= t("admin.billing.index.filter_label") %> + <%= link_to t("admin.billing.index.filter_all"), admin_billing_path, class: (@filter_club ? nil : "admin-nav-active") %> <% @clubs.each do |club| %> · <%= link_to club.name, admin_billing_path(club_id: club.id), class: (@filter_club&.id == club.id ? "admin-nav-active" : nil) %> <% end %> @@ -33,7 +33,7 @@ · <%= payment.formatted_amount %> <% unless club.billing_profile_complete? %> - Dati fatturazione incompleti + <%= t("admin.billing.index.incomplete_profile_warning") %> <% end %> @@ -46,16 +46,16 @@ <% end %> <% else %> -

Nessun dato di fatturazione — il cliente deve completare il profilo.

+

<%= t("admin.billing.index.no_billing_data") %>

<% end %> <%= form_with url: admin_billing_payment_attach_pdf_path(payment), method: :post, multipart: true, local: true, class: "billing-upload-form" do %> - <%= submit_tag "Carica PDF e invia al cliente", class: "admin-btn admin-btn--primary" %> + <%= submit_tag t("admin.billing.index.upload_button"), class: "admin-btn admin-btn--primary" %> <% end %> @@ -63,21 +63,21 @@ <% else %>

- Nessun pagamento in attesa di fattura<%= @filter_club ? " per #{@filter_club.name}" : "" %>. + <%= t("admin.billing.index.none_pending", club: (@filter_club ? t("admin.billing.index.none_pending_for_club", club: @filter_club.name) : "")) %>

<% end %> <% if @completed_payments.any? %> -

Fatture già caricate

+

<%= t("admin.billing.index.uploaded_title") %>

- - - - - - + + + + + + @@ -96,4 +96,4 @@
DataSocietàDescrizioneImportoFatturaStato<%= t("admin.billing.index.table.date") %><%= t("admin.billing.index.table.club") %><%= t("admin.billing.index.table.description") %><%= t("admin.billing.index.table.amount") %><%= t("admin.billing.index.table.invoice") %><%= t("admin.billing.index.table.status") %>
<% end %> -

<%= link_to "← Dashboard", admin_root_path %>

+

<%= link_to t("admin.billing.index.back_dashboard"), admin_root_path %>

diff --git a/backend/app/views/admin/billing_invoices/edit.html.erb b/backend/app/views/admin/billing_invoices/edit.html.erb index 9f0c476..f25bcab 100644 --- a/backend/app/views/admin/billing_invoices/edit.html.erb +++ b/backend/app/views/admin/billing_invoices/edit.html.erb @@ -1,45 +1,45 @@ -

Fattura <%= @invoice.number %> — <%= @club.name %>

+

<%= t("admin.billing_invoices.edit.title", number: @invoice.number, club: @club.name) %>

- Stato: <%= @invoice.status %> + <%= t("admin.billing_invoices.edit.status_label") %> <%= @invoice.status %> <% if @invoice.emailed_at.present? %> - · Inviata il <%= @invoice.emailed_at.in_time_zone.strftime("%d/%m/%Y %H:%M") %> + · <%= t("admin.billing_invoices.edit.sent_at", date: @invoice.emailed_at.in_time_zone.strftime("%d/%m/%Y %H:%M")) %> <% end %>

<%= form_with model: @invoice, url: admin_club_billing_invoice_path(@club, @invoice), multipart: true, method: :patch do |f| %> - <%= f.label :number, "Numero fattura" %> + <%= f.label :number, t("admin.billing_invoices.edit.number_label") %> <%= f.text_field :number, required: true, disabled: @invoice.status.in?(%w[sent]) %> - <%= f.label :issued_on, "Data emissione" %> + <%= f.label :issued_on, t("admin.billing_invoices.edit.issued_on_label") %> <%= f.date_field :issued_on, required: true, disabled: @invoice.status.in?(%w[sent]) %> - <%= label_tag :amount_euros, "Importo (€)" %> + <%= label_tag :amount_euros, t("admin.billing_invoices.edit.amount_label") %> <%= number_field_tag "billing_invoice[amount_euros]", @invoice.amount_cents / 100.0, step: 0.01, min: 0.01, required: true, disabled: @invoice.status.in?(%w[sent]) %> <% if @payment %> -

Pagamento: <%= @payment.display_description %> — <%= @payment.formatted_amount %>

+

<%= t("admin.billing_invoices.edit.payment_info", description: @payment.display_description, amount: @payment.formatted_amount) %>

<% end %> - <%= f.label :pdf, "PDF fattura" %> + <%= f.label :pdf, t("admin.billing_invoices.edit.pdf_label") %> <% if @invoice.pdf.attached? %> -

PDF già caricato: <%= @invoice.pdf.filename %>

+

<%= t("admin.billing_invoices.edit.pdf_already", filename: @invoice.pdf.filename) %>

<% end %> <%= f.file_field :pdf, accept: "application/pdf" %> - <%= f.label :notes, "Note interne" %> + <%= f.label :notes, t("admin.billing_invoices.edit.notes_label") %> <%= f.text_area :notes, rows: 2 %>
<% unless @invoice.status.in?(%w[sent]) %> - <%= f.submit "Salva bozza", class: "btn btn-secondary" %> - <%= f.submit "Emetti e invia via email", class: "btn btn-primary", - data: { turbo_confirm: "Inviare la fattura a #{@club.billing_email}?" } %> + <%= f.submit t("admin.billing_invoices.edit.save_draft"), name: "commit_action", value: "draft", class: "btn btn-secondary" %> + <%= f.submit t("admin.billing_invoices.edit.issue_send"), name: "commit_action", value: "issue", class: "btn btn-primary", + data: { turbo_confirm: t("admin.billing_invoices.edit.issue_confirm", email: @club.billing_email) } %> <% end %>
<% end %>
-

<%= link_to "← Pagamenti e fatture", admin_club_billing_invoices_path(@club) %>

+

<%= link_to t("admin.billing_invoices.edit.back"), admin_club_billing_invoices_path(@club) %>

diff --git a/backend/app/views/admin/billing_invoices/index.html.erb b/backend/app/views/admin/billing_invoices/index.html.erb index 4d6f669..a5e2234 100644 --- a/backend/app/views/admin/billing_invoices/index.html.erb +++ b/backend/app/views/admin/billing_invoices/index.html.erb @@ -1,18 +1,18 @@ -

Fatturazione — <%= @club.name %>

+

<%= t("admin.billing_invoices.index.title", club: @club.name) %>

- <%= link_to "← Dashboard", admin_root_path %> - · <%= link_to "Nuova fattura (senza pagamento)", new_admin_club_billing_invoice_path(@club), class: "btn btn-secondary" %> + <%= link_to t("admin.billing_invoices.index.back_dashboard"), admin_root_path %> + · <%= link_to t("admin.billing_invoices.index.new_invoice"), new_admin_club_billing_invoice_path(@club), class: "btn btn-secondary" %>

-

Pagamenti

+

<%= t("admin.billing_invoices.index.payments_title") %>

<% if @payments.any? %> - - - - + + + + @@ -26,16 +26,16 @@ @@ -43,14 +43,21 @@
DataDescrizioneImportoFattura<%= t("admin.billing_invoices.index.table.date") %><%= t("admin.billing_invoices.index.table.description") %><%= t("admin.billing_invoices.index.table.amount") %><%= t("admin.billing_invoices.index.table.invoice") %>
<% if inv %> <%= inv.number %> — <%= inv.status %> - <% if inv.pdf.attached? %> (PDF)<% end %> + <% if inv.pdf.attached? %><%= t("admin.billing_invoices.index.pdf_suffix") %><% end %> <% else %> - — + <%= t("admin.common.dash") %> <% end %> <% if inv %> - <%= link_to "Modifica / invia PDF", edit_admin_club_billing_invoice_path(@club, inv) %> + <%= link_to t("admin.billing_invoices.index.edit_send_pdf"), edit_admin_club_billing_invoice_path(@club, inv) %> <% else %> - <%= link_to "Crea fattura", new_admin_club_billing_invoice_path(@club, billing_payment_id: payment.id), class: "btn btn-primary", style: "padding:6px 10px;font-size:0.85rem" %> + <%= link_to t("admin.billing_invoices.index.create_invoice"), new_admin_club_billing_invoice_path(@club, billing_payment_id: payment.id), class: "btn btn-primary", style: "padding:6px 10px;font-size:0.85rem" %> <% end %>
<% else %> -

Nessun pagamento registrato per questa società.

+

<%= t("admin.billing_invoices.index.no_payments") %>

<% end %> -

Tutte le fatture

+

<%= t("admin.billing_invoices.index.all_invoices_title") %>

<% if @invoices.any? %> - + + + + + + + + <% @invoices.each do |inv| %> @@ -59,12 +66,12 @@ - - + + <% end %>
NumeroDataImportoStatoPagamento
<%= t("admin.billing_invoices.index.table2.number") %><%= t("admin.billing_invoices.index.table2.date") %><%= t("admin.billing_invoices.index.table2.amount") %><%= t("admin.billing_invoices.index.table2.status") %><%= t("admin.billing_invoices.index.table2.payment") %>
<%= inv.issued_on %> <%= inv.formatted_amount %> <%= inv.status %><%= inv.billing_payment_id.present? ? "Sì" : "No" %><%= link_to "Modifica", edit_admin_club_billing_invoice_path(@club, inv) %><%= inv.billing_payment_id.present? ? t("admin.common.yes") : t("admin.common.no") %><%= link_to t("admin.billing_invoices.index.edit"), edit_admin_club_billing_invoice_path(@club, inv) %>
<% else %> -

Nessuna fattura.

+

<%= t("admin.billing_invoices.index.no_invoices") %>

<% end %> diff --git a/backend/app/views/admin/billing_invoices/new.html.erb b/backend/app/views/admin/billing_invoices/new.html.erb index e00a854..de9abd6 100644 --- a/backend/app/views/admin/billing_invoices/new.html.erb +++ b/backend/app/views/admin/billing_invoices/new.html.erb @@ -1,32 +1,32 @@ -

Nuova fattura — <%= @club.name %>

+

<%= t("admin.billing_invoices.new.title", club: @club.name) %>

<% if @payment %> -

Collegata al pagamento del <%= @payment.paid_at&.to_date || @payment.created_at.to_date %> (<%= @payment.formatted_amount %>).

+

<%= t("admin.billing_invoices.new.linked_payment", date: (@payment.paid_at&.to_date || @payment.created_at.to_date), amount: @payment.formatted_amount) %>

<% end %>
<%= form_with model: @invoice, url: admin_club_billing_invoices_path(@club) do |f| %> <%= f.hidden_field :billing_payment_id if @payment %> - <%= f.label :number, "Numero fattura" %> + <%= f.label :number, t("admin.billing_invoices.new.number_label") %> <%= f.text_field :number, required: true %> - <%= f.label :issued_on, "Data emissione" %> + <%= f.label :issued_on, t("admin.billing_invoices.new.issued_on_label") %> <%= f.date_field :issued_on, required: true %> - <%= label_tag :amount_euros, "Importo (€)" %> + <%= label_tag :amount_euros, t("admin.billing_invoices.new.amount_label") %> <%= number_field_tag "billing_invoice[amount_euros]", (@invoice.amount_cents ? @invoice.amount_cents / 100.0 : nil), step: 0.01, min: 0.01, required: true %> - <%= f.label :notes, "Note interne (opzionale)" %> + <%= f.label :notes, t("admin.billing_invoices.new.notes_label") %> <%= f.text_area :notes, rows: 2 %>

- Salva la bozza, poi carica il PDF e inviala al cliente dalla schermata successiva. + <%= t("admin.billing_invoices.new.hint") %>

- <%= f.submit "Crea bozza fattura", class: "btn btn-primary" %> + <%= f.submit t("admin.billing_invoices.new.submit"), class: "btn btn-primary" %> <% end %>
-

<%= link_to "← Pagamenti e fatture", admin_club_billing_invoices_path(@club) %>

+

<%= link_to t("admin.billing_invoices.new.back"), admin_club_billing_invoices_path(@club) %>

diff --git a/backend/app/views/admin/club_recordings/index.html.erb b/backend/app/views/admin/club_recordings/index.html.erb index a6faa14..21dd94e 100644 --- a/backend/app/views/admin/club_recordings/index.html.erb +++ b/backend/app/views/admin/club_recordings/index.html.erb @@ -1,4 +1,4 @@ -<% content_for :title, "Replay — #{@club.name}" %> +<% content_for :title, t("admin.club_recordings.index.title", club: @club.name) %> <% content_for :replay_archive_styles, true %> <%= render "recordings/club_archive", admin_mode: true %> diff --git a/backend/app/views/admin/clubs/_comped_form.html.erb b/backend/app/views/admin/clubs/_comped_form.html.erb index 7e7b09f..e539bd7 100644 --- a/backend/app/views/admin/clubs/_comped_form.html.erb +++ b/backend/app/views/admin/clubs/_comped_form.html.erb @@ -1,43 +1,43 @@ <%# locals: (club:, subscription:, return_to: nil) %>
-

Abbonamento omaggio

+

<%= t("admin.comped.title") %>

- Sponsor o promozione: assegna Premium Light/Full senza pagamento Stripe. Revocabile in qualsiasi momento. + <%= t("admin.comped.description") %>

<% sub = subscription %> <% if sub&.admin_comped? %>

- Attivo: <%= sub.plan.name %> + <%= t("admin.comped.active_label") %> <%= sub.plan.name %> <% if sub.admin_comped_reason.present? %> · <%= sub.admin_comped_reason %> <% end %> <% if sub.admin_comped_at.present? %> -
Dal <%= l(sub.admin_comped_at, format: :long) %> - <% if sub.admin_comped_by.present? %> · admin <%= sub.admin_comped_by.username %><% end %> +
<%= t("admin.comped.since_label_html", date: l(sub.admin_comped_at, format: :long)) %> + <% if sub.admin_comped_by.present? %> <%= t("admin.comped.by_admin", username: sub.admin_comped_by.username) %><% end %> <% end %> <% if sub.stripe_subscription_id.present? %> -
Nota: esiste anche un abbonamento Stripe collegato; l’omaggio ha priorità sul piano. +
<%= t("admin.comped.stripe_note") %> <% end %>

- <%= button_to "Revoca omaggio (torna Free o ripristina Stripe)", + <%= button_to t("admin.comped.revoke_button"), revoke_comped_admin_club_path(club, return_to: return_to), method: :delete, class: "admin-btn admin-btn--secondary", - form: { data: { turbo_confirm: "Revocare l’abbonamento omaggio per #{club.name}?" } } %> + form: { data: { turbo_confirm: t("admin.comped.revoke_confirm", club: club.name) } } %> <% else %>

- Piano attuale: <%= sub&.plan&.name || "Free" %> + <%= t("admin.comped.current_plan", plan: sub&.plan&.name || t("admin.common.free_plan")) %> <% if sub&.stripe_subscription_id.present? %> - · pagamento Stripe attivo + <%= t("admin.comped.stripe_active") %> <% end %>

<%= form_with url: grant_comped_admin_club_path(club), method: :post, local: true, class: "admin-comped-form" do %> <%= hidden_field_tag :return_to, return_to if return_to.present? %>
- <%= submit_tag "Concedi omaggio", class: "admin-btn admin-btn--primary" %> + <%= submit_tag t("admin.comped.grant_button"), class: "admin-btn admin-btn--primary" %>
<% end %> <% end %> diff --git a/backend/app/views/admin/clubs/index.html.erb b/backend/app/views/admin/clubs/index.html.erb index 0fdae58..cf05d0a 100644 --- a/backend/app/views/admin/clubs/index.html.erb +++ b/backend/app/views/admin/clubs/index.html.erb @@ -1,17 +1,16 @@ -

Società e squadre

+

<%= t("admin.clubs.index.title") %>

- Abbonamenti omaggio (sponsor / promozioni) e stato YouTube per squadra. - Pagamenti Stripe: <%= link_to "Fatturazione", admin_billing_path %>. + <%= t("admin.clubs.index.subtitle_html", billing_link: link_to(t("admin.clubs.index.billing_link"), admin_billing_path)) %>

- - - - - + + + + + @@ -20,20 +19,20 @@ <% sub = club.subscription %> - + - + <% end %> diff --git a/backend/app/views/admin/clubs/show.html.erb b/backend/app/views/admin/clubs/show.html.erb index 40f7101..fb90dad 100644 --- a/backend/app/views/admin/clubs/show.html.erb +++ b/backend/app/views/admin/clubs/show.html.erb @@ -1,36 +1,36 @@ -

<%= link_to "← Società e squadre", admin_clubs_path %>

+

<%= link_to t("admin.clubs.show.back"), admin_clubs_path %>

<%= @club.name %>

- Sport: <%= @club.sport %> - · <%= link_to "Archivio replay", admin_club_recordings_path(@club) %> - · <%= link_to "Pagamenti e fatture", admin_billing_path(club_id: @club.id) %> - · <%= link_to "Canale YouTube piattaforma", admin_youtube_platform_path %> + <%= t("admin.clubs.show.sport_label", sport: @club.sport) %> + · <%= link_to t("admin.clubs.show.recordings_archive"), admin_club_recordings_path(@club) %> + · <%= link_to t("admin.clubs.show.billing_link"), admin_billing_path(club_id: @club.id) %> + · <%= link_to t("admin.clubs.show.youtube_platform_link"), admin_youtube_platform_path %>

<%= render "admin/clubs/comped_form", club: @club, subscription: @subscription, return_to: admin_club_path(@club) %> <% cred = @club.youtube_credential %>

- YouTube società: + <%= t("admin.clubs.show.youtube_club_label") %> <% if cred %> - <%= cred.channel_title.presence || cred.channel_id %> (collegato) + <%= cred.channel_title.presence || cred.channel_id %> <%= t("admin.clubs.show.youtube_connected") %> <% elsif @teams.any? { |t| t.entitlements.premium_full? } %> - Premium Full — canale società non collegato (usa Match Live TV) + <%= t("admin.clubs.show.youtube_premium_full_not_connected") %> <% else %> - + <%= t("admin.common.dash") %> <% end %>

-

Squadre

+

<%= t("admin.clubs.show.teams_title") %>

<% if @teams.empty? %> -

Nessuna squadra registrata.

+

<%= t("admin.clubs.show.no_teams") %>

<% else %>
SocietàPianoSquadreOmaggioStripe<%= t("admin.clubs.index.table.club") %><%= t("admin.clubs.index.table.plan") %><%= t("admin.clubs.index.table.teams") %><%= t("admin.clubs.index.table.comped") %><%= t("admin.clubs.index.table.stripe") %>
<%= club.name %><%= sub&.plan&.name || "Free" %><%= sub&.plan&.name || t("admin.common.free_plan") %> <%= club.teams.size %> <% if sub&.admin_comped? %> - + <%= t("admin.common.yes") %> <% if sub.admin_comped_reason.present? %> · <%= sub.admin_comped_reason %><% end %> <% else %> - — + <%= t("admin.common.dash") %> <% end %> <%= sub&.stripe_subscription_id.present? ? "Sì" : "—" %><%= sub&.stripe_subscription_id.present? ? t("admin.common.yes") : t("admin.common.dash") %> - <%= link_to "Gestisci", admin_club_path(club) %> - · <%= link_to "Fatture", admin_billing_path(club_id: club.id) %> + <%= link_to t("admin.clubs.index.manage"), admin_club_path(club) %> + · <%= link_to t("admin.clubs.index.invoices"), admin_billing_path(club_id: club.id) %>
- - + + @@ -39,7 +39,7 @@ - + <% end %> diff --git a/backend/app/views/admin/dashboard/index.html.erb b/backend/app/views/admin/dashboard/index.html.erb index 4296863..dadf64d 100644 --- a/backend/app/views/admin/dashboard/index.html.erb +++ b/backend/app/views/admin/dashboard/index.html.erb @@ -2,147 +2,153 @@
-
Sessioni attive
+
<%= t("admin.dashboard.kpi.active_sessions") %>
<%= @stats[:active_sessions] %>
-
in diretta ora
+
<%= t("admin.dashboard.kpi.active_sessions_sub") %>
-
Trasmesse oggi
+
<%= t("admin.dashboard.kpi.today") %>
<%= @stats[:sessions_today] %>
-
<%= format_duration_minutes(@stats[:stream_minutes_today]) %> di streaming
+
<%= t("admin.dashboard.kpi.today_sub", duration: format_duration_minutes(@stats[:stream_minutes_today])) %>
-
Trasmesse questo mese
+
<%= t("admin.dashboard.kpi.month") %>
<%= @stats[:sessions_month] %>
-
<%= format_duration_minutes(@stats[:stream_minutes_month]) %> totali
+
<%= t("admin.dashboard.kpi.month_sub", duration: format_duration_minutes(@stats[:stream_minutes_month])) %>
-
Squadre
+
<%= t("admin.dashboard.kpi.teams") %>
<%= @stats[:teams_count] %>
-
<%= @stats[:users_count] %> utenti
+
<%= t("admin.dashboard.kpi.teams_sub", count: @stats[:users_count]) %>
-
CPU
+
<%= t("admin.dashboard.kpi.cpu") %>
<%= @host[:cpu] %>%
-
load <%= @host[:load_avg]["1m"] %> / <%= @host[:load_avg]["5m"] %> / <%= @host[:load_avg]["15m"] %>
+
<%= t("admin.dashboard.kpi.cpu_sub", one: @host[:load_avg]["1m"], five: @host[:load_avg]["5m"], fifteen: @host[:load_avg]["15m"]) %>
-
RAM
+
<%= t("admin.dashboard.kpi.ram") %>
<%= @host[:memory][:used_percent] %>%
-
<%= format_bytes(@host[:memory][:used_bytes]) %> / <%= format_bytes(@host[:memory][:total_bytes]) %>
+
<%= t("admin.dashboard.kpi.ram_sub", used: format_bytes(@host[:memory][:used_bytes]), total: format_bytes(@host[:memory][:total_bytes])) %>
-
Banda (totale)
+
<%= t("admin.dashboard.kpi.network") %>
<%= format_bytes(@host[:network][:total_bytes]) %>
-
↑ <%= format_bytes(@host[:network][:tx_bytes_total]) %> · ↓ <%= format_bytes(@host[:network][:rx_bytes_total]) %>
+
<%= t("admin.dashboard.kpi.network_sub", tx: format_bytes(@host[:network][:tx_bytes_total]), rx: format_bytes(@host[:network][:rx_bytes_total])) %>
-
Replay in archivio
+
<%= t("admin.dashboard.kpi.recordings") %>
<%= @stats[:recordings_ready] %>
-
<%= @stats[:recordings_count] %> registrazioni
+
<%= t("admin.dashboard.kpi.recordings_sub", count: @stats[:recordings_count]) %>
-

CPU (%)

+

<%= t("admin.dashboard.charts.cpu") %>

-

RAM (%)

+

<%= t("admin.dashboard.charts.ram") %>

-

Stato piattaforma (Ops)

+

<%= t("admin.dashboard.ops_panel.title") %>

<% critical = Ops::Incident.critical_open.count %> <% warnings = Ops::Incident.open.where(severity: "warning").count %> <% if critical.positive? %> -

<%= critical %> incidente/i critico/i aperto/i — <%= link_to "Vedi dashboard Ops", admin_ops_path %>

+

<%= t("admin.dashboard.ops_panel.critical", count: critical) %> — <%= link_to t("admin.dashboard.ops_panel.view_dashboard"), admin_ops_path %>

<% elsif warnings.positive? %> -

<%= warnings %> warning — <%= link_to "Dashboard Ops", admin_ops_path %>

+

<%= t("admin.dashboard.ops_panel.warnings", count: warnings) %> — <%= link_to t("admin.dashboard.ops_panel.dashboard_link"), admin_ops_path %>

<% else %> -

Nessun incidente aperto. <%= link_to "Dashboard Ops", admin_ops_path %>

+

<%= t("admin.dashboard.ops_panel.none_html", link: link_to(t("admin.dashboard.ops_panel.dashboard_link"), admin_ops_path)) %>

<% end %>
-

Disco sistema

+

<%= t("admin.dashboard.disk.system_title") %>

<% root = @host[:disk][:root] %> <% if root[:total_bytes] %>
<%= root[:path] %><%= format_bytes(root[:used_bytes]) %> / <%= format_bytes(root[:total_bytes]) %>
-

Libero: <%= format_bytes(root[:free_bytes]) %> (<%= root[:used_percent] %>% usato)

+

<%= t("admin.dashboard.disk.free_label", free: format_bytes(root[:free_bytes]), percent: root[:used_percent]) %>

<% else %> -

Metriche disco non disponibili

+

<%= t("admin.dashboard.disk.not_available") %>

<% end %>
-

Archivio registrazioni

+

<%= t("admin.dashboard.disk.recordings_title") %>

<% rec = @host[:disk][:recordings] %> <% if rec[:total_bytes] %>
<%= rec[:path] %><%= format_bytes(rec[:used_bytes]) %> / <%= format_bytes(rec[:total_bytes]) %>
<% if rec[:dir_size_bytes] %> -

Contenuto registrazioni: <%= format_bytes(rec[:dir_size_bytes]) %>

+

<%= t("admin.dashboard.disk.recordings_content", size: format_bytes(rec[:dir_size_bytes])) %>

<% end %>
<% else %> -

Percorso <%= Admin::HostMetrics::RECORDINGS_PATH %> non montato

+

<%= t("admin.dashboard.disk.recordings_not_mounted", path: Admin::HostMetrics::RECORDINGS_PATH) %>

<% end %>
-

Sessioni attive

+

<%= t("admin.dashboard.sessions.title") %>

<% if @active_sessions.any? %>
SquadraSport<%= t("admin.clubs.show.table.team") %><%= t("admin.clubs.show.table.sport") %>
<%= team.name %> <%= team.sport %><%= link_to "Partite e dettagli", admin_team_path(team) %> · <%= link_to "Replay", admin_club_recordings_path(@club, team_id: team.id) %><%= link_to t("admin.clubs.show.matches_and_details"), admin_team_path(team) %> · <%= link_to t("admin.clubs.show.replay"), admin_club_recordings_path(@club, team_id: team.id) %>
- + + + + + + + <% @active_sessions.each do |s| %> - + <% end %>
PartitaStatoInizioLink
<%= t("admin.dashboard.sessions.table.match") %><%= t("admin.dashboard.sessions.table.status") %><%= t("admin.dashboard.sessions.table.start") %><%= t("admin.dashboard.sessions.table.link") %>
<%= s.match.team.name %> vs <%= s.match.opponent_name %> <%= s.status %><%= s.started_at&.strftime("%d/%m %H:%M") || "—" %><%= s.started_at&.strftime("%d/%m %H:%M") || t("admin.common.dash") %> - <%= link_to "Dettaglio", admin_session_path(s) %> - <%= button_to "Termina", stop_admin_session_path(s), method: :post, class: "admin-btn admin-btn--danger admin-btn--sm", form: { data: { turbo_confirm: "Terminare questa sessione?" } } %> + <%= link_to t("admin.dashboard.sessions.detail"), admin_session_path(s) %> + <%= button_to t("admin.dashboard.sessions.stop"), stop_admin_session_path(s), method: :post, class: "admin-btn admin-btn--danger admin-btn--sm", form: { data: { turbo_confirm: t("admin.dashboard.sessions.stop_confirm") } } %>
<% else %> -

Nessuna sessione attiva in questo momento.

+

<%= t("admin.dashboard.sessions.none") %>

<% end %>
-

Squadre

+

<%= t("admin.dashboard.teams.title") %>

    - <% @teams.each do |t| %> + <% @teams.each do |t_| %>
  • - <%= link_to t.name, admin_team_path(t) %> - <%= t.matches.count %> partite + <%= link_to t_.name, admin_team_path(t_) %> + <%= t("admin.dashboard.teams.matches_count", count: t_.matches.count) %>
  • <% end %>
<% if @stats[:teams_count] > @teams.size %> -

<%= link_to "Vedi società (#{@stats[:teams_count]} squadre)", admin_clubs_path %>

+

<%= link_to t("admin.dashboard.teams.view_all", count: @stats[:teams_count]), admin_clubs_path %>

<% end %>
diff --git a/backend/app/views/admin/ops/index.html.erb b/backend/app/views/admin/ops/index.html.erb index 41c7922..f4abb37 100644 --- a/backend/app/views/admin/ops/index.html.erb +++ b/backend/app/views/admin/ops/index.html.erb @@ -2,30 +2,30 @@
-
Critici aperti
+
<%= t("admin.ops.kpi.critical") %>
<%= @summary[:open_critical] %>
-
Warning aperti
+
<%= t("admin.ops.kpi.warning") %>
<%= @summary[:open_warning] %>
-
Totale aperti
+
<%= t("admin.ops.kpi.total") %>
<%= @summary[:open_total] %>
-

Incidenti aperti

+

<%= t("admin.ops.open.title") %>

<% if @open_incidents.any? %> - - - - - + + + + + @@ -44,26 +44,32 @@ <% end %>
SeveritàTipoTitoloOcc.Ultimo<%= t("admin.ops.open.table.severity") %><%= t("admin.ops.open.table.kind") %><%= t("admin.ops.open.table.title") %><%= t("admin.ops.open.table.occurrences") %><%= t("admin.ops.open.table.last_seen") %>
<%= inc.last_seen_at&.strftime("%d/%m %H:%M") %> <% unless inc.status == 'acknowledged' %> - <%= button_to "Preso in carico", acknowledge_admin_op_path(inc), method: :post, class: "admin-btn admin-btn--sm" %> + <%= button_to t("admin.ops.actions.acknowledge"), acknowledge_admin_op_path(inc), method: :post, class: "admin-btn admin-btn--sm" %> <% end %> - <%= button_to "Risolvi", resolve_admin_op_path(inc), method: :post, class: "admin-btn admin-btn--sm" %> - <%= button_to "Mute 24h", mute_admin_op_path(inc), method: :post, class: "admin-btn admin-btn--sm admin-btn--outline" %> + <%= button_to t("admin.ops.actions.resolve"), resolve_admin_op_path(inc), method: :post, class: "admin-btn admin-btn--sm" %> + <%= button_to t("admin.ops.actions.mute"), mute_admin_op_path(inc), method: :post, class: "admin-btn admin-btn--sm admin-btn--outline" %>
<% else %> -

Nessun incidente aperto. La piattaforma risulta sana.

+

<%= t("admin.ops.open.none") %>

<% end %>
-

Risolti (ultimi 30 giorni)

+

<%= t("admin.ops.resolved.title") %>

<% if @resolved_incidents.any? %> - + + + + + + + <% @resolved_incidents.each do |inc| %> @@ -78,6 +84,6 @@
SeveritàTipoTitoloOcc.Risolto
<%= t("admin.ops.open.table.severity") %><%= t("admin.ops.open.table.kind") %><%= t("admin.ops.open.table.title") %><%= t("admin.ops.open.table.occurrences") %><%= t("admin.ops.resolved.table.resolved_at") %>
<% else %> -

Nessun incidente risolto di recente.

+

<%= t("admin.ops.resolved.none") %>

<% end %>
diff --git a/backend/app/views/admin/passwords/edit.html.erb b/backend/app/views/admin/passwords/edit.html.erb index d13c420..15d6fbe 100644 --- a/backend/app/views/admin/passwords/edit.html.erb +++ b/backend/app/views/admin/passwords/edit.html.erb @@ -1,29 +1,29 @@
-

Cambia password

+

<%= t("admin.passwords.edit.title") %>

<% if flash[:alert] %>

<%= flash[:alert] %>

<% end %> <%= form_with url: admin_password_path, method: :patch, local: true do %>

-
+

-
+

-
+

- <%= link_to "Annulla", admin_root_path %> + <%= link_to t("admin.passwords.edit.cancel"), admin_root_path %>

<% end %>
diff --git a/backend/app/views/admin/sessions/_links.html.erb b/backend/app/views/admin/sessions/_links.html.erb index baee4cc..4e4cb19 100644 --- a/backend/app/views/admin/sessions/_links.html.erb +++ b/backend/app/views/admin/sessions/_links.html.erb @@ -1,6 +1,6 @@ <% watch_links = admin_session_watch_links(session) %>