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 @@
<%= 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") %>
- 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? %> -| Data | -Società | -Descrizione | -Importo | -Fattura | -Stato | +<%= 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") %> |
|---|
<%= 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 @@ -- 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 %>
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 %><%= 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 @@ -- <%= 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" %>
-| Data | -Descrizione | -Importo | -Fattura | +<%= 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 %> | @@ -43,14 +43,21 @@
|---|
Nessun pagamento registrato per questa società.
+<%= t("admin.billing_invoices.index.no_payments") %>
<% end %> -| Numero | Data | Importo | Stato | Pagamento | |
|---|---|---|---|---|---|
| <%= 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) %> | <% end %>
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 @@ -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 %>- 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) %>- 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 %>
- 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? %>- 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)) %>
| Società | -Piano | -Squadre | -Omaggio | -Stripe | +<%= 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? %> - Sì + <%= 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) %> |
| Squadra | -Sport | +<%= 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) %> |
| Partita | Stato | Inizio | Link | ||
|---|---|---|---|---|---|
| <%= 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") %> | <% admin_session_watch_links(s).each do |link| %> <%= link_to link[:label], link[:url], target: "_blank", rel: "noopener", class: "admin-link-chip" %> <% end %> - <%= link_to "Regia", admin_session_path(s), class: "admin-link-chip admin-link-chip--regia" %> + <%= link_to t("admin.dashboard.sessions.regia"), admin_session_path(s), class: "admin-link-chip admin-link-chip--regia" %> | - <%= 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") } } %> |
Nessuna sessione attiva in questo momento.
+<%= t("admin.dashboard.sessions.none") %>
<% end %><%= 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 %>| Severità | -Tipo | -Titolo | -Occ. | -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" %> | <% end %>
|---|
Nessun incidente aperto. La piattaforma risulta sana.
+<%= t("admin.ops.open.none") %>
<% end %>| Severità | Tipo | Titolo | Occ. | 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") %> | +
Nessun incidente risolto di recente.
+<%= t("admin.ops.resolved.none") %>
<% end %><%= 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 %>Nessun link video disponibile per questa sessione.
+<%= t("admin.sessions.links.none") %>
<% end %> -Sessione terminata — link regia non disponibile.
+<%= t("admin.sessions.links.terminated") %>
<% else %> <% if flash[:regia_url].present? %>- Regia + <%= t("admin.sessions.links.regia_label") %> <%= link_to flash[:regia_url], flash[:regia_url], target: "_blank", rel: "noopener", class: "admin-link-url" %>
<%= flash[:regia_url] %>
Valido fino a <%= expires %>
+<%= t("admin.sessions.links.valid_until", date: expires) %>
<% end %> <% elsif session.regia_token_active? %> -Esiste già un link regia attivo (l’URL non è recuperabile). Generane uno nuovo se serve condividerlo di nuovo.
+<%= t("admin.sessions.links.active_exists") %>
<% else %> -Nessun link regia attivo. Genera un link da condividere con chi gestisce il punteggio.
+<%= t("admin.sessions.links.none_active") %>
<% end %> - <%= button_to "Genera link regia", + <%= button_to t("admin.sessions.links.generate_button"), regia_link_admin_session_path(session), method: :post, class: "admin-btn admin-btn--secondary admin-btn--sm", diff --git a/backend/app/views/admin/sessions/index.html.erb b/backend/app/views/admin/sessions/index.html.erb index 9c48299..fe14c3c 100644 --- a/backend/app/views/admin/sessions/index.html.erb +++ b/backend/app/views/admin/sessions/index.html.erb @@ -1,6 +1,14 @@ -| Match | Status | Disconnects | Link | |
|---|---|---|---|---|
| <%= t("admin.sessions.index.table.match") %> | +<%= t("admin.sessions.index.table.status") %> | +<%= t("admin.sessions.index.table.disconnects") %> | +<%= t("admin.sessions.index.table.link") %> | ++ |
| <%= link_to "Dettaglio", admin_session_path(s) %> | +<%= link_to t("admin.sessions.index.detail"), admin_session_path(s) %> |
| Tipo | Quando | Meta |
|---|---|---|
| <%= t("admin.sessions.show.table.type") %> | +<%= t("admin.sessions.show.table.when") %> | +<%= t("admin.sessions.show.table.meta") %> | +