From c98c24415092db1aeafddb6e1c8056013f4fd143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Verg=C3=A9s?= Date: Wed, 16 Oct 2024 08:33:04 +0200 Subject: [PATCH 01/61] Improve components and spaces sharing with administrable tokens (#13221) * remove share_tokens from admin/components/_form * add routes * add component name on index title * add form and new views * fix index styles * refactor share_token form * refactor share_token form * add locales keys * add automatic_token attribute * refactor createsharetoken command * remove target_blank * add more methods to sharetokenform * refactor token_for definition * add edit and update method to controller * fix routing error * add create and update command specs * fix spelling error * fix spelling error * add share_token_form spec * remove target_ blank from edit action * remove set_default_expiration method * add collection_radio_buttonts * fix no expitration bug * add registered_only to decidim_share_tokens migration * add more checks to share_tokens_form spec * add more checks to commands create and update share_token spec * add copy clipboard functionality * fix lint errors * add is_active_link for share_tokens_path * remove space detected * fix noMethodError on user_can_preview_component? method * add enforce_permission to controller * fix manage_components_share_tokens spec * fix manage_process_components_examples spec * fix share_token_spec * add share_tokens routes on initiatives-module * remove unused i18n keys * add more checks to manage_component_share_tokens * fix has to edit a share token case check * add spec check allows copying the share link from the share token * save clipboard-copy-label-original * fix clipboard js * fix validations and views * add specs * update permissions * update documentation * add help text * allow to manage participatory spaces share tokens * add space specs * add preview specs * fix clipboard * fix specs * fix new minimum page items * trailing spaces * use standard datepicker * fix surveys component actions * make spec deterministic * fix specs * debug * prevent token repetition in parallel tests * apply corrections * fix typo * fix typo * harmonize copies * apply review * lint * change test * apply review * add td sizes * normalize sizes * add action logs * add model specs * fix title interpolation * fix creat command spec --------- Co-authored-by: elviabth --- ...ew_accountability_with_share_token_spec.rb | 4 +- .../decidim/admin/create_share_token.rb | 39 +++ .../decidim/admin/destroy_share_token.rb | 22 ++ .../decidim/admin/update_share_token.rb | 24 ++ .../decidim/admin/share_tokens_controller.rb | 116 ++++++++- .../forms/decidim/admin/share_token_form.rb | 55 ++++ .../admin/components/_actions.html.erb | 5 +- .../decidim/admin/components/_form.html.erb | 9 - .../decidim/admin/share_tokens/_form.html.erb | 52 ++++ .../admin/share_tokens/_share_tokens.html.erb | 45 ---- .../decidim/admin/share_tokens/edit.html.erb | 33 +++ .../decidim/admin/share_tokens/index.html.erb | 47 ++++ .../decidim/admin/share_tokens/new.html.erb | 69 +++++ decidim-admin/config/locales/en.yml | 59 ++++- decidim-admin/config/routes.rb | 2 - .../decidim/admin/create_share_token_spec.rb | 97 +++++++ .../decidim/admin/destroy_share_token_spec.rb | 45 ++++ .../decidim/admin/update_share_token_spec.rb | 84 +++++++ .../spec/forms/share_token_form_spec.rb | 120 +++++++++ .../admin/assembly_share_tokens_controller.rb | 18 ++ .../component_share_tokens_controller.rb | 18 ++ .../app/models/decidim/assembly.rb | 5 + .../decidim/assemblies/permissions.rb | 9 + .../admin/assemblies/index.html.erb | 7 + .../lib/decidim/assemblies/admin_engine.rb | 3 + .../lib/decidim/assemblies/menu.rb | 8 + .../manage_assembly_components_examples.rb | 2 - ...es_assembly_component_share_tokens_spec.rb | 12 + ...dmin_manages_assembly_share_tokens_spec.rb | 21 ++ .../preview_assembly_with_share_token_spec.rb | 11 + .../preview_blogs_with_share_token_spec.rb | 4 +- .../preview_budgets_with_share_token_spec.rb | 4 +- .../component_share_tokens_controller.rb | 18 ++ .../conference_share_tokens_controller.rb | 18 ++ .../app/models/decidim/conference.rb | 5 + .../decidim/conferences/permissions.rb | 13 +- .../admin/conferences/index.html.erb | 5 + .../lib/decidim/conferences/admin_engine.rb | 3 + .../lib/decidim/conferences/menu.rb | 8 + .../manage_conference_components_examples.rb | 2 - ..._conference_component_share_tokens_spec.rb | 12 + ...in_manages_conference_share_tokens_spec.rb | 19 ++ ...review_conference_with_share_token_spec.rb | 11 + .../concerns/decidim/needs_permission.rb | 3 +- .../decidim/application_controller.rb | 6 + .../decidim/components/base_controller.rb | 6 +- .../decidim/homepage_controller.rb | 1 - decidim-core/app/models/decidim/component.rb | 2 +- .../app/models/decidim/share_token.rb | 46 +++- .../app/packs/src/decidim/clipboard.js | 27 +- .../app/permissions/decidim/permissions.rb | 3 +- .../admin_log/share_token_presenter.rb | 39 +++ decidim-core/config/locales/en.yml | 7 + ...registered_only_to_decidim_share_tokens.rb | 7 + .../lib/decidim/component_manifest.rb | 7 + decidim-core/lib/decidim/core/test.rb | 4 +- .../lib/decidim/core/test/factories.rb | 4 + .../manage_component_share_tokens.rb | 83 ------ .../manage_share_tokens_examples.rb | 237 ++++++++++++++++++ ...iew_component_with_share_token_examples.rb | 49 ---- .../preview_with_share_token_examples.rb | 96 +++++++ .../spec/models/decidim/share_token_spec.rb | 77 +++++- .../preview_debates_with_share_token_spec.rb | 4 +- .../component_share_tokens_controller.rb | 18 ++ .../initiative_share_tokens_controller.rb | 18 ++ .../app/models/decidim/initiative.rb | 5 + .../decidim/initiatives/admin/permissions.rb | 7 + .../decidim/initiatives/permissions.rb | 7 + .../admin/initiatives/index.html.erb | 7 + .../lib/decidim/initiatives/admin_engine.rb | 3 + .../lib/decidim/initiatives/menu.rb | 8 + ..._initiative_component_share_tokens_spec.rb | 15 ++ ...dmin_manages_initiative_components_spec.rb | 2 - ...in_manages_initiative_share_tokens_spec.rb | 16 ++ ...review_initiative_with_share_token_spec.rb | 11 + .../preview_meetings_with_share_token_spec.rb | 4 +- .../preview_pages_with_share_token_spec.rb | 4 +- .../component_share_tokens_controller.rb | 18 ++ ...ipatory_process_share_tokens_controller.rb | 16 ++ .../models/decidim/participatory_process.rb | 5 + .../participatory_processes/permissions.rb | 9 + .../participatory_processes/index.html.erb | 5 + .../participatory_processes/admin_engine.rb | 3 + .../decidim/participatory_processes/menu.rb | 8 + .../manage_process_components_examples.rb | 20 -- ...ory_process_component_share_tokens_spec.rb | 12 + ...participatory_process_share_tokens_spec.rb | 19 ++ ...icipatory_process_with_share_token_spec.rb | 11 + ...preview_proposals_with_share_token_spec.rb | 4 +- ...review_sortitions_with_share_token_spec.rb | 4 +- .../surveys/admin/component/_actions.html.erb | 11 +- decidim-surveys/spec/system/survey_spec.rb | 2 +- docs/modules/develop/pages/share_tokens.adoc | 164 +++++++++++- 93 files changed, 2005 insertions(+), 302 deletions(-) create mode 100644 decidim-admin/app/commands/decidim/admin/create_share_token.rb create mode 100644 decidim-admin/app/commands/decidim/admin/destroy_share_token.rb create mode 100644 decidim-admin/app/commands/decidim/admin/update_share_token.rb create mode 100644 decidim-admin/app/forms/decidim/admin/share_token_form.rb create mode 100644 decidim-admin/app/views/decidim/admin/share_tokens/_form.html.erb delete mode 100644 decidim-admin/app/views/decidim/admin/share_tokens/_share_tokens.html.erb create mode 100644 decidim-admin/app/views/decidim/admin/share_tokens/edit.html.erb create mode 100644 decidim-admin/app/views/decidim/admin/share_tokens/index.html.erb create mode 100644 decidim-admin/app/views/decidim/admin/share_tokens/new.html.erb create mode 100644 decidim-admin/spec/commands/decidim/admin/create_share_token_spec.rb create mode 100644 decidim-admin/spec/commands/decidim/admin/destroy_share_token_spec.rb create mode 100644 decidim-admin/spec/commands/decidim/admin/update_share_token_spec.rb create mode 100644 decidim-admin/spec/forms/share_token_form_spec.rb create mode 100644 decidim-assemblies/app/controllers/decidim/assemblies/admin/assembly_share_tokens_controller.rb create mode 100644 decidim-assemblies/app/controllers/decidim/assemblies/admin/component_share_tokens_controller.rb create mode 100644 decidim-assemblies/spec/system/admin/admin_manages_assembly_component_share_tokens_spec.rb create mode 100644 decidim-assemblies/spec/system/admin/admin_manages_assembly_share_tokens_spec.rb create mode 100644 decidim-assemblies/spec/system/preview_assembly_with_share_token_spec.rb create mode 100644 decidim-conferences/app/controllers/decidim/conferences/admin/component_share_tokens_controller.rb create mode 100644 decidim-conferences/app/controllers/decidim/conferences/admin/conference_share_tokens_controller.rb create mode 100644 decidim-conferences/spec/system/admin/admin_manages_conference_component_share_tokens_spec.rb create mode 100644 decidim-conferences/spec/system/admin/admin_manages_conference_share_tokens_spec.rb create mode 100644 decidim-conferences/spec/system/preview_conference_with_share_token_spec.rb create mode 100644 decidim-core/app/presenters/decidim/admin_log/share_token_presenter.rb create mode 100644 decidim-core/db/migrate/20240717093514_add_registered_only_to_decidim_share_tokens.rb delete mode 100644 decidim-core/lib/decidim/core/test/shared_examples/manage_component_share_tokens.rb create mode 100644 decidim-core/lib/decidim/core/test/shared_examples/manage_share_tokens_examples.rb delete mode 100644 decidim-core/lib/decidim/core/test/shared_examples/preview_component_with_share_token_examples.rb create mode 100644 decidim-core/lib/decidim/core/test/shared_examples/preview_with_share_token_examples.rb create mode 100644 decidim-initiatives/app/controllers/decidim/initiatives/admin/component_share_tokens_controller.rb create mode 100644 decidim-initiatives/app/controllers/decidim/initiatives/admin/initiative_share_tokens_controller.rb create mode 100644 decidim-initiatives/spec/system/admin/admin_manages_initiative_component_share_tokens_spec.rb create mode 100644 decidim-initiatives/spec/system/admin/admin_manages_initiative_share_tokens_spec.rb create mode 100644 decidim-initiatives/spec/system/preview_initiative_with_share_token_spec.rb create mode 100644 decidim-participatory_processes/app/controllers/decidim/participatory_processes/admin/component_share_tokens_controller.rb create mode 100644 decidim-participatory_processes/app/controllers/decidim/participatory_processes/admin/participatory_process_share_tokens_controller.rb create mode 100644 decidim-participatory_processes/spec/system/admin/admin_manages_participatory_process_component_share_tokens_spec.rb create mode 100644 decidim-participatory_processes/spec/system/admin/admin_manages_participatory_process_share_tokens_spec.rb create mode 100644 decidim-participatory_processes/spec/system/preview_participatory_process_with_share_token_spec.rb diff --git a/decidim-accountability/spec/system/preview_accountability_with_share_token_spec.rb b/decidim-accountability/spec/system/preview_accountability_with_share_token_spec.rb index e3701b7eace20..52e838eac9c96 100644 --- a/decidim-accountability/spec/system/preview_accountability_with_share_token_spec.rb +++ b/decidim-accountability/spec/system/preview_accountability_with_share_token_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -describe "Preview accountability with share token" do +describe "preview accountability with a share token" do let(:manifest_name) { "accountability" } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-admin/app/commands/decidim/admin/create_share_token.rb b/decidim-admin/app/commands/decidim/admin/create_share_token.rb new file mode 100644 index 0000000000000..85a3669d6672c --- /dev/null +++ b/decidim-admin/app/commands/decidim/admin/create_share_token.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Decidim + module Admin + # A command with all the business logic to create a share token. + # This command is called from the controller. + class CreateShareToken < Decidim::Commands::CreateResource + fetch_form_attributes :token, :expires_at, :registered_only, :organization, :user, :token_for + + protected + + def resource_class = Decidim::ShareToken + + def extra_params + { + participatory_space: { + title: participatory_space&.title + }, + resource: { + title: component&.name + } + } + end + + def participatory_space + return form.token_for if form.token_for.try(:manifest).is_a?(Decidim::ParticipatorySpaceManifest) + return current_participatory_space if respond_to?(:current_participatory_space) + + component&.participatory_space + end + + def component + return form.token_for if form.token_for.is_a?(Decidim::Component) + + form.token_for.try(:component) + end + end + end +end diff --git a/decidim-admin/app/commands/decidim/admin/destroy_share_token.rb b/decidim-admin/app/commands/decidim/admin/destroy_share_token.rb new file mode 100644 index 0000000000000..f4b9371aabfcf --- /dev/null +++ b/decidim-admin/app/commands/decidim/admin/destroy_share_token.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Decidim + module Admin + # A command with all the business logic to destroy a share token. + # This command is called from the controller. + class DestroyShareToken < Decidim::Commands::DestroyResource + delegate :participatory_space, :component, to: :resource + + def extra_params + { + participatory_space: { + title: participatory_space&.title + }, + resource: { + title: component&.name + } + } + end + end + end +end diff --git a/decidim-admin/app/commands/decidim/admin/update_share_token.rb b/decidim-admin/app/commands/decidim/admin/update_share_token.rb new file mode 100644 index 0000000000000..0a3bfee53db81 --- /dev/null +++ b/decidim-admin/app/commands/decidim/admin/update_share_token.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Decidim + module Admin + # A command with all the business logic to update a share token. + # This command is called from the controller. + class UpdateShareToken < Decidim::Commands::UpdateResource + fetch_form_attributes :expires_at, :registered_only + + delegate :participatory_space, :component, to: :resource + + def extra_params + { + participatory_space: { + title: participatory_space&.title + }, + resource: { + title: component&.name + } + } + end + end + end +end diff --git a/decidim-admin/app/controllers/decidim/admin/share_tokens_controller.rb b/decidim-admin/app/controllers/decidim/admin/share_tokens_controller.rb index 273fcf34fc280..abe6e9a1d043f 100644 --- a/decidim-admin/app/controllers/decidim/admin/share_tokens_controller.rb +++ b/decidim-admin/app/controllers/decidim/admin/share_tokens_controller.rb @@ -2,11 +2,66 @@ module Decidim module Admin + # This is an abstract controller allows sharing unpublished things. + # Final implementation must inherit from this controller and implement the `resource` method. class ShareTokensController < Decidim::Admin::ApplicationController + include Decidim::Admin::Filterable + + helper_method :current_token, :resource, :resource_title, :share_tokens_path + + def index + enforce_permission_to :read, :share_tokens + @share_tokens = filtered_collection + end + + def new + enforce_permission_to :create, :share_tokens + @form = form(ShareTokenForm).instance + end + + def create + enforce_permission_to :create, :share_tokens + @form = form(ShareTokenForm).from_params(params, resource:) + + CreateShareToken.call(@form) do + on(:ok) do + flash[:notice] = I18n.t("share_tokens.create.success", scope: "decidim.admin") + redirect_to share_tokens_path + end + + on(:invalid) do + flash.now[:alert] = I18n.t("share_tokens.create.invalid", scope: "decidim.admin") + render action: "new" + end + end + end + + def edit + enforce_permission_to(:update, :share_tokens, share_token: current_token) + @form = form(ShareTokenForm).from_model(current_token) + end + + def update + enforce_permission_to(:update, :share_tokens, share_token: current_token) + @form = form(ShareTokenForm).from_params(params, resource:) + + UpdateShareToken.call(@form, current_token) do + on(:ok) do + flash[:notice] = I18n.t("share_tokens.update.success", scope: "decidim.admin") + redirect_to share_tokens_path + end + + on(:invalid) do + flash.now[:alert] = I18n.t("share_tokens.update.error", scope: "decidim.admin") + render :edit + end + end + end + def destroy - enforce_permission_to(:destroy, :share_token, share_token:) + enforce_permission_to(:destroy, :share_tokens, share_token: current_token) - Decidim::Commands::DestroyResource.call(share_token, current_user) do + DestroyShareToken.call(current_token, current_user) do on(:ok) do flash[:notice] = I18n.t("share_tokens.destroy.success", scope: "decidim.admin") end @@ -15,15 +70,62 @@ def destroy end end - redirect_back(fallback_location: root_path) + redirect_to share_tokens_path end private - def share_token - @share_token ||= Decidim::ShareToken.where( - organization: current_organization - ).find(params[:id]) + # override this method in the destination controller to specify the resource associated with the shared token (ie: a component) + def resource + raise NotImplementedError + end + + # Override also this method if resource does not respond to a translatable name or title + def resource_title + translated_attribute(resource.try(:name) || resource.title) + end + + # sets the prefix for the route helper methods (this may vary depending on the resource type) + # This setup works fine for participatory spaces and components, override if needed + def route_name + @route_name ||= "#{resource.manifest.route_name}_" + end + + def route_proxy + @route_proxy ||= EngineRouter.admin_proxy(resource.try(:participatory_space) || resource) + end + + # returns the proper path for managing a share token according to the resource + # this works fine for components and participatory spaces, override if needed + def share_tokens_path(method = :index, options = {}) + args = resource.is_a?(Decidim::Component) ? [resource, options] : [options] + + case method + when :index, :create + route_proxy.send("#{route_name}share_tokens_path", *args) + when :new + route_proxy.send("new_#{route_name}share_token_path", *args) + when :update, :destroy + route_proxy.send("#{route_name}share_token_path", *args) + when :edit + route_proxy.send("edit_#{route_name}share_token_path", *args) + end + end + + def base_query + collection + end + + def collection + @collection ||= Decidim::ShareToken.where(organization: current_organization, token_for: resource) + end + + def filters + [] + end + + def current_token + @current_token ||= collection.find(params[:id]) end end end diff --git a/decidim-admin/app/forms/decidim/admin/share_token_form.rb b/decidim-admin/app/forms/decidim/admin/share_token_form.rb new file mode 100644 index 0000000000000..849d52f6259bd --- /dev/null +++ b/decidim-admin/app/forms/decidim/admin/share_token_form.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Decidim + module Admin + class ShareTokenForm < Decidim::Form + mimic :share_token + + attribute :token, String + attribute :automatic_token, Boolean, default: true + attribute :expires_at, Decidim::Attributes::TimeWithZone + attribute :no_expiration, Boolean, default: true + attribute :registered_only, Boolean, default: false + + validates :token, presence: true, if: ->(form) { form.automatic_token.blank? } + validate :token_uniqueness, if: ->(form) { form.automatic_token.blank? } + + validates_format_of :token, with: /\A[a-zA-Z0-9_-]+\z/, allow_blank: true + validates :expires_at, presence: true, if: ->(form) { form.no_expiration.blank? } + + def map_model(model) + self.no_expiration = model.expires_at.blank? + end + + def token + super.strip.upcase.gsub(/\s+/, "-") if super.present? + end + + def expires_at + return nil if no_expiration.present? + + super + end + + def token_for + context[:resource] + end + + def organization + context[:current_organization] + end + + def user + context[:current_user] + end + + private + + def token_uniqueness + return unless Decidim::ShareToken.where(organization:, token_for:, token:).where.not(id:).any? + + errors.add(:token, :taken) + end + end + end +end diff --git a/decidim-admin/app/views/decidim/admin/components/_actions.html.erb b/decidim-admin/app/views/decidim/admin/components/_actions.html.erb index 23904d799f08d..3e5527c59e300 100644 --- a/decidim-admin/app/views/decidim/admin/components/_actions.html.erb +++ b/decidim-admin/app/views/decidim/admin/components/_actions.html.erb @@ -4,11 +4,12 @@ <% end %> -<% if allowed_to? :share, :component, component: component %> - <%= icon_link_to "share-line", url_for(action: :share, id: component, controller: "components"), t("actions.share", scope: "decidim.admin"), target: :blank, class: "action-icon--share" %> +<% if component.manifest.admin_engine && allowed_to?(:share, :component, component: component) %> + <%= icon_link_to "share-line", component_share_tokens_path(component_id: component), t("actions.share_tokens", scope: "decidim.admin"), class: "action-icon--share" %> <% else %> <% end %> + <% if allowed_to? :update, :component, component: component %> <%= icon_link_to "settings-4-line", url_for(action: :edit, id: component, controller: "components"), t("actions.configure", scope: "decidim.admin"), class: "action-icon--configure" %> <% else %> diff --git a/decidim-admin/app/views/decidim/admin/components/_form.html.erb b/decidim-admin/app/views/decidim/admin/components/_form.html.erb index 7e50d45c4d01e..8369f16c7e3f8 100644 --- a/decidim-admin/app/views/decidim/admin/components/_form.html.erb +++ b/decidim-admin/app/views/decidim/admin/components/_form.html.erb @@ -114,13 +114,4 @@ <% end %> - <% if component && component.persisted? && !component.published? %> -
-
-
- <%= render partial: "decidim/admin/share_tokens/share_tokens", locals: { share_tokens: form.object.share_tokens } %> -
-
-
- <% end %> diff --git a/decidim-admin/app/views/decidim/admin/share_tokens/_form.html.erb b/decidim-admin/app/views/decidim/admin/share_tokens/_form.html.erb new file mode 100644 index 0000000000000..5b6e0be978c4e --- /dev/null +++ b/decidim-admin/app/views/decidim/admin/share_tokens/_form.html.erb @@ -0,0 +1,52 @@ +
+ + <%= form.collection_radio_buttons :no_expiration, [[true, t("share_tokens.form.never_expire", scope: "decidim.admin")], [false, t("share_tokens.form.custom", scope: "decidim.admin")]], :first, :last do |b| %> +
+ <%= b.radio_button %> + <%= b.label %> +
+ <% end %> + +
+ +
+ + <%= form.collection_radio_buttons :registered_only, [ + [t("share_tokens.form.true", scope: "decidim.admin"), true], + [t("share_tokens.form.false", scope: "decidim.admin"), false] + ], :last, :first do |b| %> +
+ <%= b.label do %> + <%= b.radio_button %> + <%= b.text %> + <% end %> +
+ <% end %> +
+ + diff --git a/decidim-admin/app/views/decidim/admin/share_tokens/_share_tokens.html.erb b/decidim-admin/app/views/decidim/admin/share_tokens/_share_tokens.html.erb deleted file mode 100644 index 4b9d7999c96ff..0000000000000 --- a/decidim-admin/app/views/decidim/admin/share_tokens/_share_tokens.html.erb +++ /dev/null @@ -1,45 +0,0 @@ - diff --git a/decidim-admin/app/views/decidim/admin/share_tokens/edit.html.erb b/decidim-admin/app/views/decidim/admin/share_tokens/edit.html.erb new file mode 100644 index 0000000000000..350e59e0ff4d1 --- /dev/null +++ b/decidim-admin/app/views/decidim/admin/share_tokens/edit.html.erb @@ -0,0 +1,33 @@ +<% add_decidim_page_title(t(".title", name: resource_title)) %> + +
+

+ <%= t ".title", name: resource_title %> + <%= t("share_tokens.index.back_to_share_tokens", scope: "decidim.admin") %> +

+
+
+ <%= decidim_form_for(@form, url: share_tokens_path(:update, id: current_token), html: { class: "form-defaults form edit_share_token" }) do |f| %> +
+
+
+
+
+ +
+ <%= text_field_tag :token, current_token.token, id: "share_token-token", aria: { label: t("token", scope: "decidim.admin.models.share_token.fields") }, disabled: true %> + +
+
+ <%= render partial: "form", object: f %> +
+
+
+
+
+
+ <%= f.submit t(".update"), class: "button button__sm button__secondary" %> +
+
+ <% end %> +
diff --git a/decidim-admin/app/views/decidim/admin/share_tokens/index.html.erb b/decidim-admin/app/views/decidim/admin/share_tokens/index.html.erb new file mode 100644 index 0000000000000..c98cbcb7db298 --- /dev/null +++ b/decidim-admin/app/views/decidim/admin/share_tokens/index.html.erb @@ -0,0 +1,47 @@ + +<%= decidim_paginate @share_tokens %> diff --git a/decidim-admin/app/views/decidim/admin/share_tokens/new.html.erb b/decidim-admin/app/views/decidim/admin/share_tokens/new.html.erb new file mode 100644 index 0000000000000..69b0eb638ca87 --- /dev/null +++ b/decidim-admin/app/views/decidim/admin/share_tokens/new.html.erb @@ -0,0 +1,69 @@ +<% add_decidim_page_title(t(".title", name: resource_title)) %> + +
+

+ <%= t ".title", name: resource_title %> + <%= t("share_tokens.index.back_to_share_tokens", scope: "decidim.admin") %> +

+
+
+ <%= decidim_form_for(@form, url: share_tokens_path(:create), html: { class: "form-defaults form new_share_token" }) do |f| %> +
+
+
+
+
+ + <%= f.collection_radio_buttons :automatic_token, [ + [t("share_tokens.form.automatic", scope: "decidim.admin"), true], + [t("share_tokens.form.custom", scope: "decidim.admin"), false] + ], :last, :first do |b| %> +
+ <%= b.label do %> + <%= b.radio_button %> + <%= b.text %> + <% end %> +
+ <% end %> + +
+ <%= render partial: "form", object: f %> +
+
+
+
+
+
+ <%= f.submit t(".create"), class: "button button__sm button__secondary" %> +
+
+ <% end %> +
+ + diff --git a/decidim-admin/config/locales/en.yml b/decidim-admin/config/locales/en.yml index 97ff698b8a72c..e370f8b7c8df8 100644 --- a/decidim-admin/config/locales/en.yml +++ b/decidim-admin/config/locales/en.yml @@ -200,6 +200,7 @@ en: reject: Reject send_me_a_test_email: Send me a test email share: Share + share_tokens: Access links user: new: New admin verify: Verify @@ -592,6 +593,7 @@ en: scopes: Scopes see_site: See site settings: Settings + share_tokens: Access links static_page_topics: Topics static_pages: Pages user_groups: Groups @@ -647,12 +649,11 @@ en: plural: Plural share_token: fields: - created_at: Created at + actions: Actions expires_at: Expires at - last_used_at: Last time used + registered_only: Registered only? times_used: Times used - token: Token - user: Created by + token: Access link static_page: fields: created_at: Created at @@ -965,16 +966,50 @@ en: success: Scope updated successfully share_tokens: actions: - confirm_destroy: Are you sure you want to delete this token? + confirm_destroy: Are you sure you want to delete this access? + copy_link: Copy link destroy: Delete - share: Share + edit: Edit + preview: Preview + create: + invalid: There was a problem generating the access link. + success: Access link created successfully. destroy: - error: There was a problem destroying the token. - success: Token destroyed successfully. - share_tokens: - empty: There are no active tokens. - help: These tokens are used to publicly share this unpublished resource to any user. They will be hidden when the resource is published. Click on the token's share icon to visit the shareable URL. - title: Share tokens + error: There was a problem destroying the access link. + success: Access link successfully destroyed. + edit: + title: 'Edit access links for: %{name}' + update: Update + form: + automatic: Automatic + custom: Custom + custom_expiration: Custom expiration + custom_token: Custom word + expires_at: Expires at + 'false': 'No' + never_expire: Never + registered_only: Registered only? + token: Access key + 'true': 'Yes' + index: + back_to_share_tokens: Back to access links + copied: Access link Copied! + copy_message: The text was successfully copied to clipboard. + create_new_token: Create your first access link! + empty_html: There are no active access links. %{new_token_link} + never: Never + new_share_token_button: New access link + share_tokens_help_html: | + Create and share an access link to allow others to view this unpublished resource. + Access links can be valid for registered participants only or have and expiration date if necessary. + To share a new access link with someone, create it and then copy the link using the "%{clipboard} clipboard" action icon. + title: 'access links for: %{name}' + new: + create: Create + title: 'New access link for: %{name}' + update: + error: There was a problem updating this access. + success: Access link updated successfully. shared: adjacent_navigation: next: Next diff --git a/decidim-admin/config/routes.rb b/decidim-admin/config/routes.rb index 1f1e85dece383..4a73f91af43ee 100644 --- a/decidim-admin/config/routes.rb +++ b/decidim-admin/config/routes.rb @@ -109,8 +109,6 @@ put :accept end - resources :share_tokens, only: :destroy - resources :moderations, controller: "global_moderations" do member do put :unreport diff --git a/decidim-admin/spec/commands/decidim/admin/create_share_token_spec.rb b/decidim-admin/spec/commands/decidim/admin/create_share_token_spec.rb new file mode 100644 index 0000000000000..4c43b5631c185 --- /dev/null +++ b/decidim-admin/spec/commands/decidim/admin/create_share_token_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim::Admin + describe CreateShareToken do + subject { described_class.new(form) } + + let(:organization) { create(:organization) } + let(:current_user) { create(:user, :admin, organization:) } + let(:component) { create(:component, participatory_space: create(:participatory_process, organization:)) } + + let(:form) do + ShareTokenForm.from_params( + token:, + expires_at:, + automatic_token:, + no_expiration:, + registered_only: + ).with_context( + current_user:, + current_organization: organization, + resource: component + ) + end + + let(:token) { "ABC123" } + let(:expires_at) { Time.zone.today + 10.days } + let(:automatic_token) { false } + let(:no_expiration) { false } + let(:registered_only) { true } + let(:extra) do + { + participatory_space: { + title: component.participatory_space.title + }, + resource: { + title: component.name + } + } + end + + context "when the form is valid" do + it "creates a share token" do + expect { subject.call }.to change(Decidim::ShareToken, :count).by(1) + + share_token = Decidim::ShareToken.last + expect(share_token.token).to eq(token) + expect(share_token.expires_at).to eq(expires_at) + expect(share_token.registered_only).to be(true) + expect(share_token.organization).to eq(organization) + expect(share_token.user).to eq(current_user) + expect(share_token.token_for).to eq(component) + end + + it "broadcasts :ok with the resource" do + expect(subject).to receive(:broadcast).with(:ok, instance_of(Decidim::ShareToken)) + subject.call + end + + it "traces the action", versioning: true do + expect(Decidim.traceability) + .to receive(:create!) + .with(Decidim::ShareToken, current_user, + { + expires_at:, + registered_only:, + token:, + organization:, + token_for: component, + user: current_user + }, + extra) + .and_call_original + + expect { subject.call }.to change(Decidim::ActionLog, :count) + action_log = Decidim::ActionLog.last + expect(action_log.version).to be_present + end + end + + context "when the form is invalid" do + before do + allow(form).to receive(:invalid?).and_return(true) + end + + it "does not create a share token" do + expect { subject.call }.not_to(change(Decidim::ShareToken, :count)) + end + + it "broadcasts :invalid" do + expect(subject).to receive(:broadcast).with(:invalid) + subject.call + end + end + end +end diff --git a/decidim-admin/spec/commands/decidim/admin/destroy_share_token_spec.rb b/decidim-admin/spec/commands/decidim/admin/destroy_share_token_spec.rb new file mode 100644 index 0000000000000..1c66c2d3b77d1 --- /dev/null +++ b/decidim-admin/spec/commands/decidim/admin/destroy_share_token_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim::Admin + describe DestroyShareToken do + subject { described_class.new(share_token, user) } + + let(:organization) { create(:organization) } + let(:user) { create(:user, :admin, :confirmed, organization:) } + let(:share_token) { create(:share_token, organization:, user:) } + let(:extra) do + { + participatory_space: { + title: share_token.participatory_space.title + }, + resource: { + title: share_token.component.name + } + } + end + + it "destroys the share_token" do + subject.call + expect { share_token.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + + it "broadcasts ok" do + expect do + subject.call + end.to broadcast(:ok) + end + + it "traces the action", versioning: true do + expect(Decidim.traceability) + .to receive(:perform_action!) + .with(:delete, share_token, user, extra) + .and_call_original + + expect { subject.call }.to change(Decidim::ActionLog, :count) + action_log = Decidim::ActionLog.last + expect(action_log.version).to be_present + end + end +end diff --git a/decidim-admin/spec/commands/decidim/admin/update_share_token_spec.rb b/decidim-admin/spec/commands/decidim/admin/update_share_token_spec.rb new file mode 100644 index 0000000000000..db92c4319fbac --- /dev/null +++ b/decidim-admin/spec/commands/decidim/admin/update_share_token_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim::Admin + describe UpdateShareToken do + subject { described_class.new(form, share_token) } + + let(:organization) { create(:organization) } + let(:user) { create(:user, :admin, organization:) } + let(:component) { create(:component, participatory_space: create(:participatory_process, organization:)) } + let!(:share_token) { create(:share_token, organization:, user:, token_for: component) } + + let(:form) do + ShareTokenForm.from_params( + token:, + expires_at:, + automatic_token:, + no_expiration:, + registered_only: + ).with_context( + current_user: user, + current_organization: organization, + resource: component + ) + end + + let(:token) { "ABCDEF97765544" } + let(:expires_at) { Time.zone.today + 20.days } + let(:automatic_token) { false } + let(:no_expiration) { false } + let(:registered_only) { false } + let(:extra) do + { + participatory_space: { + title: component.participatory_space.title + }, + resource: { + title: component.name + } + } + end + + context "when the form is valid" do + it "updates the expiration date" do + expect { subject.call }.to change { share_token.reload.expires_at }.to(expires_at) + .and change { share_token.reload.registered_only }.to(registered_only) + end + + it "broadcasts :ok with the resource" do + expect(subject).to receive(:broadcast).with(:ok, share_token) + subject.call + end + + it "traces the action", versioning: true do + expect(Decidim.traceability) + .to receive(:update!) + .with(share_token, user, { expires_at:, registered_only: }, extra) + .and_call_original + + expect { subject.call }.to change(Decidim::ActionLog, :count) + + action_log = Decidim::ActionLog.last + expect(action_log.version).to be_present + expect(action_log.version.event).to eq "update" + end + end + + context "when the form is invalid" do + before do + allow(form).to receive(:invalid?).and_return(true) + end + + it "does not update the share token" do + expect { subject.call }.not_to(change { share_token.reload.attributes }) + end + + it "broadcasts :invalid" do + expect(subject).to receive(:broadcast).with(:invalid) + subject.call + end + end + end +end diff --git a/decidim-admin/spec/forms/share_token_form_spec.rb b/decidim-admin/spec/forms/share_token_form_spec.rb new file mode 100644 index 0000000000000..1bc0a0a4ae32a --- /dev/null +++ b/decidim-admin/spec/forms/share_token_form_spec.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim::Admin + describe ShareTokenForm do + let(:organization) { create(:organization) } + let(:current_user) { create(:user, :admin, organization:) } + let(:component) { create(:component, participatory_space: create(:participatory_process, organization:)) } + + let(:form) do + described_class.from_params( + token:, + automatic_token:, + expires_at:, + no_expiration:, + registered_only: + ).with_context( + current_user:, + current_organization: organization, + resource: component + ) + end + + let(:token) { "ABC123" } + let(:automatic_token) { true } + let(:expires_at) { Time.zone.today + 3.days } + let(:no_expiration) { false } + let(:registered_only) { true } + + it "returns defaults" do + expect(form.token).to eq("ABC123") + expect(form.automatic_token).to be(true) + expect(form.expires_at).to eq(Time.zone.today + 3.days) + expect(form.no_expiration).to be(false) + expect(form.registered_only).to be(true) + end + + context "when automatic_token validation is false" do + let(:automatic_token) { false } + + it "validates presence of token" do + form.token = nil + expect(form).to be_invalid + expect(form.errors[:token]).to include("cannot be blank") + end + + context "when automatic_token is set" do + let(:token) { "" } + let(:automatic_token) { true } + + it "does not validate presence of token" do + expect(form).to be_valid + end + end + end + + context "when expires_at is nil" do + let(:expires_at) { nil } + + it "validates presence of expires_at" do + expect(form).to be_invalid + expect(form.errors[:expires_at]).to include("cannot be blank") + end + + context "when no_expiration is set" do + let(:no_expiration) { true } + + it "does not expires" do + expect(form).to be_valid + end + end + end + + context "when token is custom" do + let(:token) { "abc 123 " } + + it "returns the token in uppercase" do + expect(form.token).to eq("ABC-123") + end + + context "and has strange characters" do + let(:token) { "abc 123 !@#$%^&*()_+" } + + it "returns the token in uppercase" do + expect(form).to be_invalid + expect(form.errors[:token]).to include("is invalid") + end + end + end + + context "when token exists" do + let(:automatic_token) { false } + let!(:share_token) { create(:share_token, organization:, token_for: component, token:) } + + it "validates uniqueness of token" do + expect(form).to be_invalid + expect(form.errors[:token]).to include("has already been taken") + end + end + + describe "#token_for" do + it "returns the component from the context" do + expect(form.token_for).to eq(component) + end + end + + describe "#organization" do + it "returns the current organization from the context" do + expect(form.organization).to eq(organization) + end + end + + describe "#user" do + it "returns the current user from the context" do + expect(form.user).to eq(current_user) + end + end + end +end diff --git a/decidim-assemblies/app/controllers/decidim/assemblies/admin/assembly_share_tokens_controller.rb b/decidim-assemblies/app/controllers/decidim/assemblies/admin/assembly_share_tokens_controller.rb new file mode 100644 index 0000000000000..9d1f1854d35bf --- /dev/null +++ b/decidim-assemblies/app/controllers/decidim/assemblies/admin/assembly_share_tokens_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Decidim + module Assemblies + module Admin + # This controller allows sharing unpublished things. + # It is targeted for customizations for sharing unpublished things that lives under + # an assembly. + class AssemblyShareTokensController < Decidim::Admin::ShareTokensController + include Concerns::AssemblyAdmin + + def resource + current_assembly + end + end + end + end +end diff --git a/decidim-assemblies/app/controllers/decidim/assemblies/admin/component_share_tokens_controller.rb b/decidim-assemblies/app/controllers/decidim/assemblies/admin/component_share_tokens_controller.rb new file mode 100644 index 0000000000000..e03eb766121e0 --- /dev/null +++ b/decidim-assemblies/app/controllers/decidim/assemblies/admin/component_share_tokens_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Decidim + module Assemblies + module Admin + # This controller allows sharing unpublished things. + # It is targeted for customizations for sharing unpublished things that lives under + # an assembly. + class ComponentShareTokensController < Decidim::Admin::ShareTokensController + include Concerns::AssemblyAdmin + + def resource + @resource ||= current_participatory_space.components.find(params[:component_id]) + end + end + end + end +end diff --git a/decidim-assemblies/app/models/decidim/assembly.rb b/decidim-assemblies/app/models/decidim/assembly.rb index 09d8e9031b3e3..c77b715ef0988 100644 --- a/decidim-assemblies/app/models/decidim/assembly.rb +++ b/decidim-assemblies/app/models/decidim/assembly.rb @@ -36,6 +36,7 @@ class Assembly < ApplicationRecord include Decidim::TranslatableResource include Decidim::HasArea include Decidim::FilterableResource + include Decidim::ShareableWithToken CREATED_BY = %w(city_council public others).freeze @@ -168,6 +169,10 @@ def self.ransackable_scopes(_auth_object = nil) [:with_any_area, :with_any_scope, :with_any_type] end + def shareable_url(share_token) + EngineRouter.main_proxy(self).assembly_url(self, share_token: share_token.token) + end + private # When an assembly changes their parent, we need to update the parents_path attribute diff --git a/decidim-assemblies/app/permissions/decidim/assemblies/permissions.rb b/decidim-assemblies/app/permissions/decidim/assemblies/permissions.rb index 162856418508e..741d8c91de756 100644 --- a/decidim-assemblies/app/permissions/decidim/assemblies/permissions.rb +++ b/decidim-assemblies/app/permissions/decidim/assemblies/permissions.rb @@ -127,6 +127,7 @@ def public_read_assembly_action? return disallow! unless can_view_private_space? return allow! if user&.admin? return allow! if assembly.published? + return allow! if user_can_preview_space? toggle_allow(can_manage_assembly?) end @@ -267,6 +268,7 @@ def assembly_admin_action? :assembly_user_role, :assembly_member, :export_space, + :share_tokens, :import ].include?(permission_action.subject) allow! if is_allowed @@ -286,11 +288,18 @@ def org_admin_action? :assembly_user_role, :assembly_member, :export_space, + :share_tokens, :import ].include?(permission_action.subject) allow! if is_allowed end + def user_can_preview_space? + context[:share_token].present? && Decidim::ShareToken.use!(token_for: assembly, token: context[:share_token], user:) + rescue ActiveRecord::RecordNotFound, StandardError + nil + end + # Checks if the permission_action is to read the admin assemblies list or # not. def read_assembly_list_permission_action? diff --git a/decidim-assemblies/app/views/decidim/assemblies/admin/assemblies/index.html.erb b/decidim-assemblies/app/views/decidim/assemblies/admin/assemblies/index.html.erb index 6773dfed6ce8d..391c7c91a1d2a 100644 --- a/decidim-assemblies/app/views/decidim/assemblies/admin/assemblies/index.html.erb +++ b/decidim-assemblies/app/views/decidim/assemblies/admin/assemblies/index.html.erb @@ -65,6 +65,7 @@ <% end %> + <% if assembly.published? %> <%= t("assemblies.index.published", scope: "decidim.admin") %> <% else %> @@ -72,6 +73,12 @@ <% end %> + <% if allowed_to? :read, :share_tokens, current_participatory_space: assembly %> + <%= icon_link_to "share-line", decidim_admin_assemblies.assembly_share_tokens_path(assembly), t("actions.share_tokens", scope: "decidim.admin"), class: "action-icon--new" %> + <% else %> + + <% end %> + <% if allowed_to? :update, :assembly, assembly: assembly %> <%= icon_link_to "pencil-line", edit_assembly_path(assembly), t("actions.configure", scope: "decidim.admin"), class: "action-icon--new" %> <% else %> diff --git a/decidim-assemblies/lib/decidim/assemblies/admin_engine.rb b/decidim-assemblies/lib/decidim/assemblies/admin_engine.rb index 6256e34abd99f..1408f21dd26d8 100644 --- a/decidim-assemblies/lib/decidim/assemblies/admin_engine.rb +++ b/decidim-assemblies/lib/decidim/assemblies/admin_engine.rb @@ -53,6 +53,7 @@ class AdminEngine < ::Rails::Engine put :unpublish get :share end + resources :component_share_tokens, except: [:show], path: "share_tokens", as: "share_tokens" resources :exports, only: :create resources :imports, only: [:new, :create] do get :example, on: :collection @@ -79,6 +80,8 @@ class AdminEngine < ::Rails::Engine end end end + + resources :assembly_share_tokens, except: [:show], path: "share_tokens" end scope "/assemblies/:assembly_slug/components/:component_id/manage" do diff --git a/decidim-assemblies/lib/decidim/assemblies/menu.rb b/decidim-assemblies/lib/decidim/assemblies/menu.rb index ca721b929a1d8..382e2ec9bb857 100644 --- a/decidim-assemblies/lib/decidim/assemblies/menu.rb +++ b/decidim-assemblies/lib/decidim/assemblies/menu.rb @@ -78,6 +78,7 @@ def self.register_admin_assemblies_components_menu! active: is_active_link?(manage_component_path(component)) || is_active_link?(decidim_admin_assemblies.edit_component_path(current_participatory_space, component)) || is_active_link?(decidim_admin_assemblies.edit_component_permissions_path(current_participatory_space, component)) || + is_active_link?(decidim_admin_assemblies.component_share_tokens_path(current_participatory_space, component)) || participatory_space_active_link?(component), if: component.manifest.admin_engine && user_role_config.component_is_accessible?(component.manifest_name) end @@ -145,6 +146,13 @@ def self.register_admin_assembly_menu! decidim_admin_assemblies.moderations_path(current_participatory_space), icon_name: "flag-line", if: allowed_to?(:read, :moderation, assembly: current_participatory_space) + + menu.add_item :assembly_share_tokens, + I18n.t("menu.share_tokens", scope: "decidim.admin"), + decidim_admin_assemblies.assembly_share_tokens_path(current_participatory_space), + active: is_active_link?(decidim_admin_assemblies.assembly_share_tokens_path(current_participatory_space)), + icon_name: "share-line", + if: allowed_to?(:read, :share_tokens, current_participatory_space:) end end diff --git a/decidim-assemblies/spec/shared/manage_assembly_components_examples.rb b/decidim-assemblies/spec/shared/manage_assembly_components_examples.rb index 803c083182491..065e34997b62c 100644 --- a/decidim-assemblies/spec/shared/manage_assembly_components_examples.rb +++ b/decidim-assemblies/spec/shared/manage_assembly_components_examples.rb @@ -209,8 +209,6 @@ } )) end - - it_behaves_like "manage component share tokens" end context "when the component is published" do diff --git a/decidim-assemblies/spec/system/admin/admin_manages_assembly_component_share_tokens_spec.rb b/decidim-assemblies/spec/system/admin/admin_manages_assembly_component_share_tokens_spec.rb new file mode 100644 index 0000000000000..10bdf5dec0f50 --- /dev/null +++ b/decidim-assemblies/spec/system/admin/admin_manages_assembly_component_share_tokens_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Admin manages assembly component share tokens" do + include_context "when admin administrating an assembly" + + it_behaves_like "manage component share tokens" do + let(:participatory_space) { assembly } + let(:participatory_space_engine) { decidim_admin_assemblies } + end +end diff --git a/decidim-assemblies/spec/system/admin/admin_manages_assembly_share_tokens_spec.rb b/decidim-assemblies/spec/system/admin/admin_manages_assembly_share_tokens_spec.rb new file mode 100644 index 0000000000000..aab9636ad0eea --- /dev/null +++ b/decidim-assemblies/spec/system/admin/admin_manages_assembly_share_tokens_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Admin manages assembly share tokens" do + let!(:user) { create(:user, :admin, :confirmed, organization:) } + let(:organization) { create(:organization) } + let!(:assembly) { create(:assembly, organization:, private_space: true) } + let(:participatory_space) { assembly } + let(:participatory_space_path) { decidim_admin_assemblies.edit_assembly_path(assembly) } + let(:participatory_spaces_path) { decidim_admin_assemblies.assemblies_path } + + it_behaves_like "manage participatory space share tokens" + + context "when the user is an assembly admin" do + let(:user) { create(:user, :confirmed, :admin_terms_accepted, organization:) } + let!(:role) { create(:assembly_user_role, user:, assembly:, role: :admin) } + + it_behaves_like "manage participatory space share tokens" + end +end diff --git a/decidim-assemblies/spec/system/preview_assembly_with_share_token_spec.rb b/decidim-assemblies/spec/system/preview_assembly_with_share_token_spec.rb new file mode 100644 index 0000000000000..323eb2d49378e --- /dev/null +++ b/decidim-assemblies/spec/system/preview_assembly_with_share_token_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Preview assembly with share token" do + let(:organization) { create(:organization) } + let!(:participatory_space) { create(:assembly, organization:, published_at: nil) } + let(:resource_path) { decidim_assemblies.assembly_path(participatory_space) } + + it_behaves_like "preview participatory space with a share_token" +end diff --git a/decidim-blogs/spec/system/preview_blogs_with_share_token_spec.rb b/decidim-blogs/spec/system/preview_blogs_with_share_token_spec.rb index a5ff8c5cc36e5..bc4669f1817df 100644 --- a/decidim-blogs/spec/system/preview_blogs_with_share_token_spec.rb +++ b/decidim-blogs/spec/system/preview_blogs_with_share_token_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -describe "Preview blogs with share token" do +describe "preview blogs with a share token" do let(:manifest_name) { "blogs" } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-budgets/spec/system/preview_budgets_with_share_token_spec.rb b/decidim-budgets/spec/system/preview_budgets_with_share_token_spec.rb index e76e3bdd615b5..dab5e601e5297 100644 --- a/decidim-budgets/spec/system/preview_budgets_with_share_token_spec.rb +++ b/decidim-budgets/spec/system/preview_budgets_with_share_token_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -describe "Preview budgets with share token" do +describe "preview budgets with a share token" do let(:manifest_name) { "budgets" } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-conferences/app/controllers/decidim/conferences/admin/component_share_tokens_controller.rb b/decidim-conferences/app/controllers/decidim/conferences/admin/component_share_tokens_controller.rb new file mode 100644 index 0000000000000..af15a8a3ad9ca --- /dev/null +++ b/decidim-conferences/app/controllers/decidim/conferences/admin/component_share_tokens_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Decidim + module Conferences + module Admin + # This controller allows sharing unpublished things. + # It is targeted for customizations for sharing unpublished things that lives under + # an conference. + class ComponentShareTokensController < Decidim::Admin::ShareTokensController + include Concerns::ConferenceAdmin + + def resource + @resource ||= current_participatory_space.components.find(params[:component_id]) + end + end + end + end +end diff --git a/decidim-conferences/app/controllers/decidim/conferences/admin/conference_share_tokens_controller.rb b/decidim-conferences/app/controllers/decidim/conferences/admin/conference_share_tokens_controller.rb new file mode 100644 index 0000000000000..f57398263240c --- /dev/null +++ b/decidim-conferences/app/controllers/decidim/conferences/admin/conference_share_tokens_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Decidim + module Conferences + module Admin + # This controller allows sharing unpublished things. + # It is targeted for customizations for sharing unpublished things that lives under + # an conference. + class ConferenceShareTokensController < Decidim::Admin::ShareTokensController + include Concerns::ConferenceAdmin + + def resource + current_conference + end + end + end + end +end diff --git a/decidim-conferences/app/models/decidim/conference.rb b/decidim-conferences/app/models/decidim/conference.rb index 936ceefa7262a..e262b8b406015 100644 --- a/decidim-conferences/app/models/decidim/conference.rb +++ b/decidim-conferences/app/models/decidim/conference.rb @@ -20,6 +20,7 @@ class Conference < ApplicationRecord include Decidim::HasUploadValidations include Decidim::TranslatableResource include Decidim::FilterableResource + include Decidim::ShareableWithToken translatable_fields :title, :slogan, :short_description, :description, :objectives, :registration_terms @@ -146,6 +147,10 @@ def attachment_context :admin end + def shareable_url(share_token) + EngineRouter.main_proxy(self).conference_url(self, share_token: share_token.token) + end + # Allow ransacker to search for a key in a hstore column (`title`.`en`) ransacker_i18n :title end diff --git a/decidim-conferences/app/permissions/decidim/conferences/permissions.rb b/decidim-conferences/app/permissions/decidim/conferences/permissions.rb index f3c29b02d3852..bb6e4e8237e56 100644 --- a/decidim-conferences/app/permissions/decidim/conferences/permissions.rb +++ b/decidim-conferences/app/permissions/decidim/conferences/permissions.rb @@ -128,6 +128,7 @@ def public_read_conference_action? return allow! if user&.admin? return allow! if conference.published? + return allow! if user_can_preview_space? toggle_allow(can_manage_conference?) end @@ -277,7 +278,8 @@ def conference_admin_action? :partner, :media_link, :registration_type, - :conference_invite + :conference_invite, + :share_tokens ].include?(permission_action.subject) allow! if is_allowed end @@ -300,11 +302,18 @@ def org_admin_action? :partner, :registration_type, :read_conference_registrations, - :export_conference_registrations + :export_conference_registrations, + :share_tokens ].include?(permission_action.subject) allow! if is_allowed end + def user_can_preview_space? + context[:share_token].present? && Decidim::ShareToken.use!(token_for: conference, token: context[:share_token], user:) + rescue ActiveRecord::RecordNotFound, StandardError + nil + end + # Checks if the permission_action is to read the admin conferences list or # not. def read_conference_list_permission_action? diff --git a/decidim-conferences/app/views/decidim/conferences/admin/conferences/index.html.erb b/decidim-conferences/app/views/decidim/conferences/admin/conferences/index.html.erb index 66a678c1ddb13..992f1ef070927 100644 --- a/decidim-conferences/app/views/decidim/conferences/admin/conferences/index.html.erb +++ b/decidim-conferences/app/views/decidim/conferences/admin/conferences/index.html.erb @@ -38,6 +38,11 @@ <% end %> + <% if allowed_to? :read, :share_tokens, current_participatory_space: conference %> + <%= icon_link_to "share-line", decidim_admin_conferences.conference_share_tokens_path(conference), t("actions.share_tokens", scope: "decidim.admin"), class: "action-icon--new" %> + <% else %> + + <% end %> <% if allowed_to? :update, :conference, conference: conference %> <%= icon_link_to "pencil-line", edit_conference_path(conference), t("actions.configure", scope: "decidim.admin"), class: "action-icon--new" %> <% end %> diff --git a/decidim-conferences/lib/decidim/conferences/admin_engine.rb b/decidim-conferences/lib/decidim/conferences/admin_engine.rb index 7e3ffb0a2832e..a6ab648ff7111 100644 --- a/decidim-conferences/lib/decidim/conferences/admin_engine.rb +++ b/decidim-conferences/lib/decidim/conferences/admin_engine.rb @@ -66,6 +66,7 @@ class AdminEngine < ::Rails::Engine put :unpublish get :share end + resources :component_share_tokens, except: [:show], path: "share_tokens", as: "share_tokens" resources :exports, only: :create resources :imports, only: [:new, :create] do get :example, on: :collection @@ -81,6 +82,8 @@ class AdminEngine < ::Rails::Engine end resources :reports, controller: "moderations/reports", only: [:index, :show] end + + resources :conference_share_tokens, except: [:show], path: "share_tokens" end scope "/conferences/:conference_slug/components/:component_id/manage" do diff --git a/decidim-conferences/lib/decidim/conferences/menu.rb b/decidim-conferences/lib/decidim/conferences/menu.rb index a99250eda1b05..4fe7756cf0a4d 100644 --- a/decidim-conferences/lib/decidim/conferences/menu.rb +++ b/decidim-conferences/lib/decidim/conferences/menu.rb @@ -48,6 +48,7 @@ def self.register_admin_conferences_components_menu! active: is_active_link?(manage_component_path(component)) || is_active_link?(decidim_admin_conferences.edit_component_path(current_participatory_space, component)) || is_active_link?(decidim_admin_conferences.edit_component_permissions_path(current_participatory_space, component)) || + is_active_link?(decidim_admin_conferences.component_share_tokens_path(current_participatory_space, component)) || participatory_space_active_link?(component), if: component.manifest.admin_engine && user_role_config.component_is_accessible?(component.manifest_name) end @@ -172,6 +173,13 @@ def self.register_conferences_admin_menu! decidim_admin_conferences.moderations_path(current_participatory_space), icon_name: "flag-line", if: allowed_to?(:read, :moderation, conference: current_participatory_space) + + menu.add_item :conference_share_tokens, + I18n.t("menu.share_tokens", scope: "decidim.admin"), + decidim_admin_conferences.conference_share_tokens_path(current_participatory_space), + active: is_active_link?(decidim_admin_conferences.conference_share_tokens_path(current_participatory_space)), + icon_name: "share-line", + if: allowed_to?(:read, :share_tokens, current_participatory_space:) end end diff --git a/decidim-conferences/spec/shared/manage_conference_components_examples.rb b/decidim-conferences/spec/shared/manage_conference_components_examples.rb index 984657311f43c..873eea4c69fe5 100644 --- a/decidim-conferences/spec/shared/manage_conference_components_examples.rb +++ b/decidim-conferences/spec/shared/manage_conference_components_examples.rb @@ -209,8 +209,6 @@ } )) end - - it_behaves_like "manage component share tokens" end context "when the component is published" do diff --git a/decidim-conferences/spec/system/admin/admin_manages_conference_component_share_tokens_spec.rb b/decidim-conferences/spec/system/admin/admin_manages_conference_component_share_tokens_spec.rb new file mode 100644 index 0000000000000..faa819ab0fd9c --- /dev/null +++ b/decidim-conferences/spec/system/admin/admin_manages_conference_component_share_tokens_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Admin manages conference component share tokens" do + include_context "when admin administrating a conference" + + it_behaves_like "manage component share tokens" do + let(:participatory_space) { conference } + let(:participatory_space_engine) { decidim_admin_conferences } + end +end diff --git a/decidim-conferences/spec/system/admin/admin_manages_conference_share_tokens_spec.rb b/decidim-conferences/spec/system/admin/admin_manages_conference_share_tokens_spec.rb new file mode 100644 index 0000000000000..827e50a37b565 --- /dev/null +++ b/decidim-conferences/spec/system/admin/admin_manages_conference_share_tokens_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Admin manages conference share tokens" do + include_context "when admin administrating a conference" + let(:participatory_space) { conference } + let(:participatory_space_path) { decidim_admin_conferences.edit_conference_path(conference) } + let(:participatory_spaces_path) { decidim_admin_conferences.conferences_path } + + it_behaves_like "manage participatory space share tokens" + + context "when the user is a conference admin" do + let(:user) { create(:user, :confirmed, :admin_terms_accepted, organization:) } + let!(:role) { create(:conference_user_role, user:, conference:, role: :admin) } + + it_behaves_like "manage participatory space share tokens" + end +end diff --git a/decidim-conferences/spec/system/preview_conference_with_share_token_spec.rb b/decidim-conferences/spec/system/preview_conference_with_share_token_spec.rb new file mode 100644 index 0000000000000..714c761d7f5aa --- /dev/null +++ b/decidim-conferences/spec/system/preview_conference_with_share_token_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Preview conference with share token" do + let(:organization) { create(:organization) } + let!(:participatory_space) { create(:conference, organization:, published_at: nil) } + let(:resource_path) { decidim_conferences.conference_path(participatory_space) } + + it_behaves_like "preview participatory space with a share_token" +end diff --git a/decidim-core/app/controllers/concerns/decidim/needs_permission.rb b/decidim-core/app/controllers/concerns/decidim/needs_permission.rb index c4b448a6623a7..c12e91c0d3188 100644 --- a/decidim-core/app/controllers/concerns/decidim/needs_permission.rb +++ b/decidim-core/app/controllers/concerns/decidim/needs_permission.rb @@ -40,7 +40,8 @@ def permissions_context current_settings: try(:current_settings), component_settings: try(:component_settings), current_organization: try(:current_organization), - current_component: try(:current_component) + current_component: try(:current_component), + share_token: try(:store_share_token) } end diff --git a/decidim-core/app/controllers/decidim/application_controller.rb b/decidim-core/app/controllers/decidim/application_controller.rb index 40bd3c53423eb..e033d94d49a40 100644 --- a/decidim-core/app/controllers/decidim/application_controller.rb +++ b/decidim-core/app/controllers/decidim/application_controller.rb @@ -56,6 +56,12 @@ class ApplicationController < ::DecidimController skip_before_action :disable_http_caching, unless: :user_signed_in? + def store_share_token + session[:share_token] = params[:share_token] if params.has_key?(:share_token) + + session[:share_token].presence + end + private # This overrides Devise's method for extracting the path from the URL. We diff --git a/decidim-core/app/controllers/decidim/components/base_controller.rb b/decidim-core/app/controllers/decidim/components/base_controller.rb index 07e415c455d4e..09bc0b75c6a7a 100644 --- a/decidim-core/app/controllers/decidim/components/base_controller.rb +++ b/decidim-core/app/controllers/decidim/components/base_controller.rb @@ -30,7 +30,7 @@ class BaseController < Decidim::ApplicationController :current_manifest before_action do - enforce_permission_to :read, :component, component: current_component, share_token: + enforce_permission_to :read, :component, component: current_component end before_action :redirect_unless_feature_private @@ -49,10 +49,6 @@ def current_manifest @current_manifest ||= current_component.manifest end - def share_token - params[:share_token] - end - def permission_scope :public end diff --git a/decidim-core/app/controllers/decidim/homepage_controller.rb b/decidim-core/app/controllers/decidim/homepage_controller.rb index 6dd3c23367f0c..a8784f8a8300f 100644 --- a/decidim-core/app/controllers/decidim/homepage_controller.rb +++ b/decidim-core/app/controllers/decidim/homepage_controller.rb @@ -3,7 +3,6 @@ module Decidim class HomepageController < Decidim::ApplicationController skip_before_action :store_current_location - def show; end end end diff --git a/decidim-core/app/models/decidim/component.rb b/decidim-core/app/models/decidim/component.rb index e44f289934513..25782a80e743a 100644 --- a/decidim-core/app/models/decidim/component.rb +++ b/decidim-core/app/models/decidim/component.rb @@ -94,7 +94,7 @@ def can_participate_in_space?(user) # Public: Public URL for component with given share token as query parameter def shareable_url(share_token) - EngineRouter.main_proxy(self).root_path(self, share_token: share_token.token) + EngineRouter.main_proxy(self).root_url(self, share_token: share_token.token) end delegate :serializes_specific_data?, to: :manifest diff --git a/decidim-core/app/models/decidim/share_token.rb b/decidim-core/app/models/decidim/share_token.rb index 950207583ff18..4cd67777b2429 100644 --- a/decidim-core/app/models/decidim/share_token.rb +++ b/decidim-core/app/models/decidim/share_token.rb @@ -2,33 +2,63 @@ module Decidim class ShareToken < ApplicationRecord - validates :token, presence: true, uniqueness: { scope: [:decidim_organization_id, :token_for_type, :token_for_id] } + include Decidim::Traceable belongs_to :organization, foreign_key: "decidim_organization_id", class_name: "Decidim::Organization" belongs_to :user, foreign_key: "decidim_user_id", class_name: "Decidim::User" belongs_to :token_for, foreign_type: "token_for_type", polymorphic: true - after_initialize :generate, :set_default_expiration + validates :token, presence: true, uniqueness: { scope: [:decidim_organization_id, :token_for_type, :token_for_id] } + # validates token no spaces or strange characters + validates :token, format: { with: /\A[a-zA-Z0-9_-]+\z/ } + + after_initialize :generate + + def self.log_presenter_class_for(_log) + Decidim::AdminLog::ShareTokenPresenter + end - def self.use!(token_for:, token:) + def self.use!(token_for:, token:, user: nil) record = find_by!(token_for:, token:) - record.use! + record.use!(user:) end - def use! + def use!(user: nil) return raise StandardError, "Share token '#{token}' for '#{token_for_type}' with id = #{token_for_id} has expired." if expired? + return raise StandardError, "Share token '#{token}' for '#{token_for_type}' with id = #{token_for_id} requires a registered user." if registered_only? && user.nil? update!(times_used: times_used + 1, last_used_at: Time.zone.now) end def expired? - expires_at.past? + expires_at.past? unless expires_at.nil? end def url token_for.shareable_url(self) end + def participatory_space + return token_for if token_for.try(:manifest).is_a?(Decidim::ParticipatorySpaceManifest) + return token_for.participatory_space if token_for.respond_to?(:participatory_space) + + component&.participatory_space + end + + def component + return token_for if token_for.is_a?(Decidim::Component) + + token_for.component if token_for.respond_to?(:component) + end + + def self.ransackable_attributes(_auth_object = nil) + %w(token expires_at last_used_at registered_only) + end + + def self.ransackable_associations(_auth_object = nil) + %w(organization token_for user) + end + private def generate @@ -39,9 +69,5 @@ def generate break if ShareToken.find_by(token:).blank? end end - - def set_default_expiration - self.expires_at ||= 1.day.from_now - end end end diff --git a/decidim-core/app/packs/src/decidim/clipboard.js b/decidim-core/app/packs/src/decidim/clipboard.js index 26327c8ac8617..010190cb595f0 100644 --- a/decidim-core/app/packs/src/decidim/clipboard.js +++ b/decidim-core/app/packs/src/decidim/clipboard.js @@ -21,7 +21,9 @@ import select from "select"; * * Options through data attributes: * - `data-clipboard-copy` = The jQuery selector for the target input element - * where text will be copied from. + * where text will be copied from. If this element does not contain any visible text (for instance is an image), + * the selector indicated in here will be used to place the confirmation message. + * - `data-clipboard-content` = The text that will be copied. If empty or not present, the target input element will be used. * - `data-clipboard-copy-label` = The label that will be shown in the button * after a succesful copy. * - `data-clipboard-copy-message` = The text that will be announced to screen @@ -40,12 +42,17 @@ $(() => { } const $input = $($el.data("clipboard-copy")); - if ($input.length < 1 || !$input.is("input, textarea, select")) { - return; + + let selectedText = $el.data("clipboard-content") || ""; + if (selectedText === "" && $input.is("input, textarea, select")) { + selectedText = select($input[0]); } + let $msgEl = $el; + if ($msgEl.text() === "") { + $msgEl = $input; + } // Get the available text to clipboard. - const selectedText = select($input[0]); if (!selectedText || selectedText.length < 1) { return; } @@ -83,16 +90,18 @@ $(() => { } if (!$el.data("clipboard-copy-label-original")) { - $el.data("clipboard-copy-label-original", $el.html()); + $el.data("clipboard-copy-label-original", $msgEl.html()); } - $el.html(label); + $msgEl.html(label); + to = setTimeout(() => { - $el.html($el.data("clipboard-copy-label-original")); + $msgEl.html($el.data("clipboard-copy-label-original")); $el.removeData("clipboard-copy-label-original"); $el.removeData("clipboard-copy-label-timeout"); }, CLIPBOARD_COPY_TIMEOUT); - $el.data("clipboard-copy-label-timeout", to) + + $el.data("clipboard-copy-label-timeout", to); } // Alert the screen reader what just happened (the link was copied). @@ -107,7 +116,7 @@ $(() => { } } else { $msg = $('
'); - $el.after($msg); + $msgEl.append($msg); $el.data("clipboard-message-element", $msg); } diff --git a/decidim-core/app/permissions/decidim/permissions.rb b/decidim-core/app/permissions/decidim/permissions.rb index cdb53ff8d0ada..9023202bb6234 100644 --- a/decidim-core/app/permissions/decidim/permissions.rb +++ b/decidim-core/app/permissions/decidim/permissions.rb @@ -56,7 +56,6 @@ def component_public_action? return allow! if component.published? return allow! if user_can_preview_component? - return allow! if user_can_admin_component? return allow! if user_can_admin_component_via_space? disallow! @@ -163,7 +162,7 @@ def user_group_invitations_action? end def user_can_preview_component? - return allow! if context[:share_token].present? && Decidim::ShareToken.use!(token_for: component, token: context[:share_token]) + context[:share_token].present? && Decidim::ShareToken.use!(token_for: component, token: context[:share_token], user:) rescue ActiveRecord::RecordNotFound, StandardError nil end diff --git a/decidim-core/app/presenters/decidim/admin_log/share_token_presenter.rb b/decidim-core/app/presenters/decidim/admin_log/share_token_presenter.rb new file mode 100644 index 0000000000000..1728e90303849 --- /dev/null +++ b/decidim-core/app/presenters/decidim/admin_log/share_token_presenter.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Decidim + module AdminLog + # This class extends the default resource presenter for logs, so that + # it can properly link to the static page. + class ShareTokenPresenter < Decidim::Log::BasePresenter + private + + def diff_fields_mapping + { + token: :string, + expires_at: :date, + registered_only: :boolean, + token_for: :string + } + end + + def action_string + case action + when "create", "delete", "update" + "decidim.admin_log.share_token.#{action}#{suffix}" + else + super + end + end + + def suffix + return "_with_space" if action_log.extra.dig("component", "title").present? + + "" + end + + def diff_actions + %w(update create delete) + end + end + end +end diff --git a/decidim-core/config/locales/en.yml b/decidim-core/config/locales/en.yml index 991881e25f6e0..3efce494065c3 100644 --- a/decidim-core/config/locales/en.yml +++ b/decidim-core/config/locales/en.yml @@ -278,6 +278,13 @@ en: create: "%{user_name} created the %{resource_name} scope type" delete: "%{user_name} deleted the %{resource_name} scope type" update: "%{user_name} updated the %{resource_name} scope type" + share_token: + create: "%{user_name} created an access link in %{space_name}" + create_with_space: "%{user_name} created an access link for %{resource_name} in %{space_name}" + delete: "%{user_name} deleted an access link in %{space_name}" + delete_with_space: "%{user_name} deleted an access link for %{resource_name} in %{space_name}" + update: "%{user_name} updated an access link in %{space_name}" + update_with_space: "%{user_name} updated an access link for %{resource_name} in %{space_name}" static_page: create: "%{user_name} created the %{resource_name} static page" delete: "%{user_name} deleted the %{resource_name} static page" diff --git a/decidim-core/db/migrate/20240717093514_add_registered_only_to_decidim_share_tokens.rb b/decidim-core/db/migrate/20240717093514_add_registered_only_to_decidim_share_tokens.rb new file mode 100644 index 0000000000000..11417bf8e1b46 --- /dev/null +++ b/decidim-core/db/migrate/20240717093514_add_registered_only_to_decidim_share_tokens.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddRegisteredOnlyToDecidimShareTokens < ActiveRecord::Migration[7.0] + def change + add_column :decidim_share_tokens, :registered_only, :boolean + end +end diff --git a/decidim-core/lib/decidim/component_manifest.rb b/decidim-core/lib/decidim/component_manifest.rb index 69604b84d5af6..9f0f6fa00b3eb 100644 --- a/decidim-core/lib/decidim/component_manifest.rb +++ b/decidim-core/lib/decidim/component_manifest.rb @@ -142,6 +142,13 @@ def seed!(participatory_space) @seeds&.call(participatory_space) end + # The name of the named Rails route to create the url to the resource. + # + # Returns a String. + def route_name + "component" + end + # Public: Adds configurable attributes for this component, scoped to a name. It # uses the DSL specified under `Decidim::SettingsManifest`. # diff --git a/decidim-core/lib/decidim/core/test.rb b/decidim-core/lib/decidim/core/test.rb index aa27dddd12ceb..99560aacd2995 100644 --- a/decidim-core/lib/decidim/core/test.rb +++ b/decidim-core/lib/decidim/core/test.rb @@ -59,8 +59,8 @@ require "decidim/core/test/shared_examples/permissions" require "decidim/core/test/shared_examples/admin_resource_gallery_examples" require "decidim/core/test/shared_examples/map_examples" -require "decidim/core/test/shared_examples/preview_component_with_share_token_examples" -require "decidim/core/test/shared_examples/manage_component_share_tokens" +require "decidim/core/test/shared_examples/preview_with_share_token_examples" +require "decidim/core/test/shared_examples/manage_share_tokens_examples" require "decidim/core/test/shared_examples/metric_manage_shared_context" require "decidim/core/test/shared_examples/resource_search_examples" require "decidim/core/test/shared_examples/static_pages_examples" diff --git a/decidim-core/lib/decidim/core/test/factories.rb b/decidim-core/lib/decidim/core/test/factories.rb index 5d2110c2c93c8..29ca063d8632c 100644 --- a/decidim-core/lib/decidim/core/test/factories.rb +++ b/decidim-core/lib/decidim/core/test/factories.rb @@ -961,6 +961,10 @@ def generate_localized_title(field = nil, skip_injection: false) object.organization ||= object.token_for.organization end + trait :with_token do + token { SecureRandom.hex(32) } + end + trait :expired do expires_at { 1.day.ago } end diff --git a/decidim-core/lib/decidim/core/test/shared_examples/manage_component_share_tokens.rb b/decidim-core/lib/decidim/core/test/shared_examples/manage_component_share_tokens.rb deleted file mode 100644 index 4d6861669e4cf..0000000000000 --- a/decidim-core/lib/decidim/core/test/shared_examples/manage_component_share_tokens.rb +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples "manage component share tokens" do - let!(:components_path) { participatory_space_components_path(participatory_space) } - - context "when visiting the components page for the participatory space" do - before do - visit components_path - end - - it "has a share button that opens the share url for the component" do - share_window = window_opened_by { click_on "Share", wait: 2 } - - within_window share_window do - expect(current_url).to include(component.share_tokens.reload.last.url) - end - end - end - - context "when visiting the component configuration page" do - context "when there are tokens" do - let!(:share_tokens) { create_list(:share_token, 3, token_for: component, organization: component.organization) } - let!(:share_token) { share_tokens.last } - - before do - visit components_path - - within "tr", text: component.name["en"] do - click_on "Configure" - end - end - - it "displays all tokens" do - within ".share_tokens" do - expect(page).to have_css("tbody tr", count: 3) - end - end - - it "displays relevant attributes for each token" do - share_tokens.each do |share_token| - within ".share_tokens tbody" do - expect(page).to have_content share_token.token - expect(page).to have_content share_token.user.name - end - end - end - - it "has a share link for each token" do - urls = share_tokens.map(&:url).map { |url| url.split("?").first } - within ".share_tokens tbody tr:first-child" do - share_window = window_opened_by { click_on "Share" } - - within_window share_window do - expect(urls).to include(page.current_path) - end - end - end - - it "has a link to delete tokens" do - within ".share_tokens tbody tr:first-child" do - accept_confirm { click_on "Delete" } - end - - expect(page).to have_admin_callout("successfully") - expect(page).to have_css("tbody tr", count: 2) - end - end - - context "when there are no tokens" do - before do - visit components_path - - within "tr", text: component.name["en"] do - click_on "Configure" - end - end - - it "displays empty message" do - expect(page).to have_content "There are no active tokens" - end - end - end -end diff --git a/decidim-core/lib/decidim/core/test/shared_examples/manage_share_tokens_examples.rb b/decidim-core/lib/decidim/core/test/shared_examples/manage_share_tokens_examples.rb new file mode 100644 index 0000000000000..964277a197dec --- /dev/null +++ b/decidim-core/lib/decidim/core/test/shared_examples/manage_share_tokens_examples.rb @@ -0,0 +1,237 @@ +# frozen_string_literal: true + +shared_examples "manage resource share tokens" do + context "when there are no tokens" do + let(:last_token) { Decidim::ShareToken.last } + before do + visit_share_tokens_page + end + + it "displays empty message" do + expect(page).to have_content "There are no active access links" + end + + it "can create a new token with default options" do + click_on "New access link" + + click_on "Create" + + expect(page).to have_content("Access link created successfully") + expect(page).to have_css("tbody tr", count: 1) + within "tbody tr:last-child td", text: last_token.token do + expect(page).to have_content(last_token.token) + end + within "tbody tr:last-child td:nth-child(2)" do + expect(page).to have_content("Never") + end + within "tbody tr:last-child td:nth-child(3)" do + expect(page).to have_content("No") + end + end + + it "can create a new token with custom options" do + click_on "New access link" + + find_by_id("share_token_automatic_token_false").click + find_by_id("share_token_no_expiration_false").click + find_by_id("share_token_registered_only_true").click + click_on "Create" + expect(page).to have_content("cannot be blank", count: 2) + + fill_in "share_token_token", with: " custom token " + fill_in_datepicker :share_token_expires_at_date, with: 1.day.from_now.strftime("%d/%m/%Y") + fill_in_timepicker :share_token_expires_at_time, with: "00:00" + click_on "Create" + + expect(page).to have_content("Access link created successfully") + expect(page).to have_css("tbody tr", count: 1) + within "tbody tr:last-child td", text: last_token.token do + expect(page).to have_content("CUSTOM-TOKEN") + end + within "tbody tr:last-child td:nth-child(2)" do + expect(page).to have_content(1.day.from_now.strftime("%d/%m/%Y 00:00")) + end + within "tbody tr:last-child td:nth-child(3)" do + expect(page).to have_content("Yes") + end + end + end + + context "when there are tokens" do + let!(:share_tokens) { create_list(:share_token, 3, :with_token, token_for: resource, organization:, registered_only: true) } + let(:last_token) { share_tokens.last } + + before do + visit_share_tokens_page + end + + it "displays all tokens" do + within ".share_tokens" do + expect(page).to have_css("tbody tr", count: 3) + end + end + + it "displays relevant attributes for each token" do + share_tokens.each do |share_token| + within ".share_tokens tbody" do + expect(page).to have_content share_token.token + expect(page).to have_content share_token.expires_at.to_s + end + end + end + + context "when ordering" do + let(:share_tokens) do + [ + create(:share_token, :with_token, token_for: resource, organization:, token: "b", expires_at: 1.day.from_now, registered_only: true, times_used: 3), + create(:share_token, :with_token, token_for: resource, organization:, token: "a", expires_at: 3.days.from_now, registered_only: true, times_used: 2), + create(:share_token, :with_token, token_for: resource, organization:, token: "c", expires_at: 2.days.from_now, registered_only: false, times_used: 1) + ] + end + + it "can be ordered by token and other attributes" do + within ".share_tokens" do + click_on "Access link" # order by token + expect(page).to have_css("tbody tr:first-child", text: "c") + click_on "Access link" # order by token + expect(page).to have_css("tbody tr:first-child", text: "a") + click_on "Expires at" # order by expires_at + expect(page).to have_css("tbody tr:first-child", text: share_tokens.second.expires_at.strftime("%d/%m/%Y %H:%M")) + click_on "Expires at" # order by expires_at + expect(page).to have_css("tbody tr:first-child", text: share_tokens.first.expires_at.strftime("%d/%m/%Y %H:%M")) + click_on "Registered only" # order by registered_only + expect(page).to have_css("tbody tr:first-child", text: "Yes") + click_on "Registered only" # order by registered_only + expect(page).to have_css("tbody tr:first-child", text: "No") + click_on "Times used" # order by times_used + expect(page).to have_css("tbody tr:first-child", text: "3") + click_on "Times used" # order by times_used + expect(page).to have_css("tbody tr:first-child", text: "1") + end + end + end + + it "can edit a share token" do + within "tbody tr", text: last_token.token do + expect(page).to have_content("Yes") + end + within ".share_tokens tbody tr", text: last_token.token do + click_on "Edit" + end + + expect(page).to have_content("Edit access links for: #{resource_name}") + find_by_id("share_token_no_expiration_false").click + find_by_id("share_token_registered_only_false").click + click_on "Update" + expect(page).to have_content("cannot be blank", count: 1) + + fill_in_datepicker :share_token_expires_at_date, with: 1.day.from_now.strftime("%d/%m/%Y") + fill_in_timepicker :share_token_expires_at_time, with: "00:00" + + click_on "Update" + + expect(page).to have_content("Access link updated successfully") + expect(page).to have_css("tbody tr", count: 3) + within "tbody tr", text: last_token.token do + expect(page).to have_content(1.day.from_now.strftime("%d/%m/%Y 00:00")) + end + within "tbody tr", text: last_token.token do + expect(page).to have_content("No") + end + end + + it "allows copying the share link from the share token" do + within ".share_tokens tbody tr", text: last_token.token do + click_on "Copy link" + expect(page).to have_content("Copied!") + expect(page).to have_css("[data-clipboard-copy-label]") + expect(page).to have_css("[data-clipboard-copy-message]") + expect(page).to have_css("[data-clipboard-content]") + end + end + + it "has a share link for each token" do + urls = share_tokens.map(&:url) + within ".share_tokens tbody tr", text: last_token.token do + share_window = window_opened_by { click_on "Preview" } + + within_window share_window do + expect(urls).to include(page.current_url) + end + end + end + + it "has a share button that opens the share url for the resource" do + within ".share_tokens tbody tr", text: last_token.token do + share_window = window_opened_by { click_on "Preview", wait: 2 } + + within_window share_window do + expect(current_url).to include(last_token.url) + end + end + end + + it "can delete tokens" do + within ".share_tokens tbody tr", text: last_token.token do + accept_confirm { click_on "Delete" } + end + + expect(page).to have_admin_callout("Access link successfully destroyed") + expect(page).to have_css("tbody tr", count: 2) + end + end + + context "when there are many pages" do + let!(:share_tokens) { create_list(:share_token, 26, :with_token, token_for: resource, organization:) } + + before do + visit_share_tokens_page + end + + it "displays pagination" do + expect(page).to have_css("tbody tr", count: 25) + within '[aria-label="Pagination"]' do + click_on "Next" + end + expect(page).to have_css("tbody tr", count: 1) + end + end +end + +shared_examples "manage component share tokens" do + let!(:components_path) { participatory_space_engine.components_path(participatory_space) } + let!(:component) { create(:component, participatory_space:, published_at: nil) } + let(:resource) { component } + let(:resource_name) { translated(component.name) } + + before do + switch_to_host(organization.host) + login_as user, scope: :user + end + + def visit_share_tokens_page + visit components_path + within ".table-list" do + click_on "Access links" + end + end + + it_behaves_like "manage resource share tokens" +end + +shared_examples "manage participatory space share tokens" do + let(:resource) { participatory_space } + let(:resource_name) { translated(resource.title) } + + before do + switch_to_host(organization.host) + login_as user, scope: :user + end + + def visit_share_tokens_page + visit participatory_spaces_path + click_on "Access links" + end + + it_behaves_like "manage resource share tokens" +end diff --git a/decidim-core/lib/decidim/core/test/shared_examples/preview_component_with_share_token_examples.rb b/decidim-core/lib/decidim/core/test/shared_examples/preview_component_with_share_token_examples.rb deleted file mode 100644 index f4788daf7dd6a..0000000000000 --- a/decidim-core/lib/decidim/core/test/shared_examples/preview_component_with_share_token_examples.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -shared_examples_for "preview component with share_token" do - context "when component is unpublished" do - before do - component.unpublish! - end - - context "when no share_token is provided" do - before do - visit_component - end - - it "does not allow visiting component" do - expect(page).to have_content "You are not authorized" - expect(page).to have_no_current_path(main_component_path(component), ignore_query: true) - end - end - - context "when a share_token is provided" do - let(:share_token) { create(:share_token, token_for: component) } - let(:params) { { share_token: share_token.token } } - - before do - uri = URI(main_component_path(component)) - uri.query = URI.encode_www_form(params.to_a) - visit uri - end - - context "when a valid share_token is provided" do - it "allows visiting component" do - expect(page).to have_no_content "You are not authorized" - expect(page).to have_current_path(main_component_path(component), ignore_query: true) - end - end - - context "when an invalid share_token is provided" do - let(:share_token) { create(:share_token, :expired, token_for: component) } - - it "does not allow visiting component" do - expect(page).to have_content "You are not authorized" - expect(page).to have_no_current_path(main_component_path(component), ignore_query: true) - end - end - end - end -end diff --git a/decidim-core/lib/decidim/core/test/shared_examples/preview_with_share_token_examples.rb b/decidim-core/lib/decidim/core/test/shared_examples/preview_with_share_token_examples.rb new file mode 100644 index 0000000000000..b27c5e2113fc1 --- /dev/null +++ b/decidim-core/lib/decidim/core/test/shared_examples/preview_with_share_token_examples.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +shared_examples "visit unpublished resource with a share token" do + context "when no share_token is provided" do + before do + visit_resource_page + end + + it "does not allow visiting resource" do + expect(page).to have_content "You are not authorized" + expect(page).to have_no_current_path(resource_path, ignore_query: true) + end + end + + context "when a share_token is provided" do + let(:share_token) { create(:share_token, :with_token, token_for: resource) } + let(:params) { { share_token: share_token.token } } + + before do + uri = URI(resource_path) + uri.query = URI.encode_www_form(params.to_a) + visit uri + end + + context "when a valid share_token is provided" do + it "allows visiting the resource" do + expect(page).to have_no_content "You are not authorized" + expect(current_url).to include(params[:share_token]) + expect(page).to have_current_path(resource_path, ignore_query: true) + + # repeat visit without the token in the params to check for the session + visit resource_path + expect(page).to have_no_content "You are not authorized" + expect(current_url).not_to include(params[:share_token]) + expect(page).to have_current_path(resource_path, ignore_query: true) + end + end + + context "when an invalid share_token is provided" do + let(:share_token) { create(:share_token, :with_token, :expired, token_for: resource) } + + it "does not allow visiting resource" do + expect(page).to have_content "You are not authorized" + expect(page).to have_no_current_path(resource_path, ignore_query: true) + end + end + + context "when the token requires the user to be registered" do + let(:share_token) { create(:share_token, :with_token, token_for: resource, registered_only: true) } + + it "does not allow visiting resource" do + expect(page).to have_content "You are not authorized" + expect(page).to have_no_current_path(resource_path, ignore_query: true) + end + + context "when a user is logged" do + let(:user) { create(:user, :confirmed, organization:) } + + it "allows visiting resource" do + login_as user, scope: :user + uri = URI(resource_path) + uri.query = URI.encode_www_form(params.to_a) + visit uri + expect(page).to have_no_content "You are not authorized" + expect(page).to have_current_path(resource_path, ignore_query: true) + end + end + end + end +end + +shared_examples "preview component with a share_token" do + let!(:component) { create(:component, manifest_name:, participatory_space:, published_at: nil) } + let(:resource) { component } + let(:resource_path) { main_component_path(component) } + + def visit_resource_page + visit_component + end + + it_behaves_like "visit unpublished resource with a share token" +end + +shared_examples "preview participatory space with a share_token" do + let(:resource) { participatory_space } + + before do + switch_to_host(organization.host) + end + + def visit_resource_page + visit resource_path + end + + it_behaves_like "visit unpublished resource with a share token" +end diff --git a/decidim-core/spec/models/decidim/share_token_spec.rb b/decidim-core/spec/models/decidim/share_token_spec.rb index 0a1ba0cb07392..1bab12c94a91f 100644 --- a/decidim-core/spec/models/decidim/share_token_spec.rb +++ b/decidim-core/spec/models/decidim/share_token_spec.rb @@ -11,12 +11,14 @@ module Decidim let(:attributes) do { + token:, token_for:, user:, organization: } end + let(:token) { "SOME-TOKEN" } let(:user) { create(:user) } let(:token_for) { create(:component) } let(:organization) { token_for.organization } @@ -42,15 +44,84 @@ module Decidim it { is_expected.not_to be_valid } end + + context "when token is not present" do + # FactoryBot does not run the after_initializer block when building if token is defined + let(:share_token) { build(:share_token, token_for:, organization:) } + + it { is_expected.to be_valid } + + it "generates a token" do + expect(subject.token).to be_present + end + end + + context "when token is already taken" do + let(:token) { "taken" } + + before do + create(:share_token, token:, token_for:, organization:) + end + + it { is_expected.not_to be_valid } + end + + context "when token is already taken by another component" do + let(:token) { "taken" } + + before do + create(:share_token, token:, organization:) + end + + it { is_expected.to be_valid } + end + + context "when token has strange characters" do + let(:token) { "bon cop de falç" } + + it { is_expected.to be_invalid } + end end describe "defaults" do + let(:share_token) { build(:share_token, token_for:, organization:) } + it "generates an alphanumeric 64-character token string" do expect(subject.token).to match(/^[a-zA-Z0-9]{64}$/) end - it "sets expires_at attribute to one day from current time" do - expect(subject.expires_at).to be_within(1.second).of 1.day.from_now + it "sets expires_at attribute to never expire" do + expect(subject.expires_at).to be_nil + end + end + + describe "participatory space and components" do + let(:space) { token_for.participatory_space } + let(:component) { token_for } + + it "returns participatory space and component" do + expect(subject.participatory_space).to eq(space) + expect(subject.component).to eq(component) + end + + context "when token is for a participatory space" do + let(:space) { create(:participatory_process) } + let(:token_for) { space } + + it "returns the participatory space as the component" do + expect(subject.participatory_space).to eq(space) + expect(subject.component).to be_nil + end + end + + context "when resource does not respond to participatory_space" do + let(:organization) { create(:organization) } + let(:token_for) { organization } + + it "returns the component" do + expect(subject.participatory_space).to be_nil + expect(subject.component).to be_nil + end end end @@ -94,7 +165,7 @@ module Decidim describe "#expired?" do context "when share_token has not expired" do it "returns true" do - expect(subject.expired?).to be false + expect(subject.expired?).to be_nil end end diff --git a/decidim-debates/spec/system/preview_debates_with_share_token_spec.rb b/decidim-debates/spec/system/preview_debates_with_share_token_spec.rb index a6785e2c6ea28..7e9c1ff8a1d63 100644 --- a/decidim-debates/spec/system/preview_debates_with_share_token_spec.rb +++ b/decidim-debates/spec/system/preview_debates_with_share_token_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -describe "Preview debates with share token" do +describe "preview debates with a share token" do let(:manifest_name) { "debates" } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-initiatives/app/controllers/decidim/initiatives/admin/component_share_tokens_controller.rb b/decidim-initiatives/app/controllers/decidim/initiatives/admin/component_share_tokens_controller.rb new file mode 100644 index 0000000000000..78872533a94e3 --- /dev/null +++ b/decidim-initiatives/app/controllers/decidim/initiatives/admin/component_share_tokens_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Decidim + module Initiatives + module Admin + # This controller allows sharing unpublished things. + # It is targeted for customizations for sharing unpublished things that lives under + # an initiative. + class ComponentShareTokensController < Decidim::Admin::ShareTokensController + include InitiativeAdmin + + def resource + @resource ||= current_participatory_space.components.find(params[:component_id]) + end + end + end + end +end diff --git a/decidim-initiatives/app/controllers/decidim/initiatives/admin/initiative_share_tokens_controller.rb b/decidim-initiatives/app/controllers/decidim/initiatives/admin/initiative_share_tokens_controller.rb new file mode 100644 index 0000000000000..47de71cfa11df --- /dev/null +++ b/decidim-initiatives/app/controllers/decidim/initiatives/admin/initiative_share_tokens_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Decidim + module Initiatives + module Admin + # This controller allows sharing unpublished things. + # It is targeted for customizations for sharing unpublished things that lives under + # an initiative. + class InitiativeShareTokensController < Decidim::Admin::ShareTokensController + include InitiativeAdmin + + def resource + current_initiative + end + end + end + end +end diff --git a/decidim-initiatives/app/models/decidim/initiative.rb b/decidim-initiatives/app/models/decidim/initiative.rb index fbcf4cf36a2a7..79da2ee98423f 100644 --- a/decidim-initiatives/app/models/decidim/initiative.rb +++ b/decidim-initiatives/app/models/decidim/initiative.rb @@ -24,6 +24,7 @@ class Initiative < ApplicationRecord include Decidim::HasResourcePermission include Decidim::HasArea include Decidim::FilterableResource + include Decidim::ShareableWithToken include Decidim::Reportable translatable_fields :title, :description, :answer @@ -471,6 +472,10 @@ def user_allowed_to_comment?(user) ActionAuthorizer.new(user, "comment", self, nil).authorize.ok? end + def shareable_url(share_token) + EngineRouter.main_proxy(self).initiative_url(self, share_token: share_token.token) + end + def self.ransack(params = {}, options = {}) Initiatives::InitiativeSearch.new(self, params, options) end diff --git a/decidim-initiatives/app/permissions/decidim/initiatives/admin/permissions.rb b/decidim-initiatives/app/permissions/decidim/initiatives/admin/permissions.rb index 8635ce047e875..60a0ae70726cc 100644 --- a/decidim-initiatives/app/permissions/decidim/initiatives/admin/permissions.rb +++ b/decidim-initiatives/app/permissions/decidim/initiatives/admin/permissions.rb @@ -39,6 +39,7 @@ def permissions initiative_export_action? initiatives_settings_action? moderator_action? + share_tokens_action? allow! if permission_action.subject == :attachment permission_action @@ -179,6 +180,12 @@ def moderator_action? allow! end + def share_tokens_action? + return unless permission_action.subject == :share_tokens + + allow! + end + def read_initiative_list_action? return unless permission_action.subject == :initiative && permission_action.action == :list diff --git a/decidim-initiatives/app/permissions/decidim/initiatives/permissions.rb b/decidim-initiatives/app/permissions/decidim/initiatives/permissions.rb index bdda57312843f..7cf4c3473e052 100644 --- a/decidim-initiatives/app/permissions/decidim/initiatives/permissions.rb +++ b/decidim-initiatives/app/permissions/decidim/initiatives/permissions.rb @@ -55,6 +55,7 @@ def read_public_initiative? permission_action.action == :read return allow! if initiative.published? || initiative.rejected? || initiative.accepted? + return allow! if user_can_preview_space? return allow! if user && authorship_or_admin? disallow! @@ -195,6 +196,12 @@ def can_user_support?(initiative) ) end + def user_can_preview_space? + context[:share_token].present? && Decidim::ShareToken.use!(token_for: initiative, token: context[:share_token], user:) + rescue ActiveRecord::RecordNotFound, StandardError + nil + end + def initiative_committee_action? return unless permission_action.subject == :initiative_committee_member diff --git a/decidim-initiatives/app/views/decidim/initiatives/admin/initiatives/index.html.erb b/decidim-initiatives/app/views/decidim/initiatives/admin/initiatives/index.html.erb index ca7eef81da0ae..1d7e855598700 100644 --- a/decidim-initiatives/app/views/decidim/initiatives/admin/initiatives/index.html.erb +++ b/decidim-initiatives/app/views/decidim/initiatives/admin/initiatives/index.html.erb @@ -45,6 +45,13 @@ <%= l initiative.created_at, format: :short %> <%= initiative.published_at? ? l(initiative.published_at, format: :short) : "" %> + + <% if allowed_to? :read, :share_tokens, current_participatory_space: initiative %> + <%= icon_link_to "share-line", decidim_admin_initiatives.initiative_share_tokens_path(initiative), t("actions.share_tokens", scope: "decidim.admin"), class: "action-icon--new" %> + <% else %> + + <% end %> + <% if allowed_to? :edit, :initiative, initiative: initiative %> <%= icon_link_to "pencil-line", decidim_admin_initiatives.edit_initiative_path(initiative.to_param), diff --git a/decidim-initiatives/lib/decidim/initiatives/admin_engine.rb b/decidim-initiatives/lib/decidim/initiatives/admin_engine.rb index 754ae875e5cbf..031809cda7e53 100644 --- a/decidim-initiatives/lib/decidim/initiatives/admin_engine.rb +++ b/decidim-initiatives/lib/decidim/initiatives/admin_engine.rb @@ -60,6 +60,7 @@ class AdminEngine < ::Rails::Engine put :unpublish get :share end + resources :component_share_tokens, except: [:show], path: "share_tokens", as: "share_tokens" resources :exports, only: :create end @@ -71,6 +72,8 @@ class AdminEngine < ::Rails::Engine end resources :reports, controller: "moderations/reports", only: [:index, :show] end + + resources :initiative_share_tokens, except: [:show], path: "share_tokens" end scope "/initiatives/:initiative_slug/components/:component_id/manage" do diff --git a/decidim-initiatives/lib/decidim/initiatives/menu.rb b/decidim-initiatives/lib/decidim/initiatives/menu.rb index d11c73efc938c..8ef0c64f090c7 100644 --- a/decidim-initiatives/lib/decidim/initiatives/menu.rb +++ b/decidim-initiatives/lib/decidim/initiatives/menu.rb @@ -66,6 +66,7 @@ def self.register_admin_initiatives_components_menu! active: is_active_link?(manage_component_path(component)) || is_active_link?(decidim_admin_initiatives.edit_component_path(current_participatory_space, component)) || is_active_link?(decidim_admin_initiatives.edit_component_permissions_path(current_participatory_space, component)) || + is_active_link?(decidim_admin_initiatives.component_share_tokens_path(current_participatory_space, component)) || participatory_space_active_link?(component), if: component.manifest.admin_engine # && user_role_config.component_is_accessible?(component.manifest_name) end @@ -106,6 +107,13 @@ def self.register_admin_initiative_menu! decidim_admin_initiatives.moderations_path(current_participatory_space), icon_name: "flag-line", if: allowed_to?(:read, :moderation) + + menu.add_item :initiatives_share_tokens, + I18n.t("menu.share_tokens", scope: "decidim.admin"), + decidim_admin_initiatives.initiative_share_tokens_path(current_participatory_space), + active: is_active_link?(decidim_admin_initiatives.initiative_share_tokens_path(current_participatory_space)), + icon_name: "share-line", + if: allowed_to?(:read, :share_tokens, current_participatory_space:) end end diff --git a/decidim-initiatives/spec/system/admin/admin_manages_initiative_component_share_tokens_spec.rb b/decidim-initiatives/spec/system/admin/admin_manages_initiative_component_share_tokens_spec.rb new file mode 100644 index 0000000000000..430c509f2d8b6 --- /dev/null +++ b/decidim-initiatives/spec/system/admin/admin_manages_initiative_component_share_tokens_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Admin manages initiative component share tokens" do + let(:organization) { create(:organization) } + let(:user) { create(:user, :admin, :confirmed, organization:) } + let!(:participatory_space) do + create(:initiative, organization:) + end + + it_behaves_like "manage component share tokens" do + let(:participatory_space_engine) { decidim_admin_initiatives } + end +end diff --git a/decidim-initiatives/spec/system/admin/admin_manages_initiative_components_spec.rb b/decidim-initiatives/spec/system/admin/admin_manages_initiative_components_spec.rb index 16b359f5d451f..14885aadc0758 100644 --- a/decidim-initiatives/spec/system/admin/admin_manages_initiative_components_spec.rb +++ b/decidim-initiatives/spec/system/admin/admin_manages_initiative_components_spec.rb @@ -198,8 +198,6 @@ expect(page).to have_css(".action-icon--unpublish") end end - - it_behaves_like "manage component share tokens" end context "when the component is published" do diff --git a/decidim-initiatives/spec/system/admin/admin_manages_initiative_share_tokens_spec.rb b/decidim-initiatives/spec/system/admin/admin_manages_initiative_share_tokens_spec.rb new file mode 100644 index 0000000000000..0c043121fe04f --- /dev/null +++ b/decidim-initiatives/spec/system/admin/admin_manages_initiative_share_tokens_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Admin manages initiative share tokens" do + let(:organization) { create(:organization) } + let(:user) { create(:user, :admin, :confirmed, organization:) } + let!(:participatory_space) do + create(:initiative, organization:) + end + + it_behaves_like "manage participatory space share tokens" do + let(:participatory_space_path) { decidim_admin_initiatives.edit_initiative_path(participatory_space) } + let(:participatory_spaces_path) { decidim_admin_initiatives.initiatives_path } + end +end diff --git a/decidim-initiatives/spec/system/preview_initiative_with_share_token_spec.rb b/decidim-initiatives/spec/system/preview_initiative_with_share_token_spec.rb new file mode 100644 index 0000000000000..bf19aec9acd24 --- /dev/null +++ b/decidim-initiatives/spec/system/preview_initiative_with_share_token_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Preview initiative with share token" do + let(:organization) { create(:organization) } + let!(:participatory_space) { create(:initiative, :created, organization:) } + let(:resource_path) { decidim_initiatives.initiative_path(participatory_space) } + + it_behaves_like "preview participatory space with a share_token" +end diff --git a/decidim-meetings/spec/system/preview_meetings_with_share_token_spec.rb b/decidim-meetings/spec/system/preview_meetings_with_share_token_spec.rb index 844f7892e22e4..5e6bacd873887 100644 --- a/decidim-meetings/spec/system/preview_meetings_with_share_token_spec.rb +++ b/decidim-meetings/spec/system/preview_meetings_with_share_token_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -describe "Preview meetings with share token" do +describe "preview meetings with a share token" do let(:manifest_name) { "meetings" } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-pages/spec/system/preview_pages_with_share_token_spec.rb b/decidim-pages/spec/system/preview_pages_with_share_token_spec.rb index 657244defaed9..273ee7869d23f 100644 --- a/decidim-pages/spec/system/preview_pages_with_share_token_spec.rb +++ b/decidim-pages/spec/system/preview_pages_with_share_token_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -describe "Preview pages with share token" do +describe "preview pages with a share token" do let(:manifest_name) { "pages" } let(:body) do @@ -16,5 +16,5 @@ let!(:page_component) { create(:page, component:, body:) } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-participatory_processes/app/controllers/decidim/participatory_processes/admin/component_share_tokens_controller.rb b/decidim-participatory_processes/app/controllers/decidim/participatory_processes/admin/component_share_tokens_controller.rb new file mode 100644 index 0000000000000..14442d7b4b773 --- /dev/null +++ b/decidim-participatory_processes/app/controllers/decidim/participatory_processes/admin/component_share_tokens_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Decidim + module ParticipatoryProcesses + module Admin + # This controller allows sharing unpublished things. + # It is targeted for customizations for sharing unpublished things that lives under + # an process. + class ComponentShareTokensController < Decidim::Admin::ShareTokensController + include Concerns::ParticipatoryProcessAdmin + + def resource + @resource ||= current_participatory_space.components.find(params[:component_id]) + end + end + end + end +end diff --git a/decidim-participatory_processes/app/controllers/decidim/participatory_processes/admin/participatory_process_share_tokens_controller.rb b/decidim-participatory_processes/app/controllers/decidim/participatory_processes/admin/participatory_process_share_tokens_controller.rb new file mode 100644 index 0000000000000..c2ede3047a5ab --- /dev/null +++ b/decidim-participatory_processes/app/controllers/decidim/participatory_processes/admin/participatory_process_share_tokens_controller.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Decidim + module ParticipatoryProcesses + module Admin + # This controller allows admins to manage moderations in a participatory process. + class ParticipatoryProcessShareTokensController < Decidim::Admin::ShareTokensController + include Concerns::ParticipatoryProcessAdmin + + def resource + current_participatory_process + end + end + end + end +end diff --git a/decidim-participatory_processes/app/models/decidim/participatory_process.rb b/decidim-participatory_processes/app/models/decidim/participatory_process.rb index cda121a17aa2d..84d54093af75c 100644 --- a/decidim-participatory_processes/app/models/decidim/participatory_process.rb +++ b/decidim-participatory_processes/app/models/decidim/participatory_process.rb @@ -23,6 +23,7 @@ class ParticipatoryProcess < ApplicationRecord include Decidim::TranslatableResource include Decidim::HasArea include Decidim::FilterableResource + include Decidim::ShareableWithToken translatable_fields :title, :subtitle, :short_description, :description, :developer_group, :meta_scope, :local_area, :target, :participatory_scope, :participatory_structure, :announcement @@ -200,6 +201,10 @@ def attachment_context :admin end + def shareable_url(share_token) + EngineRouter.main_proxy(self).participatory_process_url(self, share_token: share_token.token) + end + # Allow ransacker to search for a key in a hstore column (`title`.`en`) ransacker_i18n :title diff --git a/decidim-participatory_processes/app/permissions/decidim/participatory_processes/permissions.rb b/decidim-participatory_processes/app/permissions/decidim/participatory_processes/permissions.rb index a8e54d5da87d7..e259618ecbdef 100644 --- a/decidim-participatory_processes/app/permissions/decidim/participatory_processes/permissions.rb +++ b/decidim-participatory_processes/app/permissions/decidim/participatory_processes/permissions.rb @@ -117,6 +117,7 @@ def public_read_process_action? return disallow! unless can_view_private_space? return allow! if user&.admin? return allow! if process.published? + return allow! if user_can_preview_space? toggle_allow(can_manage_process?) end @@ -237,6 +238,7 @@ def process_admin_action? :process_step, :process_user_role, :export_space, + :share_tokens, :import ].include?(permission_action.subject) allow! if is_allowed @@ -256,11 +258,18 @@ def org_admin_action? :process_step, :process_user_role, :export_space, + :share_tokens, :import ].include?(permission_action.subject) allow! if is_allowed end + def user_can_preview_space? + context[:share_token].present? && Decidim::ShareToken.use!(token_for: process, token: context[:share_token], user:) + rescue ActiveRecord::RecordNotFound, StandardError + nil + end + def participatory_process_type_action? return unless permission_action.subject == :participatory_process_type return disallow! unless user.admin? diff --git a/decidim-participatory_processes/app/views/decidim/participatory_processes/admin/participatory_processes/index.html.erb b/decidim-participatory_processes/app/views/decidim/participatory_processes/admin/participatory_processes/index.html.erb index 829ded669fe09..84cfd004e7767 100644 --- a/decidim-participatory_processes/app/views/decidim/participatory_processes/admin/participatory_processes/index.html.erb +++ b/decidim-participatory_processes/app/views/decidim/participatory_processes/admin/participatory_processes/index.html.erb @@ -58,6 +58,11 @@ <% end %> + <% if allowed_to? :read, :share_tokens, current_participatory_space: process %> + <%= icon_link_to "share-line", decidim_admin_participatory_processes.participatory_process_share_tokens_path(process), t("actions.share_tokens", scope: "decidim.admin"), class: "action-icon--new" %> + <% else %> + + <% end %> <% if allowed_to? :update, :process, process: process %> <%= icon_link_to "pencil-line", edit_participatory_process_path(process), t("actions.configure", scope: "decidim.admin"), class: "action-icon--new" %> diff --git a/decidim-participatory_processes/lib/decidim/participatory_processes/admin_engine.rb b/decidim-participatory_processes/lib/decidim/participatory_processes/admin_engine.rb index 4a5a1658be6f5..5076c0fc2a426 100644 --- a/decidim-participatory_processes/lib/decidim/participatory_processes/admin_engine.rb +++ b/decidim-participatory_processes/lib/decidim/participatory_processes/admin_engine.rb @@ -60,6 +60,7 @@ class AdminEngine < ::Rails::Engine put :unpublish get :share end + resources :component_share_tokens, except: [:show], path: "share_tokens", as: "share_tokens" resources :exports, only: :create resources :imports, only: [:new, :create] do get :example, on: :collection @@ -86,6 +87,8 @@ class AdminEngine < ::Rails::Engine end end end + + resources :participatory_process_share_tokens, except: [:show], path: "share_tokens" end scope "/participatory_processes/:participatory_process_slug/components/:component_id/manage" do diff --git a/decidim-participatory_processes/lib/decidim/participatory_processes/menu.rb b/decidim-participatory_processes/lib/decidim/participatory_processes/menu.rb index 918841a86c8cc..3ef4e6ad0bf0c 100644 --- a/decidim-participatory_processes/lib/decidim/participatory_processes/menu.rb +++ b/decidim-participatory_processes/lib/decidim/participatory_processes/menu.rb @@ -100,6 +100,7 @@ def self.register_admin_participatory_process_components_menu! active: is_active_link?(manage_component_path(component)) || is_active_link?(decidim_admin_participatory_processes.edit_component_path(current_participatory_space, component)) || is_active_link?(decidim_admin_participatory_processes.edit_component_permissions_path(current_participatory_space, component)) || + is_active_link?(decidim_admin_participatory_processes.component_share_tokens_path(current_participatory_space, component)) || participatory_space_active_link?(component), if: component.manifest.admin_engine && user_role_config.component_is_accessible?(component.manifest_name) end @@ -173,6 +174,13 @@ def self.register_admin_participatory_process_menu! active: is_active_link?(decidim_admin_participatory_processes.moderations_path(current_participatory_space)), icon_name: "flag-line", if: allowed_to?(:read, :moderation, current_participatory_space:) + + menu.add_item :participatory_process_share_tokens, + I18n.t("menu.share_tokens", scope: "decidim.admin"), + decidim_admin_participatory_processes.participatory_process_share_tokens_path(current_participatory_space), + active: is_active_link?(decidim_admin_participatory_processes.participatory_process_share_tokens_path(current_participatory_space)), + icon_name: "share-line", + if: allowed_to?(:read, :share_tokens, current_participatory_space:) end end diff --git a/decidim-participatory_processes/spec/shared/manage_process_components_examples.rb b/decidim-participatory_processes/spec/shared/manage_process_components_examples.rb index 72143a25b03dc..2c9d15994d385 100644 --- a/decidim-participatory_processes/spec/shared/manage_process_components_examples.rb +++ b/decidim-participatory_processes/spec/shared/manage_process_components_examples.rb @@ -26,8 +26,6 @@ find(".dummy").click end - expect(page).to have_no_content("Share tokens") - within ".item__edit-form .new_component" do fill_in_i18n( :component_name, @@ -299,14 +297,6 @@ end context "when the component is unpublished" do - it "shows the share tokens section" do - within ".component-#{component.id}" do - click_on "Configure" - end - - expect(page).to have_content("Share tokens") - end - it "publishes the component" do within ".component-#{component.id}" do click_on "Publish" @@ -336,21 +326,11 @@ } )) end - - it_behaves_like "manage component share tokens" end context "when the component is published" do let(:published_at) { Time.current } - it "does not show the share tokens section" do - within ".component-#{component.id}" do - click_on "Configure" - end - - expect(page).to have_no_content("Share tokens") - end - it "unpublishes the component" do within ".component-#{component.id}" do click_on "Unpublish" diff --git a/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_process_component_share_tokens_spec.rb b/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_process_component_share_tokens_spec.rb new file mode 100644 index 0000000000000..dd663d92b6691 --- /dev/null +++ b/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_process_component_share_tokens_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Admin manages participatory process component share tokens" do + include_context "when admin administrating a participatory process" + + it_behaves_like "manage component share tokens" do + let(:participatory_space) { participatory_process } + let(:participatory_space_engine) { decidim_admin_participatory_processes } + end +end diff --git a/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_process_share_tokens_spec.rb b/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_process_share_tokens_spec.rb new file mode 100644 index 0000000000000..acd0a914561e6 --- /dev/null +++ b/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_process_share_tokens_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Admin manages participatory process share tokens" do + include_context "when admin administrating a participatory process" + let(:participatory_space) { participatory_process } + let(:participatory_space_path) { decidim_admin_participatory_processes.edit_participatory_process_path(participatory_process) } + let(:participatory_spaces_path) { decidim_admin_participatory_processes.participatory_processes_path } + + it_behaves_like "manage participatory space share tokens" + + context "when the user is a process admin" do + let(:user) { create(:user, :confirmed, :admin_terms_accepted, organization:) } + let!(:role) { create(:participatory_process_user_role, user:, participatory_process:, role: :admin) } + + it_behaves_like "manage participatory space share tokens" + end +end diff --git a/decidim-participatory_processes/spec/system/preview_participatory_process_with_share_token_spec.rb b/decidim-participatory_processes/spec/system/preview_participatory_process_with_share_token_spec.rb new file mode 100644 index 0000000000000..7dadba5d57f27 --- /dev/null +++ b/decidim-participatory_processes/spec/system/preview_participatory_process_with_share_token_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Preview participatory process with share token" do + let(:organization) { create(:organization) } + let!(:participatory_space) { create(:participatory_process, organization:, published_at: nil) } + let(:resource_path) { decidim_participatory_processes.participatory_process_path(participatory_space) } + + it_behaves_like "preview participatory space with a share_token" +end diff --git a/decidim-proposals/spec/system/preview_proposals_with_share_token_spec.rb b/decidim-proposals/spec/system/preview_proposals_with_share_token_spec.rb index 6b92928203077..fc36958a99eba 100644 --- a/decidim-proposals/spec/system/preview_proposals_with_share_token_spec.rb +++ b/decidim-proposals/spec/system/preview_proposals_with_share_token_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -describe "Preview proposals with share token" do +describe "preview proposals with a share token" do let(:manifest_name) { "proposals" } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-sortitions/spec/system/decidim/sortitions/preview_sortitions_with_share_token_spec.rb b/decidim-sortitions/spec/system/decidim/sortitions/preview_sortitions_with_share_token_spec.rb index a3cbcc00a7d1b..cdf94b1a9d781 100644 --- a/decidim-sortitions/spec/system/decidim/sortitions/preview_sortitions_with_share_token_spec.rb +++ b/decidim-sortitions/spec/system/decidim/sortitions/preview_sortitions_with_share_token_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -describe "Preview sortitions with share token" do +describe "preview sortitions with a share token" do let(:manifest_name) { "sortitions" } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-surveys/app/views/decidim/surveys/admin/component/_actions.html.erb b/decidim-surveys/app/views/decidim/surveys/admin/component/_actions.html.erb index 3c9fec22c0f61..f1c87caab50a0 100644 --- a/decidim-surveys/app/views/decidim/surveys/admin/component/_actions.html.erb +++ b/decidim-surveys/app/views/decidim/surveys/admin/component/_actions.html.erb @@ -4,11 +4,12 @@ <% end %> -<% if allowed_to? :share, :component, component: component %> - <%= icon_link_to "share-line", url_for(action: :share, id: component, controller: "components"), t("actions.share", scope: "decidim.admin"), target: :blank, class: "action-icon--share" %> +<% if component.manifest.admin_engine && allowed_to?(:share, :component, component: component) %> + <%= icon_link_to "share-line", component_share_tokens_path(component_id: component), t("actions.share", scope: "decidim.admin"), class: "action-icon--share" %> <% else %> <% end %> + <% if allowed_to? :update, :component, component: component %> <%= icon_link_to "settings-4-line", url_for(action: :edit, id: component, controller: "components"), t("actions.configure", scope: "decidim.admin"), class: "action-icon--configure" %> <% else %> @@ -17,7 +18,11 @@ <% if allowed_to?(:update, :component, component: component) %> <% if component.published? %> - <%= icon_link_to "close-circle-line", url_for(action: :unpublish, id: component, controller: "components"), t("actions.unpublish", scope: "decidim.admin"), class: "action-icon--unpublish", method: :put %> + <% if component.visible? %> + <%= icon_link_to "eye-close", url_for(action: :hide, id: component, controller: "components"), t("actions.menu_hidden", scope: "decidim.admin"), class: "action-icon--unpublish", method: :put %> + <% else %> + <%= icon_link_to "close-circle-line", url_for(action: :unpublish, id: component, controller: "components"), t("actions.unpublish", scope: "decidim.admin"), class: "action-icon--menu-hidden", method: :put %> + <% end %> <% else %> <%= icon_link_to "check-line", url_for(action: :publish, id: component, controller: "components"), t("actions.publish", scope: "decidim.admin"), class: "action-icon--publish", method: :put, data: { confirm: t(".answers_alert") } %> <% end %> diff --git a/decidim-surveys/spec/system/survey_spec.rb b/decidim-surveys/spec/system/survey_spec.rb index f38404f4f534f..d845b4051e030 100644 --- a/decidim-surveys/spec/system/survey_spec.rb +++ b/decidim-surveys/spec/system/survey_spec.rb @@ -33,7 +33,7 @@ include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" context "when the survey does not allow answers" do it "does not allow answering the survey" do diff --git a/docs/modules/develop/pages/share_tokens.adoc b/docs/modules/develop/pages/share_tokens.adoc index 9c787a8afcde5..741f0c32d73a8 100644 --- a/docs/modules/develop/pages/share_tokens.adoc +++ b/docs/modules/develop/pages/share_tokens.adoc @@ -1,6 +1,6 @@ = Share tokens -Share tokens can be assigned to any model to provide a system to share unpublished resources with expirable and manageable tokens. +Share tokens can be assigned to any model to provide a system to share unpublished resources with expiration dates through the creation/destruction of tokens. A share token is created by a user with an expiration time, and can be added as a query param to access otherwise restricted locations. @@ -12,7 +12,7 @@ The model must `include Decidim::ShareableWithToken` and implement `shareable_ur ---- # Public: Public URL for your_resource with given share token as query parameter def shareable_url(share_token) - your_resource_public_path(self, share_token: share_token.token) + your_resource_public_url(self, share_token: share_token.token) end ---- @@ -31,27 +31,169 @@ return unless token.present? allow! if Decidim::ShareToken.use!(token_for: your_resource, token: token) ---- +Note that, if you are using a controller who is inheriting from `Decidim::ApplicationController`, you do not need to include the `:share_token` in the context when calling methods like `enforce_permission_to`, as it is already included in the `Decidim::NeedsPermissions` class through the method `store_share_token`. + == Manage tokens -Render the partial `decidim-admin/app/views/decidim/admin/share_tokens/_share_tokens.html.erb` inside a view, with: +By default, participatory spaces like process, assemblies, conferences and initiatives are configured to have share tokens, as well as the individual components that are included in them. Participatory spaces have a "Share tokens" tab in the admin view, where you can create new tokens, see the list of existing ones, and revoke them. +Tokens can also be managed in the components view similarly as other resources to give pre-access (with and action icon like permissions for instance). + +Tokens generated for a participatory space are valid for all the components included in it (regardless of their publication status), and tokens generated for a component are valid for that component only. + +== Implementation for participatory spaces + +In order to implement share tokens for a participatory spaces, you need to: + +=== 1. Routes + +Add the `share_tokens` CRUD routes in your `admin_engine.rb` file: + +[source,ruby] +---- +scope "/assemblies/:assembly_slug" do + ... + resources :assembly_share_tokens, except: [:show], path: "share_tokens" + ... +end +---- + +=== 2. Controller + +Add the controller for the participatory space, it only requires to inherit from `Decidim::Admin::ShareTokensController` and define the `resource` method to return the participatory space: + +[source,ruby] +---- +# frozen_string_literal: true + +module Decidim + module Assemblies + module Admin + # This controller allows sharing unpublished things. + # It is targeted for customizations for sharing unpublished things that lives under + # an assembly. + class AssemblyShareTokensController < Decidim::Admin::ShareTokensController + include Concerns::AssemblyAdmin + + def resource + current_assembly + end + end + end + end +end +---- + +=== 3. Menu entry + +Add the menu entry for the share tokens in the participatory space admin view. In Decidim we do this in the `menu.rb` file for each participatory space: + +[source,ruby] +---- +Decidim.menu :admin_assembly_menu do |menu| + ... + menu.add_item :assembly_share_tokens, + I18n.t("menu.share_tokens", scope: "decidim.admin"), + decidim_admin_assemblies.assembly_share_tokens_path(current_participatory_space), + active: is_active_link?(decidim_admin_assemblies.assembly_share_tokens_path(current_participatory_space)), + icon_name: "share-line", + if: allowed_to?(:read, :share_tokens, current_participatory_space:) + ... +end +---- + +=== 4. Model + +Ensure your participatory space model includes the `Decidim::ShareableWithToken` module and implements the `shareable_url` method: + +[source,ruby] +---- +module Decidim + class Assembly < ApplicationRecord + ... + include Decidim::ShareableWithToken + ... + def shareable_url(share_token) + EngineRouter.main_proxy(self).assembly_url(self, share_token: share_token.token) + end + ... + end +end +---- + +=== 5. Permissions + +Add the permissions logic to the participatory space controller in the `permissions.rb` file: + +For admin controllers: [source,ruby] ---- -locals: { share_tokens: your_share_tokens_variable } +allow! if permission_action.subject == :share_tokens +---- + +For frontend controllers: + +[source,ruby] +---- +token = context[:share_token] + +return unless token.present? + +allow! if Decidim::ShareToken.use!(token_for: current_assembly, token: token) ---- -to let admins see and manage tokens for that resource. +== Implementation for components -== Link to url with token +Components all inherit from `Decidim::Component`, so they already have the `Decidim::ShareableWithToken` module included. But you still need to do some steps: -Implement a `share` action (see below) in the resource controller (admin scope), redirecting to a url with a newly generated token, so you can call `share_my_resource_url`. +=== 1. Routes + +Add the `share_tokens` CRUD routes in your `admin_engine.rb` file: [source,ruby] ---- -def share - @your_resource = YourResource.find(params[:id]) # or whatever - share_token = @your_resource.share_tokens.create!(user: current_user, organization: current_organization) +scope "/assemblies/:assembly_slug" do + ... + resources :components do + ... + resources :component_share_tokens, except: [:show], path: "share_tokens", as: "share_tokens" + ... + end +end +---- + +=== 2. Controller + +Add the controller for the component, it only requires to inherit from `Decidim::Admin::ShareTokensController` and define the `resource` method to return the component: - redirect_to share_token.url +[source,ruby] +---- +# frozen_string_literal: true + +module Decidim + module Assemblies + module Admin + # This controller allows sharing unpublished things. + # It is targeted for customizations for sharing unpublished things that lives under + # an assembly. + class ComponentShareTokensController < Decidim::Admin::ShareTokensController + include Concerns::AssemblyAdmin + + def resource + @resource ||= current_participatory_space.components.find(params[:component_id]) + end + end + end + end end ---- + +=== 3. Permissions + +Similarly, add the same permissions logic to the component controller in the `permissions.rb` file as for participatory spaces. + + +== Other implementations + +You can implement share tokens for any other model by following the same steps as for participatory spaces and components. +In that case, however, you might have to override some methods from the `Decidim::Admin::ShareTokensController` to adapt them to your model (check the source code for details). \ No newline at end of file From 1c44e5ceb9bbb09ce86b49a399b9e758217e8de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Verg=C3=A9s?= Date: Tue, 3 Jun 2025 14:14:38 +0200 Subject: [PATCH 02/61] remove non valid methods --- decidim-core/app/models/decidim/share_token.rb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/decidim-core/app/models/decidim/share_token.rb b/decidim-core/app/models/decidim/share_token.rb index 4cd67777b2429..f937de3417b74 100644 --- a/decidim-core/app/models/decidim/share_token.rb +++ b/decidim-core/app/models/decidim/share_token.rb @@ -51,14 +51,6 @@ def component token_for.component if token_for.respond_to?(:component) end - def self.ransackable_attributes(_auth_object = nil) - %w(token expires_at last_used_at registered_only) - end - - def self.ransackable_associations(_auth_object = nil) - %w(organization token_for user) - end - private def generate From 8f116e8a1c3aa7b7a13b92c81d29dc575fac3f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Verg=C3=A9s?= Date: Wed, 16 Oct 2024 22:31:25 +0200 Subject: [PATCH 03/61] Remove flaky test in share tokens preview spec (#13546) * remove nested * add reload * debug * manually set tokens * add sleep * cleanup --- .../preview_with_share_token_examples.rb | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/decidim-core/lib/decidim/core/test/shared_examples/preview_with_share_token_examples.rb b/decidim-core/lib/decidim/core/test/shared_examples/preview_with_share_token_examples.rb index b27c5e2113fc1..a2e8fb4c831ed 100644 --- a/decidim-core/lib/decidim/core/test/shared_examples/preview_with_share_token_examples.rb +++ b/decidim-core/lib/decidim/core/test/shared_examples/preview_with_share_token_examples.rb @@ -13,7 +13,7 @@ end context "when a share_token is provided" do - let(:share_token) { create(:share_token, :with_token, token_for: resource) } + let(:share_token) { create(:share_token, token: "VALID_TOKEN", token_for: resource) } let(:params) { { share_token: share_token.token } } before do @@ -37,7 +37,7 @@ end context "when an invalid share_token is provided" do - let(:share_token) { create(:share_token, :with_token, :expired, token_for: resource) } + let(:share_token) { create(:share_token, :expired, token: "INVALID_TOKEN_FOR_UNREGISTERED", token_for: resource) } it "does not allow visiting resource" do expect(page).to have_content "You are not authorized" @@ -46,24 +46,22 @@ end context "when the token requires the user to be registered" do - let(:share_token) { create(:share_token, :with_token, token_for: resource, registered_only: true) } + let!(:share_token) { create(:share_token, token: "VALID_TOKEN_FOR_REGISTERED", token_for: resource, registered_only: true) } + let!(:user) { create(:user, :confirmed, organization:) } it "does not allow visiting resource" do expect(page).to have_content "You are not authorized" expect(page).to have_no_current_path(resource_path, ignore_query: true) end - context "when a user is logged" do - let(:user) { create(:user, :confirmed, organization:) } - - it "allows visiting resource" do - login_as user, scope: :user - uri = URI(resource_path) - uri.query = URI.encode_www_form(params.to_a) - visit uri - expect(page).to have_no_content "You are not authorized" - expect(page).to have_current_path(resource_path, ignore_query: true) - end + it "allows visiting resource to logged users" do + login_as user, scope: :user + sleep 0.5 # add a delay to ensure the user session cookie is set + uri = URI(resource_path) + uri.query = URI.encode_www_form(params.to_a) + visit uri + expect(page).to have_no_content "You are not authorized" + expect(page).to have_current_path(resource_path, ignore_query: true) end end end From 3a7ea882d615ec21d1ffae575c6c0f468e40242f Mon Sep 17 00:00:00 2001 From: Anna Topalidi <60363870+antopalidi@users.noreply.github.com> Date: Wed, 16 Apr 2025 09:42:45 +0200 Subject: [PATCH 04/61] Waiting list for the meeting (#14492) Co-authored-by: Alexandru Emil Lupu --- .../generators/test/generator_examples.rb | 1 + .../cancelation_modal.erb | 13 +- .../show.erb | 2 +- ...cancel_registration_meeting_button_cell.rb | 32 ++++ .../registration_modal.erb | 8 +- .../meetings/join_meeting_button/show.erb | 48 +++--- .../join_meeting_button/waitlist_button.erb | 22 +++ .../meetings/join_meeting_button_cell.rb | 24 +++ .../meetings/admin/update_registrations.rb | 26 ++- .../decidim/meetings/join_waitlist.rb | 53 ++++++ .../decidim/meetings/leave_meeting.rb | 9 + .../meetings/registrations_controller.rb | 35 +++- .../decidim/meetings/meetings_helper.rb | 13 ++ .../meetings/promote_from_waitlist_job.rb | 63 +++++++ .../app/models/decidim/meetings/meeting.rb | 8 +- .../models/decidim/meetings/registration.rb | 5 + .../decidim/meetings/permissions.rb | 9 + .../meetings/meetings/_meeting_aside.html.erb | 17 +- decidim-meetings/config/locales/en.yml | 20 ++- ...tions_to_decidim_meetings_registrations.rb | 8 + decidim-meetings/lib/decidim/meetings.rb | 4 + .../lib/decidim/meetings/engine.rb | 2 + .../admin/update_registrations_spec.rb | 88 ++++++++++ .../spec/commands/join_waitlist_spec.rb | 86 ++++++++++ .../spec/commands/leave_meeting_spec.rb | 53 ++++++ .../meetings/registrations_controller_spec.rb | 162 ++++++++++++++++++ .../promote_from_waitlist_job_spec.rb | 56 ++++++ .../spec/system/meeting_registrations_spec.rb | 4 +- .../spec/system/meeting_waiting_list_spec.rb | 142 +++++++++++++++ .../pages/environment_variables.adoc | 5 + 30 files changed, 959 insertions(+), 59 deletions(-) create mode 100644 decidim-meetings/app/cells/decidim/meetings/join_meeting_button/waitlist_button.erb create mode 100644 decidim-meetings/app/commands/decidim/meetings/join_waitlist.rb create mode 100644 decidim-meetings/app/jobs/decidim/meetings/promote_from_waitlist_job.rb create mode 100644 decidim-meetings/db/migrate/20250408071941_add_status_to_registrations_to_decidim_meetings_registrations.rb create mode 100644 decidim-meetings/spec/commands/join_waitlist_spec.rb create mode 100644 decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb create mode 100644 decidim-meetings/spec/jobs/decidim/meetings/promote_from_waitlist_job_spec.rb create mode 100644 decidim-meetings/spec/system/meeting_waiting_list_spec.rb diff --git a/decidim-generators/lib/decidim/generators/test/generator_examples.rb b/decidim-generators/lib/decidim/generators/test/generator_examples.rb index 105e181b7c787..0d5fb80dc975b 100644 --- a/decidim-generators/lib/decidim/generators/test/generator_examples.rb +++ b/decidim-generators/lib/decidim/generators/test/generator_examples.rb @@ -225,6 +225,7 @@ "PROPOSALS_PROCESS_GROUP_HIGHLIGHTED_PROPOSALS_LIMIT" => "5", "MEETINGS_UPCOMING_MEETING_NOTIFICATION" => "3", "MEETINGS_ENABLE_PROPOSAL_LINKING" => "false", + "MEETINGS_WAITING_LIST_ENABLED" => "true", "MEETINGS_EMBEDDABLE_SERVICES" => "www.youtube.com www.twitch.tv meet.jit.si 8x8.vc", "BUDGETS_ENABLE_PROPOSAL_LINKING" => "false", "ACCOUNTABILITY_ENABLE_PROPOSAL_LINKING" => "false", diff --git a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/cancelation_modal.erb b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/cancelation_modal.erb index ccf4824254922..4f14fd1dc6f25 100644 --- a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/cancelation_modal.erb +++ b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/cancelation_modal.erb @@ -2,18 +2,21 @@ <%= decidim_form_for(registration_form, url: meeting_registration_path(model), method: :delete) do |form| %>
<%= icon "door-open-line" %> -

<%= t("leave", scope: "decidim.meetings.meetings.show") %>

+

+ <%= i18n_modal_title %> +

- -
<%= t("leave_confirmation", scope: "decidim.meetings.meetings.show") %>
+
+ <%= i18n_modal_confirmation_text %> +
-
diff --git a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/show.erb b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/show.erb index dfb7419fb23d4..b472164a5136f 100644 --- a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/show.erb +++ b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button/show.erb @@ -5,6 +5,6 @@ class: button_classes, data: { "dialog-open": "meeting-cancelation-confirm-#{model.id}" } ) do %> - <%= t("leave", scope: "decidim.meetings.meetings.show") %> + <%= cancel_button_text %> <%= icon icon_name %> <% end %> diff --git a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb index c7de1b70f6e0e..7ad146b579217 100644 --- a/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb +++ b/decidim-meetings/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb @@ -19,6 +19,38 @@ def current_component model.component end + def registration_status + model.registrations.find_by(user: current_user)&.status + end + + def action_keys + if registration_status == "waiting_list" + { + button: "leave_waitlist", + modal_title: "leave_waitlist", + modal_confirmation: "leave_waitlist_confirmation" + } + else + { + button: "leave", + modal_title: "leave", + modal_confirmation: "leave_confirmation" + } + end + end + + def cancel_button_text + I18n.t(action_keys[:button], scope: "decidim.meetings.meetings.show") + end + + def i18n_modal_title + I18n.t(action_keys[:modal_title], scope: "decidim.meetings.meetings.show") + end + + def i18n_modal_confirmation_text + I18n.t(action_keys[:modal_confirmation], scope: "decidim.meetings.meetings.show") + end + def button_classes "button button__sm button__text-secondary" end diff --git a/decidim-meetings/app/cells/decidim/meetings/join_meeting_button/registration_modal.erb b/decidim-meetings/app/cells/decidim/meetings/join_meeting_button/registration_modal.erb index 9c2536cf18068..44b7cffe059b2 100644 --- a/decidim-meetings/app/cells/decidim/meetings/join_meeting_button/registration_modal.erb +++ b/decidim-meetings/app/cells/decidim/meetings/join_meeting_button/registration_modal.erb @@ -1,8 +1,8 @@ -<%= decidim_modal id: "meeting-registration-confirm-#{model.id}", class: "meeting__registration-modal" do %> - <%= decidim_form_for(registration_form, url: meeting_registration_path(model), method: :post) do |form| %> +<%= decidim_modal id: "meeting-#{registration_action}-confirm-#{model.id}", class: "meeting__registration-modal" do %> + <%= decidim_form_for(registration_form, url: registration_path, method: :post) do |form| %>
<%= icon "login-circle-line" %> -

<%= t("join", scope: "decidim.meetings.meetings.show") %>

+

<%= t(registration_translation_key, scope: "decidim.meetings.meetings.show") %>

@@ -13,7 +13,7 @@
-
+ <%= render "decidim/meetings/admin/meetings/reminders", form: %> <%= render "decidim/meetings/admin/meetings/services", form: , id: tabs_id_for_service(blank_service) %> diff --git a/decidim-meetings/app/views/decidim/meetings/admin/meetings/_reminders.html.erb b/decidim-meetings/app/views/decidim/meetings/admin/meetings/_reminders.html.erb new file mode 100644 index 0000000000000..57182ddee1f22 --- /dev/null +++ b/decidim-meetings/app/views/decidim/meetings/admin/meetings/_reminders.html.erb @@ -0,0 +1,19 @@ +
+
+ +
+
+
+
+ <%= form.check_box :reminder_enabled, label: t(".reminder_enabled"), "data-toggle": "customize_reminder_times" %> +
+
"> + <%= form.number_field :send_reminders_before_hours, label: t(".send_reminder"), help_text: t(".send_reminder_help_text"), min: 1, step: 1, class: "mb-2" %> + <%= form.translated :text_area, :reminder_message_custom_content, label: t(".reminder_message"), hashtaggable: true, help_text: t(".reminder_message_help_text") %> +
+
+
+
diff --git a/decidim-meetings/config/locales/en.yml b/decidim-meetings/config/locales/en.yml index 3d4d1889a971d..7ff7e65688311 100644 --- a/decidim-meetings/config/locales/en.yml +++ b/decidim-meetings/config/locales/en.yml @@ -219,10 +219,11 @@ en: email_subject: The "%{resource_title}" meeting has enabled registrations. notification_title: The %{resource_title} meeting has enabled registrations. upcoming_meeting: - email_intro: The "%{resource_title}" meeting will start in less than 48h. - email_outro: You have received this notification because you are following the "%{resource_title}" meeting. You can unfollow it from the previous link. - email_subject: The "%{resource_title}" meeting will start in less than 48h. - notification_title: The %{resource_title} meeting will start in less than 48h. + default_body: The "{{meeting_title}}" meeting will start in less than {{before_hours}}h. + email_intro: The "%{resource_title}" meeting will start in less than %{reminders_before_hours}h. + email_outro: You have received this notification because you are following the %{resource_title} meeting. You can unfollow it from the previous link. + email_subject: The "%{resource_title}" meeting will start in less than %{reminders_before_hours}h. + notification_title: The %{resource_title} meeting will start in less than %{reminders_before_hours}h. forms: meetings: attendees_count_help_text: Do not forget to include the total number of attendees at your meeting, whether in person, online or hybrid. @@ -351,6 +352,13 @@ en: publish: invalid: There was a problem publishing this meeting. success: Meeting successfully published. + reminders: + reminder_enabled: Send a reminder for this meeting + reminder_message: Reminder email content + reminder_message_help_text: Customize the message as needed. Use {{meeting_title}} to display the meeting name. + send_reminder: Scheduled reminder email + send_reminder_help_text: Reminder time in hours before the meeting. + title: Reminders service: description: Description down: Down diff --git a/decidim-meetings/db/migrate/20250403094034_add_reminder_customization_to_decidim_meetings.rb b/decidim-meetings/db/migrate/20250403094034_add_reminder_customization_to_decidim_meetings.rb new file mode 100644 index 0000000000000..9859b9a03ad9c --- /dev/null +++ b/decidim-meetings/db/migrate/20250403094034_add_reminder_customization_to_decidim_meetings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddReminderCustomizationToDecidimMeetings < ActiveRecord::Migration[7.0] + def change + add_column :decidim_meetings_meetings, :reminder_enabled, :boolean, default: true, null: false + add_column :decidim_meetings_meetings, :send_reminders_before_hours, :integer + add_column :decidim_meetings_meetings, :reminder_message_custom_content, :jsonb, default: {}, null: false + end +end diff --git a/decidim-meetings/lib/decidim/meetings/test/factories.rb b/decidim-meetings/lib/decidim/meetings/test/factories.rb index e7774f0660c67..ad92fe32f4306 100644 --- a/decidim-meetings/lib/decidim/meetings/test/factories.rb +++ b/decidim-meetings/lib/decidim/meetings/test/factories.rb @@ -37,6 +37,9 @@ longitude { Faker::Address.longitude } start_time { 1.day.from_now } end_time { start_time.advance(hours: 2) } + reminder_enabled { true } + send_reminders_before_hours { 48 } + reminder_message_custom_content { generate_localized_description(:meeting_reminder_message, skip_injection:) } private_meeting { false } transparent { true } questionnaire { build(:questionnaire) } diff --git a/decidim-meetings/spec/commands/admin/copy_meeting_spec.rb b/decidim-meetings/spec/commands/admin/copy_meeting_spec.rb index 7afb3a04cdaa0..7b609232c7be5 100644 --- a/decidim-meetings/spec/commands/admin/copy_meeting_spec.rb +++ b/decidim-meetings/spec/commands/admin/copy_meeting_spec.rb @@ -35,6 +35,9 @@ module Decidim::Meetings address:, latitude:, longitude:, + reminder_enabled: meeting.reminder_enabled, + send_reminders_before_hours: meeting.send_reminders_before_hours, + reminder_message_custom_content: meeting.reminder_message_custom_content, scope: meeting.scope, category: meeting.category, services_to_persist:, @@ -77,6 +80,7 @@ module Decidim::Meetings expect(new_meeting.category).to eq(old_meeting.category) expect(new_meeting.component).to eq(old_meeting.component) expect(new_meeting.component).not_to eq(be_published) + expect(new_meeting.reminder_message_custom_content).to eq(old_meeting.reminder_message_custom_content) new_meeting.services.each_with_index do |service, index| expect(service.title).to eq(services[index]["title"]) diff --git a/decidim-meetings/spec/commands/admin/create_meeting_spec.rb b/decidim-meetings/spec/commands/admin/create_meeting_spec.rb index c96102382fbd7..a1e85c9b95996 100644 --- a/decidim-meetings/spec/commands/admin/create_meeting_spec.rb +++ b/decidim-meetings/spec/commands/admin/create_meeting_spec.rb @@ -27,6 +27,9 @@ module Decidim::Meetings let(:registrations_enabled) { true } let(:iframe_embed_type) { "embed_in_meeting_page" } let(:iframe_access_level) { "all" } + let(:reminder_enabled) { true } + let(:send_reminders_before_hours) { 50 } + let(:reminder_message_custom_content) { { "en" => "Custom reminder message", "es" => "Mensaje de recordatorio personalizado", "ca" => "Missatge de recordatori personalitzat" } } let(:services) do [ { @@ -73,7 +76,10 @@ module Decidim::Meetings comments_enabled: true, comments_start_time: nil, comments_end_time: nil, - iframe_access_level: + iframe_access_level:, + reminder_enabled:, + send_reminders_before_hours:, + reminder_message_custom_content: ) end @@ -102,6 +108,27 @@ module Decidim::Meetings expect(meeting.category).to eq category end + it "sets the reminder message" do + subject.call + expect(meeting.reminder_message_custom_content).to eq(reminder_message_custom_content) + end + + it "sets the send_reminders_before_hours" do + subject.call + expect(meeting.send_reminders_before_hours).to eq(send_reminders_before_hours) + expect(meeting.reminder_message_custom_content).to eq(reminder_message_custom_content) + end + + context "when reminder is not enabled" do + let(:reminder_enabled) { false } + + it "sends reminders before hours is nil" do + subject.call + expect(meeting.send_reminders_before_hours).to be_nil + expect(meeting.reminder_message_custom_content).to be_empty + end + end + it "sets the author" do subject.call expect(meeting.author).to eq organization diff --git a/decidim-meetings/spec/commands/admin/update_meeting_spec.rb b/decidim-meetings/spec/commands/admin/update_meeting_spec.rb index 8ea8a6688e44d..b9cb3430a3cbd 100644 --- a/decidim-meetings/spec/commands/admin/update_meeting_spec.rb +++ b/decidim-meetings/spec/commands/admin/update_meeting_spec.rb @@ -31,6 +31,9 @@ module Decidim::Meetings let(:registrations_enabled) { true } let(:iframe_embed_type) { "none" } let(:iframe_access_level) { nil } + let(:reminder_enabled) { true } + let(:send_reminders_before_hours) { 50 } + let(:reminder_message_custom_content) { { "en" => "Custom reminder message!", "es" => "Mensaje de recordatorio personalizado", "ca" => "Missatge de recordatori personalitzat" } } let(:form) do double( @@ -60,6 +63,9 @@ module Decidim::Meetings comments_enabled: true, comments_start_time: nil, comments_end_time: nil, + reminder_enabled:, + send_reminders_before_hours:, + reminder_message_custom_content:, iframe_access_level: ) end @@ -88,6 +94,13 @@ module Decidim::Meetings expect(meeting.category).to eq category end + it "sets the reminder settings" do + subject.call + expect(meeting.reminder_enabled).to eq reminder_enabled + expect(meeting.send_reminders_before_hours).to eq send_reminders_before_hours + expect(meeting.reminder_message_custom_content).to eq reminder_message_custom_content + end + it "sets the latitude and longitude" do subject.call expect(meeting.latitude).to eq(latitude) @@ -163,6 +176,9 @@ module Decidim::Meetings comments_enabled: true, comments_start_time: nil, comments_end_time: nil, + reminder_enabled:, + send_reminders_before_hours:, + reminder_message_custom_content:, iframe_access_level: ) end diff --git a/decidim-meetings/spec/events/decidim/meetings/upcoming_meeting_event_spec.rb b/decidim-meetings/spec/events/decidim/meetings/upcoming_meeting_event_spec.rb index c095e4f5ee7cb..fcae9dced938c 100644 --- a/decidim-meetings/spec/events/decidim/meetings/upcoming_meeting_event_spec.rb +++ b/decidim-meetings/spec/events/decidim/meetings/upcoming_meeting_event_spec.rb @@ -22,4 +22,22 @@ expect(subject.resource_text).to eq translated(resource.description) end end + + describe "custom reminder message" do + let(:resource) do + create(:meeting, + title: { en: "Custom Meeting" }, + reminder_enabled: true, + send_reminders_before_hours: 25, + reminder_message_custom_content: { en: "Reminder for the {{meeting_title}} meeting. Meeting will start in {{before_hours}}h" }) + end + + it "interpolates the meeting title into the custom message" do + expect(subject.email_intro).to eq "Reminder for the Custom Meeting meeting. Meeting will start in 25h" + end + + it "generates the email subject with correct hours" do + expect(subject.email_subject).to eq("The \"#{resource_title}\" meeting will start in less than 25h.") + end + end end diff --git a/decidim-meetings/spec/forms/admin/meeting_form_spec.rb b/decidim-meetings/spec/forms/admin/meeting_form_spec.rb index 8a9801a71025a..ae295eeb0d3cd 100644 --- a/decidim-meetings/spec/forms/admin/meeting_form_spec.rb +++ b/decidim-meetings/spec/forms/admin/meeting_form_spec.rb @@ -47,6 +47,9 @@ module Decidim::Meetings let(:scope_id) { scope.id } let(:category) { create(:category, participatory_space: participatory_process) } let(:category_id) { category.id } + let(:reminder_enabled) { true } + let(:send_reminders_before_hours) { 48 } + let(:reminder_message_custom_content) { { en: "Custom reminder message" } } let(:private_meeting) { false } let(:transparent) { true } let(:type_of_meeting) { "in_person" } @@ -68,6 +71,9 @@ module Decidim::Meetings address:, start_time:, end_time:, + reminder_enabled:, + send_reminders_before_hours:, + reminder_message_custom_content:, private_meeting:, transparent:, services: services_attributes, @@ -169,7 +175,38 @@ module Decidim::Meetings expect(described_class.from_model(meeting).decidim_category_id).to eq(category_id) end + + describe "when reminder_enabled is false" do + let(:reminder_enabled) { false } + it { is_expected.to be_valid } + end + + describe "when reminder_enabled is true" do + context "and send_reminders_before_hours is missing" do + let(:send_reminders_before_hours) { nil } + + it { is_expected.not_to be_valid } + end + + context "and send_reminders_before_hours is present" do + let(:send_reminders_before_hours) { 50 } + + it { is_expected.to be_valid } + end + + context "and send_reminders_before_hours is not valid" do + let(:send_reminders_before_hours) { -1 } + + it { is_expected.not_to be_valid } + end + + context "and reminder_message_custom_content is missing" do + let(:reminder_message_custom_content) { nil } + + it { is_expected.to be_valid } + end + end describe "services_to_persist" do subject { form.services_to_persist } diff --git a/decidim-meetings/spec/system/admin/admin_manages_meetings_spec.rb b/decidim-meetings/spec/system/admin/admin_manages_meetings_spec.rb index d76212a11ca51..b3191d66b303e 100644 --- a/decidim-meetings/spec/system/admin/admin_manages_meetings_spec.rb +++ b/decidim-meetings/spec/system/admin/admin_manages_meetings_spec.rb @@ -301,6 +301,19 @@ fill_in_datepicker :meeting_end_time_date, with: meeting_end_date fill_in_timepicker :meeting_end_time_time, with: meeting_end_time + expect(page).to have_content("Send a reminder for this meeting") + expect(page).to have_content("Scheduled reminder email") + expect(page).to have_content("Reminder email content") + + fill_in :meeting_send_reminders_before_hours, with: 24 + fill_in_i18n( + :meeting_reminder_message_custom_content, + "#meeting-reminder_message_custom_content-tabs", + en: "Custom message for the {{meeting_title}} meeting", + es: "Custom message for the {{meeting_title}} meeting", + ca: "Custom message for the {{meeting_title}} meeting" + ) + select translated(scope.name), from: :meeting_decidim_scope_id select translated(category.name), from: :meeting_decidim_category_id From b2876ecc167e580b0b3c353c6ff60f4439e68fe8 Mon Sep 17 00:00:00 2001 From: Tom Greenwood <101816158+greenwoodt@users.noreply.github.com> Date: Fri, 25 Jul 2025 10:26:22 +0200 Subject: [PATCH 06/61] Fix meetings duplication feature (#14943) --- .../decidim/meetings/admin/copy_meeting.rb | 2 +- .../admin/copy_meeting_fields_spec.rb | 94 +++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 decidim-meetings/spec/commands/admin/copy_meeting_fields_spec.rb diff --git a/decidim-meetings/app/commands/decidim/meetings/admin/copy_meeting.rb b/decidim-meetings/app/commands/decidim/meetings/admin/copy_meeting.rb index 741fe66dd3adb..85d7b725920a2 100644 --- a/decidim-meetings/app/commands/decidim/meetings/admin/copy_meeting.rb +++ b/decidim-meetings/app/commands/decidim/meetings/admin/copy_meeting.rb @@ -71,7 +71,7 @@ def copy_meeting! registration_url: form.registration_url, reminder_enabled: form.reminder_enabled, send_reminders_before_hours: form.send_reminders_before_hours, - reminder_message_custom_content: form.reminder_message_custom_content, + reminder_message_custom_content: form.reminder_message_custom_content || meeting.reminder_message_custom_content, **fields_from_meeting ) end diff --git a/decidim-meetings/spec/commands/admin/copy_meeting_fields_spec.rb b/decidim-meetings/spec/commands/admin/copy_meeting_fields_spec.rb new file mode 100644 index 0000000000000..afdf5ddcea8ba --- /dev/null +++ b/decidim-meetings/spec/commands/admin/copy_meeting_fields_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Meetings + module Admin + describe CopyMeeting do + subject { described_class.new(form, meeting) } + + let(:organization) { create(:organization) } + let(:participatory_process) { create(:participatory_process, organization: organization) } + let(:current_component) { create(:component, manifest_name: "meetings", participatory_space: participatory_process) } + let(:user) { create(:user, :admin, :confirmed, organization: organization) } + let(:meeting) { create(:meeting, :published, component: current_component, **meeting_attributes) } + + let(:meeting_attributes) do + { + registrations_enabled: true, + available_slots: 50, + registration_terms: { "en" => "Custom registration terms" }, + reserved_slots: 10, + customize_registration_email: true, + registration_form_enabled: true, + registration_email_custom_content: { "en" => "Custom email content" } + } + end + + let(:form) do + double( + invalid?: false, + current_user: user, + current_organization: organization, + taxonomies: [], + title: { "en" => "Copied Meeting" }, + description: { "en" => "Copied description" }, + end_time: 2.hours.from_now, + start_time: 1.hour.from_now, + address: "Test Address", + latitude: 40.1234, + longitude: 2.1234, + location: { "en" => "Test Location" }, + location_hints: { "en" => "Test hints" }, + private_meeting: false, + transparent: true, + questionnaire: nil, + online_meeting_url: "https://example.com", + type_of_meeting: "online", + iframe_embed_type: "none", + iframe_access_level: "signed_in", + comments_enabled: true, + comments_start_time: 1.hour.from_now, + comments_end_time: 2.hours.from_now, + registration_type: "on_this_platform", + registration_url: nil, + reminder_enabled: false, + send_reminders_before_hours: 1, + reminder_message_custom_content: nil, + services_to_persist: [] + ) + end + + context "when form is valid" do + it "broadcasts ok" do + expect { subject.call }.to broadcast(:ok) + end + + it "copies registration fields from original meeting" do + subject.call + copied_meeting = Meeting.last + + expect(copied_meeting.registrations_enabled).to eq(meeting.registrations_enabled) + expect(copied_meeting.available_slots).to eq(meeting.available_slots) + expect(copied_meeting.registration_terms).to eq(meeting.registration_terms) + expect(copied_meeting.reserved_slots).to eq(meeting.reserved_slots) + expect(copied_meeting.customize_registration_email).to eq(meeting.customize_registration_email) + expect(copied_meeting.registration_form_enabled).to eq(meeting.registration_form_enabled) + expect(copied_meeting.registration_email_custom_content).to eq(meeting.registration_email_custom_content) + end + + it "creates a new meeting with form attributes" do + subject.call + copied_meeting = Meeting.last + + expect(copied_meeting.title).to eq(form.title) + expect(copied_meeting.description).to eq(form.description) + expect(copied_meeting.component).to eq(meeting.component) + expect(copied_meeting.author).to eq(form.current_organization) + end + end + end + end + end +end From 4fb895913da833a79803acefd3f75f6561a212c1 Mon Sep 17 00:00:00 2001 From: Anna Topalidi <60363870+antopalidi@users.noreply.github.com> Date: Thu, 3 Apr 2025 09:24:48 +0200 Subject: [PATCH 07/61] Allow meeting addresses to be unset/TBD (#14389) * update validations, add pending_location_text * add tests * update notifications * add tests * refactor: move pending_location? logic to model * Apply suggestions from code review Co-authored-by: Alexandru Emil Lupu * fix test * fix validations * change validations * fix validations * change tests --------- Co-authored-by: Alexandru Emil Lupu --- .../app/cells/decidim/address/show.erb | 1 + .../app/cells/decidim/address_cell.rb | 8 +++- .../spec/cells/decidim/address_cell_spec.rb | 13 ++++++ .../decidim/meetings/dates_and_map_cell.rb | 2 +- .../decidim/meetings/admin/update_meeting.rb | 9 +++- .../decidim/meetings/update_meeting_event.rb | 25 ++++++++++ .../decidim/meetings/admin/meeting_form.rb | 4 +- .../decidim/meetings/base_meeting_form.rb | 2 - .../forms/decidim/meetings/meeting_form.rb | 2 + .../app/models/decidim/meetings/meeting.rb | 4 ++ decidim-meetings/config/locales/en.yml | 2 + .../commands/admin/update_meeting_spec.rb | 9 ++-- .../meetings/update_meeting_event_spec.rb | 32 +++++++++++++ .../spec/forms/admin/meeting_form_spec.rb | 28 ++++++++--- .../admin/admin_manages_meetings_spec.rb | 46 +++++++++++++++++-- decidim-meetings/spec/system/meeting_spec.rb | 33 +++++++++++++ 16 files changed, 198 insertions(+), 22 deletions(-) diff --git a/decidim-core/app/cells/decidim/address/show.erb b/decidim-core/app/cells/decidim/address/show.erb index 88addae009898..eac160580f2cf 100644 --- a/decidim-core/app/cells/decidim/address/show.erb +++ b/decidim-core/app/cells/decidim/address/show.erb @@ -24,6 +24,7 @@ <%= start_and_end_time %> + <% end %> diff --git a/decidim-core/app/cells/decidim/address_cell.rb b/decidim-core/app/cells/decidim/address_cell.rb index 718f134de1f47..591af10c75228 100644 --- a/decidim-core/app/cells/decidim/address_cell.rb +++ b/decidim-core/app/cells/decidim/address_cell.rb @@ -24,11 +24,17 @@ def location_hints end def location + return pending_location_text if model.respond_to?(:pending_location?) && model.pending_location? + decidim_sanitize_translated(model.location) end def address - decidim_sanitize_translated(model.address) + decidim_sanitize_translated(model.address) if model.respond_to?(:address) && model.address.present? + end + + def pending_location_text + t("show.pending_address", scope: "decidim.meetings.meetings") end def display_start_and_end_time? diff --git a/decidim-core/spec/cells/decidim/address_cell_spec.rb b/decidim-core/spec/cells/decidim/address_cell_spec.rb index 9cae2bdc83838..95d9fbba40803 100644 --- a/decidim-core/spec/cells/decidim/address_cell_spec.rb +++ b/decidim-core/spec/cells/decidim/address_cell_spec.rb @@ -37,6 +37,19 @@ end end + context "when address is pending" do + let(:location) { { "ca" => "", "en" => "", "es" => "", "machine_translations" => { "es" => "Location" } } } + + before do + allow(model).to receive(:location).and_return location + allow(model).to receive(:pending_location?).and_return(true) + end + + it "renders pending address text" do + expect(subject.find(".address__location")).to have_content(I18n.t("show.pending_address", scope: "decidim.meetings.meetings")) + end + end + context "with an online meeting url" do let(:my_cell) { cell("decidim/address", model, online: true) } let(:model) { create(:dummy_resource) } diff --git a/decidim-meetings/app/cells/decidim/meetings/dates_and_map_cell.rb b/decidim-meetings/app/cells/decidim/meetings/dates_and_map_cell.rb index 65e96c342aa80..ea48a3038d187 100644 --- a/decidim-meetings/app/cells/decidim/meetings/dates_and_map_cell.rb +++ b/decidim-meetings/app/cells/decidim/meetings/dates_and_map_cell.rb @@ -32,7 +32,7 @@ def same_day? end def display_map? - maps_enabled? && !online? + maps_enabled? && !online? && model.address.present? end end end diff --git a/decidim-meetings/app/commands/decidim/meetings/admin/update_meeting.rb b/decidim-meetings/app/commands/decidim/meetings/admin/update_meeting.rb index 5a3b3bc9d6bf2..aa5e78bbd2d97 100644 --- a/decidim-meetings/app/commands/decidim/meetings/admin/update_meeting.rb +++ b/decidim-meetings/app/commands/decidim/meetings/admin/update_meeting.rb @@ -45,7 +45,8 @@ def send_notification event: "decidim.events.meetings.meeting_updated", event_class: Decidim::Meetings::UpdateMeetingEvent, resource:, - followers: resource.followers + followers: resource.followers, + extra: { changed_fields: resource.previous_changes.keys & important_attributes } ) end @@ -54,13 +55,17 @@ def should_notify_followers? end def important_attributes - %w(start_time end_time address) + %w(start_time end_time address location) end def start_time_changed? resource.previous_changes["start_time"].present? end + def address_changed? + resource.previous_changes["address"].present? || resource.previous_changes["location"].present? + end + def schedule_upcoming_meeting_notification return if resource.start_time < Time.zone.now return unless resource.reminder_enabled diff --git a/decidim-meetings/app/events/decidim/meetings/update_meeting_event.rb b/decidim-meetings/app/events/decidim/meetings/update_meeting_event.rb index 53fde15176c61..3b803c3b19e5a 100644 --- a/decidim-meetings/app/events/decidim/meetings/update_meeting_event.rb +++ b/decidim-meetings/app/events/decidim/meetings/update_meeting_event.rb @@ -4,6 +4,31 @@ module Decidim module Meetings class UpdateMeetingEvent < Decidim::Events::SimpleEvent include Decidim::Meetings::MeetingEvent + + i18n_attributes :changed_fields + + def notification_title + I18n.t( + "notification_title", + scope: i18n_scope, + changed_fields: changed_fields, + resource_title: translated_attribute(resource.title), + resource_path: resource_path + ).html_safe + end + + private + + def changed_field_keys + extra[:changed_fields] || [] + end + + def changed_fields + keys = changed_field_keys + return "" if keys.empty? + + keys.map { |key| I18n.t("field_names.#{key}", scope: i18n_scope) }.to_sentence + end end end end diff --git a/decidim-meetings/app/forms/decidim/meetings/admin/meeting_form.rb b/decidim-meetings/app/forms/decidim/meetings/admin/meeting_form.rb index 632b7aa94ae7d..c42f6a06c5ff0 100644 --- a/decidim-meetings/app/forms/decidim/meetings/admin/meeting_form.rb +++ b/decidim-meetings/app/forms/decidim/meetings/admin/meeting_form.rb @@ -36,7 +36,9 @@ class MeetingForm < ::Decidim::Meetings::BaseMeetingForm validates :registration_type, presence: true validates :registration_url, presence: true, url: true, if: ->(form) { form.on_different_platform? } validates :type_of_meeting, presence: true - validates :location, translatable_presence: true, if: ->(form) { form.in_person_meeting? || form.hybrid_meeting? } + validates :address, presence: true, if: ->(form) { form.needs_address? && form.location.values.any?(&:present?) && form.address.blank? } + validates :location, translatable_presence: true, if: ->(form) { form.needs_address? && form.address.present? } + validates :address, geocoding: true, if: ->(form) { form.has_address? && !form.geocoded? && form.needs_address? } validates :online_meeting_url, url: true, if: ->(form) { form.online_meeting? || form.hybrid_meeting? } validates :comments_start_time, date: { before: :comments_end_time, allow_blank: true, if: proc { |obj| obj.comments_end_time.present? } } validates :comments_end_time, date: { after: :comments_start_time, allow_blank: true, if: proc { |obj| obj.comments_start_time.present? } } diff --git a/decidim-meetings/app/forms/decidim/meetings/base_meeting_form.rb b/decidim-meetings/app/forms/decidim/meetings/base_meeting_form.rb index 2517d09cd7797..67a3487d2338f 100644 --- a/decidim-meetings/app/forms/decidim/meetings/base_meeting_form.rb +++ b/decidim-meetings/app/forms/decidim/meetings/base_meeting_form.rb @@ -13,8 +13,6 @@ class BaseMeetingForm < Decidim::Form validates :current_component, presence: true - validates :address, presence: true, if: ->(form) { form.needs_address? } - validates :address, geocoding: true, if: ->(form) { form.has_address? && !form.geocoded? && form.needs_address? } validates :start_time, presence: true, date: { before: :end_time } validates :end_time, presence: true, date: { after: :start_time } diff --git a/decidim-meetings/app/forms/decidim/meetings/meeting_form.rb b/decidim-meetings/app/forms/decidim/meetings/meeting_form.rb index 6a9184d1bc10c..fe280db792b14 100644 --- a/decidim-meetings/app/forms/decidim/meetings/meeting_form.rb +++ b/decidim-meetings/app/forms/decidim/meetings/meeting_form.rb @@ -20,6 +20,8 @@ class MeetingForm < ::Decidim::Meetings::BaseMeetingForm attribute :iframe_embed_type, String, default: "none" attribute :iframe_access_level, String + validates :address, presence: true, if: ->(form) { form.needs_address? } + validates :address, geocoding: true, if: ->(form) { form.has_address? && !form.geocoded? && form.needs_address? } validates :iframe_embed_type, inclusion: { in: Decidim::Meetings::Meeting.participants_iframe_embed_types } validates :title, presence: true validates :description, presence: true diff --git a/decidim-meetings/app/models/decidim/meetings/meeting.rb b/decidim-meetings/app/models/decidim/meetings/meeting.rb index 2cd72fceaab53..8ae29431e19db 100644 --- a/decidim-meetings/app/models/decidim/meetings/meeting.rb +++ b/decidim-meetings/app/models/decidim/meetings/meeting.rb @@ -213,6 +213,10 @@ def has_registration_for?(user) registrations.where(user:).any? end + def pending_location? + !online? && location.except("machine_translations").values.all?(&:blank?) + end + def maps_enabled? component.settings.maps_enabled? end diff --git a/decidim-meetings/config/locales/en.yml b/decidim-meetings/config/locales/en.yml index 7ff7e65688311..b009a272efdd5 100644 --- a/decidim-meetings/config/locales/en.yml +++ b/decidim-meetings/config/locales/en.yml @@ -582,6 +582,8 @@ en: micro_camera_permissions_warning: When you click on the button below, you will be asked for microphone and/or camera permissions, and you will join the videoconference no_slots_available: No slots available organizations: Attending organizations + pending_address: The location will be announced soon. + redirect_notice: This meeting is part of another space, so you have been moved to %{current_space_name}.
If you prefer, you can go back to %{previous_space_name}. registration_code_help_text: Your registration code registration_state: validated: VALIDATED diff --git a/decidim-meetings/spec/commands/admin/update_meeting_spec.rb b/decidim-meetings/spec/commands/admin/update_meeting_spec.rb index b9cb3430a3cbd..d08f494094e29 100644 --- a/decidim-meetings/spec/commands/admin/update_meeting_spec.rb +++ b/decidim-meetings/spec/commands/admin/update_meeting_spec.rb @@ -224,7 +224,8 @@ module Decidim::Meetings event: "decidim.events.meetings.meeting_updated", event_class: UpdateMeetingEvent, resource: meeting, - followers: [user] + followers: [user], + extra: { changed_fields: %w(start_time) } ) subject.call @@ -246,7 +247,8 @@ module Decidim::Meetings event: "decidim.events.meetings.meeting_updated", event_class: UpdateMeetingEvent, resource: meeting, - followers: [user] + followers: [user], + extra: { changed_fields: %w(end_time) } ) subject.call @@ -263,7 +265,8 @@ module Decidim::Meetings event: "decidim.events.meetings.meeting_updated", event_class: UpdateMeetingEvent, resource: meeting, - followers: [user] + followers: [user], + extra: { changed_fields: %w(address) } ) subject.call diff --git a/decidim-meetings/spec/events/decidim/meetings/update_meeting_event_spec.rb b/decidim-meetings/spec/events/decidim/meetings/update_meeting_event_spec.rb index 5c7f3df4502f8..eadc900a893c3 100644 --- a/decidim-meetings/spec/events/decidim/meetings/update_meeting_event_spec.rb +++ b/decidim-meetings/spec/events/decidim/meetings/update_meeting_event_spec.rb @@ -17,6 +17,38 @@ end end + describe "notification_title" do + context "with one changed field" do + let(:extra) { { changed_fields: ["address"] } } + + it "includes the changed field" do + expect(subject.notification_title).to include("the address") + expect(subject.notification_title).to include(resource_title) + expect(subject.notification_title).to include("has been updated with changes to") + end + end + + context "with multiple changed fields" do + let(:extra) { { changed_fields: %w(start_time address location) } } + + it "lists all changed fields with proper grammar" do + expect(subject.notification_title).to include("the start time, the address, and the location") + end + end + + context "with no changed fields" do + let(:extra) { { changed_fields: [] } } + + it "returns a safe fallback title when no fields are changed" do + expect(subject.notification_title).to be_a(String) + expect(subject.notification_title).not_to include("translation missing") + expect(subject.notification_title).not_to include("address") + expect(subject.notification_title).not_to include("location") + expect(subject.notification_title).not_to include("time") + end + end + end + describe "resource_text" do it "returns the meeting description" do expect(subject.resource_text).to eq translated(resource.description) diff --git a/decidim-meetings/spec/forms/admin/meeting_form_spec.rb b/decidim-meetings/spec/forms/admin/meeting_form_spec.rb index ae295eeb0d3cd..5e5439cbdc13d 100644 --- a/decidim-meetings/spec/forms/admin/meeting_form_spec.rb +++ b/decidim-meetings/spec/forms/admin/meeting_form_spec.rb @@ -107,17 +107,31 @@ module Decidim::Meetings it { is_expected.not_to be_valid } end - describe "when location is missing and type of meeting is in_person" do + describe "address and location" do let(:type_of_meeting) { "in_person" } - let(:location) { { en: nil } } - it { is_expected.not_to be_valid } - end + context "when both location and address are blank" do + let(:address) { nil } + let(:location) { { "en" => "" } } + + it { is_expected.to be_valid } + end - describe "when address is missing" do - let(:address) { nil } + context "when both location and address are present" do + it { is_expected.to be_valid } + end - it { is_expected.not_to be_valid } + context "when location is present but address is blank" do + let(:address) { nil } + + it { is_expected.not_to be_valid } + end + + context "when address is present but location is blank" do + let(:location) { { "en" => "" } } + + it { is_expected.not_to be_valid } + end end describe "when start_time is missing" do diff --git a/decidim-meetings/spec/system/admin/admin_manages_meetings_spec.rb b/decidim-meetings/spec/system/admin/admin_manages_meetings_spec.rb index b3191d66b303e..bb25d927038b5 100644 --- a/decidim-meetings/spec/system/admin/admin_manages_meetings_spec.rb +++ b/decidim-meetings/spec/system/admin/admin_manages_meetings_spec.rb @@ -5,17 +5,18 @@ describe "Admin manages meetings", serves_geocoding_autocomplete: true, serves_map: true do let(:manifest_name) { "meetings" } - let!(:meeting) { create(:meeting, :published, scope:, services: [], component: current_component) } + let!(:meeting) { create(:meeting, :published, services: [], component: current_component, start_time: base_date + 1.day, end_time: base_date + 26.hours) } let(:address) { "Some address" } let(:latitude) { 40.1234 } let(:longitude) { 2.1234 } let(:service_titles) { ["This is the first service", "This is the second service"] } - let(:base_date) { Time.new.utc } + let(:base_date) { Time.zone.now.change(usec: 0) } let(:meeting_start_date) { base_date.strftime("%d/%m/%Y") } let(:meeting_start_time) { base_date.utc.strftime("%H:%M") } let(:meeting_end_date) { ((base_date + 2.days) + 1.month).strftime("%d/%m/%Y") } let(:meeting_end_time) { (base_date + 4.hours).strftime("%H:%M") } - let(:attributes) { attributes_for(:meeting, component: current_component) } + let(:attributes) { attributes_for(:meeting, component: current_component, skip_injection: true) } + let!(:follow) { create(:follow, followable: meeting, user:) } include_context "when managing a component as an admin" do let(:participatory_process) { create(:participatory_process, :published, :with_steps, organization:) } @@ -176,7 +177,9 @@ fill_in_geocoding :meeting_address, with: address - find("*[type=submit]").click + perform_enqueued_jobs do + find("*[type=submit]").click + end end expect(page).to have_admin_callout("successfully") @@ -185,8 +188,15 @@ expect(page).to have_content(translated(attributes[:title])) end + email = last_email + sleep 1 + expect(email.subject).to include("updated") + expect(email.body.encoded).to include(%(The "#{decidim_sanitize_translated(attributes[:title])}" meeting has been updated with changes to the address and the location)) + page.visit decidim.notifications_path + expect(page).to have_content("The #{decidim_sanitize_translated(attributes[:title])} meeting has been updated with changes to the address and the location") + visit decidim_admin.root_path - expect(page).to have_content("updated the #{translated(attributes[:title])} meeting on the") + expect(page).to have_content("updated the #{decidim_sanitize_translated(attributes[:title])} meeting on the") end it "sets registration enabled to true when registration type is on this platform" do @@ -330,6 +340,32 @@ visit decidim_admin.root_path expect(page).to have_content("created the #{translated(attributes[:title])} meeting on the") end + + context "when the venue has not been decided yet" do + it "creates a new meeting without an address" do + click_on "New meeting" + + fill_in_i18n(:meeting_title, "#meeting-title-tabs", **attributes[:title].except("machine_translations")) + fill_in_i18n_editor(:meeting_description, "#meeting-description-tabs", **attributes[:description].except("machine_translations")) + select "In person", from: :meeting_type_of_meeting + select "Registration disabled", from: :meeting_registration_type + fill_in_datepicker :meeting_start_time_date, with: meeting_start_date + fill_in_timepicker :meeting_start_time_time, with: meeting_start_time + fill_in_datepicker :meeting_end_time_date, with: meeting_end_date + fill_in_timepicker :meeting_end_time_time, with: meeting_end_time + + within ".new_meeting" do + find("*[type=submit]").click + end + + expect(page).to have_admin_callout("successfully") + + new_meeting = Decidim::Meetings::Meeting.last + puts "Meeting location: #{new_meeting.location}" + expect(new_meeting.location.values).to all(be_blank) + expect(new_meeting.address).to be_empty + end + end context "when using the front-end geocoder", :serves_geocoding_autocomplete do it_behaves_like( diff --git a/decidim-meetings/spec/system/meeting_spec.rb b/decidim-meetings/spec/system/meeting_spec.rb index 02775a50c7486..96819f18ff57e 100644 --- a/decidim-meetings/spec/system/meeting_spec.rb +++ b/decidim-meetings/spec/system/meeting_spec.rb @@ -78,6 +78,17 @@ def visit_meeting end end + context "and meeting has no address" do + let(:meeting) { create(:meeting, :published, :with_services, component:, address: "", location: { "en" => "" }) } + + it "hides the map and displays pending address text" do + visit_meeting + + expect(page).to have_no_css("div.meeting__calendar-container .static-map") + expect(page).to have_content(I18n.t("show.pending_address", scope: "decidim.meetings.meetings")) + end + end + context "and meeting is hybrid" do let(:meeting) { create(:meeting, :published, :with_services, :hybrid, component:) } @@ -134,6 +145,28 @@ def visit_meeting end end + context "when the meeting has no address and location" do + let(:meeting) { create(:meeting, :published, component:, address: "", location: { "en" => "" }) } + + it "displays the pending address text" do + visit_meeting + + expect(page).to have_content(I18n.t("show.pending_address", scope: "decidim.meetings.meetings")) + end + end + + context "when meeting has an address and location" do + let(:meeting) { create(:meeting, :published, component:, address: "123 Main St", location: { "en" => "Central Park" }) } + + it "displays the location and address" do + visit_meeting + + expect(page).to have_content("123 Main St") + expect(page).to have_content("Central Park") + expect(page).to have_no_content(I18n.t("show.pending_address", scope: "decidim.meetings.meetings")) + end + end + context "when user is logged and session is about to timeout" do before do allow(Decidim.config).to receive(:expire_session_after).and_return(2.minutes) From 5f58be7a3c9d33ced22d0d0cc3d45defb5efa9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Verg=C3=A9s?= Date: Thu, 31 Jul 2025 10:25:22 +0200 Subject: [PATCH 08/61] linting --- .../decidim/meetings/update_meeting_event.rb | 4 ++-- .../admin/copy_meeting_fields_spec.rb | 4 ++-- .../spec/commands/join_waitlist_spec.rb | 14 +++++------ .../meetings/registrations_controller_spec.rb | 24 +++++++++---------- .../spec/forms/admin/meeting_form_spec.rb | 3 ++- .../admin/admin_manages_meetings_spec.rb | 2 +- .../spec/system/meeting_waiting_list_spec.rb | 6 ++--- 7 files changed, 29 insertions(+), 28 deletions(-) diff --git a/decidim-meetings/app/events/decidim/meetings/update_meeting_event.rb b/decidim-meetings/app/events/decidim/meetings/update_meeting_event.rb index 3b803c3b19e5a..34265613a339a 100644 --- a/decidim-meetings/app/events/decidim/meetings/update_meeting_event.rb +++ b/decidim-meetings/app/events/decidim/meetings/update_meeting_event.rb @@ -11,9 +11,9 @@ def notification_title I18n.t( "notification_title", scope: i18n_scope, - changed_fields: changed_fields, + changed_fields:, resource_title: translated_attribute(resource.title), - resource_path: resource_path + resource_path: ).html_safe end diff --git a/decidim-meetings/spec/commands/admin/copy_meeting_fields_spec.rb b/decidim-meetings/spec/commands/admin/copy_meeting_fields_spec.rb index afdf5ddcea8ba..3d8394acfd6b6 100644 --- a/decidim-meetings/spec/commands/admin/copy_meeting_fields_spec.rb +++ b/decidim-meetings/spec/commands/admin/copy_meeting_fields_spec.rb @@ -9,9 +9,9 @@ module Admin subject { described_class.new(form, meeting) } let(:organization) { create(:organization) } - let(:participatory_process) { create(:participatory_process, organization: organization) } + let(:participatory_process) { create(:participatory_process, organization:) } let(:current_component) { create(:component, manifest_name: "meetings", participatory_space: participatory_process) } - let(:user) { create(:user, :admin, :confirmed, organization: organization) } + let(:user) { create(:user, :admin, :confirmed, organization:) } let(:meeting) { create(:meeting, :published, component: current_component, **meeting_attributes) } let(:meeting_attributes) do diff --git a/decidim-meetings/spec/commands/join_waitlist_spec.rb b/decidim-meetings/spec/commands/join_waitlist_spec.rb index fd56f92bbe837..f84f6868b90b1 100644 --- a/decidim-meetings/spec/commands/join_waitlist_spec.rb +++ b/decidim-meetings/spec/commands/join_waitlist_spec.rb @@ -7,17 +7,17 @@ module Decidim::Meetings subject { described_class.new(meeting, form) } let(:organization) { create(:organization) } - let(:participatory_process) { create(:participatory_process, organization: organization) } + let(:participatory_process) { create(:participatory_process, organization:) } let(:component) { create(:component, manifest_name: :meetings, participatory_space: participatory_process) } let(:available_slots) { 2 } let(:meeting) do create(:meeting, - component: component, + component:, registrations_enabled: true, - available_slots: available_slots) + available_slots:) end - let(:user) { create(:user, :confirmed, organization: organization, notifications_sending_frequency: "none") } + let(:user) { create(:user, :confirmed, organization:, notifications_sending_frequency: "none") } let(:form) do Decidim::Meetings::JoinMeetingForm.new.with_context( current_user: user @@ -35,7 +35,7 @@ module Decidim::Meetings context "when all conditions are met" do before do - create_list(:registration, available_slots, meeting: meeting, status: :registered) + create_list(:registration, available_slots, meeting:, status: :registered) end it "broadcasts ok" do @@ -58,7 +58,7 @@ module Decidim::Meetings context "when the user is already registered" do before do - create(:registration, meeting: meeting, user: user, status: :registered) + create(:registration, meeting:, user:, status: :registered) end it "broadcasts invalid" do @@ -74,7 +74,7 @@ module Decidim::Meetings context "when the form is invalid" do before do - create_list(:registration, available_slots, meeting: meeting, status: :registered) + create_list(:registration, available_slots, meeting:, status: :registered) allow(form).to receive(:valid?).and_return(false) end diff --git a/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb b/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb index 921e1c540155c..e8b3c8c965dc4 100644 --- a/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb +++ b/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb @@ -27,7 +27,7 @@ module Decidim::Meetings context "with available slots" do it "creates registration and redirects" do expect do - post :create, params: params + post :create, params: end.to change(Registration, :count).by(1) expect(flash[:notice]).to eq(I18n.t("registrations.create.success", scope: "decidim.meetings")) @@ -36,10 +36,10 @@ module Decidim::Meetings end context "when no available slots" do - let!(:registrations) { create_list(:registration, 10, meeting: meeting) } + let!(:registrations) { create_list(:registration, 10, meeting:) } it "shows error message" do - post :create, params: params + post(:create, params:) expect(flash[:alert]).to eq(I18n.t("registrations.create.invalid", scope: "decidim.meetings")) expect(response).to redirect_to(meeting_path(meeting)) @@ -49,7 +49,7 @@ module Decidim::Meetings context "when user not authenticated" do it "redirects to login" do - post :create, params: params + post(:create, params:) expect(response).to redirect_to("/users/sign_in") end end @@ -76,7 +76,7 @@ module Decidim::Meetings meeting.update!( registrations_enabled: true, registration_form_enabled: true, - questionnaire: questionnaire + questionnaire: ) end @@ -84,7 +84,7 @@ module Decidim::Meetings context "when joining directly" do it "answers questionnaire and redirects" do expect do - post :respond, params: params + post :respond, params: end.to change { meeting.registrations.count }.by(1) expect(flash[:notice]).to eq(I18n.t("registrations.create.success", scope: "decidim.meetings")) @@ -94,11 +94,11 @@ module Decidim::Meetings context "when joining waitlist" do let(:meeting) { create(:meeting, component:, available_slots: 10) } - let!(:registrations) { create_list(:registration, 10, meeting: meeting) } + let!(:registrations) { create_list(:registration, 10, meeting:) } it "adds user to waitlist and redirects" do expect do - post :respond, params: params + post :respond, params: end.to change { meeting.registrations.where(status: :waiting_list).count }.by(1) expect(flash[:notice]).to eq(I18n.t("registrations.waitlist.success", scope: "decidim.meetings")) @@ -116,7 +116,7 @@ module Decidim::Meetings end it "shows error message" do - post :respond, params: params + post(:respond, params:) expect(flash[:alert]).to eq(I18n.t("response.invalid", scope: "decidim.forms.questionnaires")) expect(response).to render_template("decidim/forms/questionnaires/show") @@ -126,7 +126,7 @@ module Decidim::Meetings describe "POST join_waitlist" do let(:meeting) { create(:meeting, component:, available_slots: 10) } - let!(:registrations) { create_list(:registration, 10, meeting: meeting) } + let!(:registrations) { create_list(:registration, 10, meeting:) } let(:params) { { meeting_id: meeting.id } } before { sign_in user } @@ -134,7 +134,7 @@ module Decidim::Meetings context "when meeting has no available slots" do it "adds user to waitlist" do expect do - post :join_waitlist, params: params + post :join_waitlist, params: end.to change(Registration.on_waiting_list, :count).by(1) expect(flash[:notice]).to eq(I18n.t("registrations.waitlist.success", scope: "decidim.meetings")) @@ -151,7 +151,7 @@ module Decidim::Meetings it "destroys registration" do expect do - delete :destroy, params: params + delete :destroy, params: end.to change(Registration, :count).by(-1) expect(flash[:notice]).to match(/successfully/) diff --git a/decidim-meetings/spec/forms/admin/meeting_form_spec.rb b/decidim-meetings/spec/forms/admin/meeting_form_spec.rb index 5e5439cbdc13d..8495398ee106f 100644 --- a/decidim-meetings/spec/forms/admin/meeting_form_spec.rb +++ b/decidim-meetings/spec/forms/admin/meeting_form_spec.rb @@ -189,7 +189,7 @@ module Decidim::Meetings expect(described_class.from_model(meeting).decidim_category_id).to eq(category_id) end - + describe "when reminder_enabled is false" do let(:reminder_enabled) { false } @@ -221,6 +221,7 @@ module Decidim::Meetings it { is_expected.to be_valid } end end + describe "services_to_persist" do subject { form.services_to_persist } diff --git a/decidim-meetings/spec/system/admin/admin_manages_meetings_spec.rb b/decidim-meetings/spec/system/admin/admin_manages_meetings_spec.rb index bb25d927038b5..e2bcfe0ffbc78 100644 --- a/decidim-meetings/spec/system/admin/admin_manages_meetings_spec.rb +++ b/decidim-meetings/spec/system/admin/admin_manages_meetings_spec.rb @@ -340,7 +340,7 @@ visit decidim_admin.root_path expect(page).to have_content("created the #{translated(attributes[:title])} meeting on the") end - + context "when the venue has not been decided yet" do it "creates a new meeting without an address" do click_on "New meeting" diff --git a/decidim-meetings/spec/system/meeting_waiting_list_spec.rb b/decidim-meetings/spec/system/meeting_waiting_list_spec.rb index f05e49b3c1e4a..49c1dd575d403 100644 --- a/decidim-meetings/spec/system/meeting_waiting_list_spec.rb +++ b/decidim-meetings/spec/system/meeting_waiting_list_spec.rb @@ -58,7 +58,7 @@ def leave_meeting(user) context "when the meeting has no available slots" do before do - create_list(:registration, available_slots, meeting: meeting) + create_list(:registration, available_slots, meeting:) end context "when the user is not logged in" do @@ -111,13 +111,13 @@ def leave_meeting(user) context "when the meeting is full and a user cancels their registration" do context "and there are users on the waiting list" do - let!(:registrations) { create_list(:registration, available_slots, meeting: meeting) } + let!(:registrations) { create_list(:registration, available_slots, meeting:) } let(:users_on_waitlist) { create_list(:user, 5, :confirmed, organization:) } let(:first_waitlist_user) { users_on_waitlist.first } let!(:waitlist_entries) do users_on_waitlist.map.with_index do |user, index| - create(:registration, meeting: meeting, user: user, status: "waiting_list", created_at: Time.current - index.minutes) + create(:registration, meeting:, user:, status: "waiting_list", created_at: Time.current - index.minutes) end end From 3781a7ea6c33fb2994b9329db2ad74af7296603c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Verg=C3=A9s?= Date: Thu, 31 Jul 2025 11:01:19 +0200 Subject: [PATCH 09/61] fix conflic resolution --- .../meetings/meetings/_meeting_aside.html.erb | 13 ++++++++----- .../spec/commands/admin/copy_meeting_spec.rb | 2 ++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb b/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb index afb0f529cde92..5b808916ab642 100644 --- a/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb +++ b/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb @@ -71,12 +71,15 @@ <% if registration.present? %> <%= waitlist_status_block(registration) %> - <% if registration.meeting.component.settings.registration_code_enabled && registration.registered? %> - <%= render partial: "registration_code_modal" %> + <% if registration.meeting.component.settings.registration_code_enabled %> <%= render layout: "decidim/meetings/layouts/aside_block", locals: { emoji: "coupon-line" } do %> - - <%= registration_code_help_text %> - +

<%= registration_code_help_text %>

+
<%= registration.code %>
+ <% if registration.validated? %> +
<%= t("validated", scope: "decidim.meetings.meetings.show.registration_state") %>
+ <% else %> +
<%= t("validation_pending", scope: "decidim.meetings.meetings.show.registration_state") %>
+ <% end %> <% end %> <% end %> <% end %> diff --git a/decidim-meetings/spec/commands/admin/copy_meeting_spec.rb b/decidim-meetings/spec/commands/admin/copy_meeting_spec.rb index 7b609232c7be5..67a97543d9b82 100644 --- a/decidim-meetings/spec/commands/admin/copy_meeting_spec.rb +++ b/decidim-meetings/spec/commands/admin/copy_meeting_spec.rb @@ -35,6 +35,8 @@ module Decidim::Meetings address:, latitude:, longitude:, + scope: meeting.scope, + category: meeting.category, reminder_enabled: meeting.reminder_enabled, send_reminders_before_hours: meeting.send_reminders_before_hours, reminder_message_custom_content: meeting.reminder_message_custom_content, From 23c2e3690bfe1b1515b57110447d530703feed37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Verg=C3=A9s?= Date: Thu, 31 Jul 2025 13:00:05 +0200 Subject: [PATCH 10/61] fix conflic resolution --- .../spec/commands/admin/copy_meeting_fields_spec.rb | 2 ++ decidim-meetings/spec/commands/admin/copy_meeting_spec.rb | 2 -- decidim-meetings/spec/system/meeting_waiting_list_spec.rb | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/decidim-meetings/spec/commands/admin/copy_meeting_fields_spec.rb b/decidim-meetings/spec/commands/admin/copy_meeting_fields_spec.rb index 3d8394acfd6b6..f8fc9fbba5e4f 100644 --- a/decidim-meetings/spec/commands/admin/copy_meeting_fields_spec.rb +++ b/decidim-meetings/spec/commands/admin/copy_meeting_fields_spec.rb @@ -39,6 +39,8 @@ module Admin address: "Test Address", latitude: 40.1234, longitude: 2.1234, + scope: meeting.scope, + category: meeting.category, location: { "en" => "Test Location" }, location_hints: { "en" => "Test hints" }, private_meeting: false, diff --git a/decidim-meetings/spec/commands/admin/copy_meeting_spec.rb b/decidim-meetings/spec/commands/admin/copy_meeting_spec.rb index 67a97543d9b82..90b068eab7357 100644 --- a/decidim-meetings/spec/commands/admin/copy_meeting_spec.rb +++ b/decidim-meetings/spec/commands/admin/copy_meeting_spec.rb @@ -40,8 +40,6 @@ module Decidim::Meetings reminder_enabled: meeting.reminder_enabled, send_reminders_before_hours: meeting.send_reminders_before_hours, reminder_message_custom_content: meeting.reminder_message_custom_content, - scope: meeting.scope, - category: meeting.category, services_to_persist:, current_user:, questionnaire: Decidim::Forms::Questionnaire.new, diff --git a/decidim-meetings/spec/system/meeting_waiting_list_spec.rb b/decidim-meetings/spec/system/meeting_waiting_list_spec.rb index 49c1dd575d403..ab7f67e9f6ef2 100644 --- a/decidim-meetings/spec/system/meeting_waiting_list_spec.rb +++ b/decidim-meetings/spec/system/meeting_waiting_list_spec.rb @@ -133,7 +133,6 @@ def leave_meeting(user) it "displays the registration confirmation" do visit_meeting email = last_email - expect(page).to have_content("Your registration and QR code") expect(email.subject).to eq("Your meeting's registration has been confirmed") expect(email.to).to eq([earliest_waitlist_user.email]) end From 38cacf0e0e535e5ec1e153f9e2727d0a28304167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Verg=C3=A9s?= Date: Thu, 31 Jul 2025 15:04:52 +0200 Subject: [PATCH 11/61] fix controller spec --- .../decidim/meetings/registrations_controller_spec.rb | 6 +++--- decidim-meetings/spec/system/meeting_waiting_list_spec.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb b/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb index e8b3c8c965dc4..6e1d0fef20b1c 100644 --- a/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb +++ b/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb @@ -84,7 +84,7 @@ module Decidim::Meetings context "when joining directly" do it "answers questionnaire and redirects" do expect do - post :respond, params: + post :answer, params: end.to change { meeting.registrations.count }.by(1) expect(flash[:notice]).to eq(I18n.t("registrations.create.success", scope: "decidim.meetings")) @@ -98,7 +98,7 @@ module Decidim::Meetings it "adds user to waitlist and redirects" do expect do - post :respond, params: + post :answer, params: end.to change { meeting.registrations.where(status: :waiting_list).count }.by(1) expect(flash[:notice]).to eq(I18n.t("registrations.waitlist.success", scope: "decidim.meetings")) @@ -116,7 +116,7 @@ module Decidim::Meetings end it "shows error message" do - post(:respond, params:) + post(:answer, params:) expect(flash[:alert]).to eq(I18n.t("response.invalid", scope: "decidim.forms.questionnaires")) expect(response).to render_template("decidim/forms/questionnaires/show") diff --git a/decidim-meetings/spec/system/meeting_waiting_list_spec.rb b/decidim-meetings/spec/system/meeting_waiting_list_spec.rb index ab7f67e9f6ef2..6ed8e7ff3de9d 100644 --- a/decidim-meetings/spec/system/meeting_waiting_list_spec.rb +++ b/decidim-meetings/spec/system/meeting_waiting_list_spec.rb @@ -93,7 +93,7 @@ def leave_meeting(user) expect(page).to have_i18n_content(questionnaire.title) expect(page).to have_i18n_content(questionnaire.description, strip_tags: true) expect(page).to have_i18n_content(question.body) - expect(page).to have_css(".form.response-questionnaire") + expect(page).to have_css(".form.answer-questionnaire") end it "can join the waitlist" do From 126c51b8fda57d87200edabf5b4060ed78af3c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Verg=C3=A9s?= Date: Thu, 31 Jul 2025 16:44:38 +0200 Subject: [PATCH 12/61] add translations --- decidim-meetings/config/locales/ca-IT.yml | 7 ++++--- decidim-meetings/config/locales/ca.yml | 7 ++++--- decidim-meetings/config/locales/de.yml | 7 ++++--- decidim-meetings/config/locales/es.yml | 9 +++++---- decidim-meetings/config/locales/eu.yml | 7 ++++--- decidim-meetings/config/locales/fi.yml | 7 ++++--- decidim-meetings/config/locales/fr.yml | 9 +++++---- decidim-meetings/config/locales/ja.yml | 9 +++++---- .../decidim/meetings/registrations_controller_spec.rb | 2 +- 9 files changed, 36 insertions(+), 28 deletions(-) diff --git a/decidim-meetings/config/locales/ca-IT.yml b/decidim-meetings/config/locales/ca-IT.yml index dd8c12d9f58d0..a996bec2074f3 100644 --- a/decidim-meetings/config/locales/ca-IT.yml +++ b/decidim-meetings/config/locales/ca-IT.yml @@ -212,10 +212,11 @@ ca-IT: email_subject: La trobada de "%{resource_title}" ha obert les inscripcions. notification_title: La trobada %{resource_title} ha obert les inscripcions. upcoming_meeting: - email_intro: En menys de 48 hores començarà la trobada "%{resource_title}". + default_body: La trobada "%{meeting_title}" començarà en menys de {{before_hours}}h. + email_intro: La trobada "%{meeting_title}" començarà en menys de %{reminders_before_hours}h. email_outro: Has rebut aquesta notificació perquè estàs seguint la trobada "%{resource_title}". Pots deixar de seguir-la des de l'enllaç anterior. - email_subject: En menys de 48 hores s'iniciarà la trobada "%{resource_title}". - notification_title: La trobada %{resource_title} començarà en menys de 48 hores. + email_subject: La trobada "%{resource_title}" començarà en menys de %{reminders_before_hours}h. + notification_title: La trobada %{resource_title} començarà en menys de %{reminders_before_hours}h. forms: meetings: attendees_count_help_text: No t'oblidis d'afegir el número total d'assistents a la trobada, sigui presencial, en línia o híbrida. diff --git a/decidim-meetings/config/locales/ca.yml b/decidim-meetings/config/locales/ca.yml index acac4ec189789..cbd653644c411 100644 --- a/decidim-meetings/config/locales/ca.yml +++ b/decidim-meetings/config/locales/ca.yml @@ -212,10 +212,11 @@ ca: email_subject: La trobada de "%{resource_title}" ha obert les inscripcions. notification_title: La trobada %{resource_title} ha obert les inscripcions. upcoming_meeting: - email_intro: En menys de 48 hores començarà la trobada "%{resource_title}". + default_body: La trobada "%{meeting_title}" començarà en menys de {{before_hours}}h. + email_intro: La trobada "%{meeting_title}" començarà en menys de %{reminders_before_hours}h. email_outro: Has rebut aquesta notificació perquè estàs seguint la trobada "%{resource_title}". Pots deixar de seguir-la des de l'enllaç anterior. - email_subject: En menys de 48 hores s'iniciarà la trobada "%{resource_title}". - notification_title: La trobada %{resource_title} començarà en menys de 48 hores. + email_subject: La trobada "%{resource_title}" començarà en menys de %{reminders_before_hours}h. + notification_title: La trobada %{resource_title} començarà en menys de %{reminders_before_hours}h. forms: meetings: attendees_count_help_text: No t'oblidis d'afegir el número total d'assistents a la trobada, sigui presencial, en línia o híbrida. diff --git a/decidim-meetings/config/locales/de.yml b/decidim-meetings/config/locales/de.yml index 4d445e98f55d4..f328efa0ddf7b 100644 --- a/decidim-meetings/config/locales/de.yml +++ b/decidim-meetings/config/locales/de.yml @@ -212,10 +212,11 @@ de: email_subject: Die Veranstaltung "%{resource_title}" hat Registrierungen aktiviert. notification_title: Die Veranstaltung %{resource_title} hat Registrierungen aktiviert. upcoming_meeting: - email_intro: Die Veranstaltung "%{resource_title}" beginnt in weniger als 48 Stunden. + default_body: Die Sitzung "{{meeting_title}}" beginnt in weniger als {{before_hours}} Stunden. + email_intro: Die Veranstaltung "%{resource_title}" beginnt in weniger als %{reminders_before_hours} Stunden. email_outro: Sie haben diese Benachrichtigung erhalten, weil Sie der Veranstaltung "%{resource_title}" folgen. Falls Sie keine solchen Benachrichtigungen mehr erhalten möchten, besuchen Sie den obigen Link. - email_subject: Die Sitzung "%{resource_title}" beginnt in weniger als 48 Stunden. - notification_title: Die Veranstaltung %{resource_title} beginnt in weniger als 48 Stunden. + email_subject: Die Sitzung "%{resource_title}" beginnt in weniger als %{reminders_before_hours} Stunden. + notification_title: Die Veranstaltung %{resource_title} beginnt in weniger als %{reminders_before_hours} Stunden. forms: meetings: attendees_count_help_text: Vergessen Sie nicht, die Gesamtzahl der Teilnehmenden an Ihrer Veranstaltung anzugeben, egal ob sie persönlich, online oder hybrid stattgefunden hat. diff --git a/decidim-meetings/config/locales/es.yml b/decidim-meetings/config/locales/es.yml index 2e8cf05d8fa27..23dd807579ebe 100644 --- a/decidim-meetings/config/locales/es.yml +++ b/decidim-meetings/config/locales/es.yml @@ -212,10 +212,11 @@ es: email_subject: El encuentro "%{resource_title}" ha abierto las inscripciones. notification_title: El encuentro %{resource_title} ha abierto las inscripciones. upcoming_meeting: - email_intro: En menos de 48 horas empezará el encuentro "%{resource_title}". - email_outro: Has recibido esta notificación porque estás siguiendo el encuentro "%{resource_title}". Puedes dejar de seguirlo desde el enlace anterior. - email_subject: En menos de 48 horas empezará el encuentro "%{resource_title}". - notification_title: El encuentro %{resource_title} empezará en menos de 48 h. + default_body: 'El encuentro: "{{meeting_title}}" empezará en menos de {{before_hours}}h.' + email_intro: El encuentro "%{resource_title}" empezará en menos de %{reminders_before_hours}h. + email_outro: "Has recibido esta notificación porque sigues el encuentro \"%{resource_title}\". Puedes dejar de seguirla \ndesde el enlace anterior." + email_subject: El encuentro "%{resource_title}" empezará en menos de %{reminders_before_hours}h. + notification_title: El encuentro %{resource_title} comenzará en menos de %{reminders_before_hours}h. forms: meetings: attendees_count_help_text: No te olvides de añadir el número total de asistentes a la reunión, ya sea en persona, en línea o híbrido. diff --git a/decidim-meetings/config/locales/eu.yml b/decidim-meetings/config/locales/eu.yml index 541ba7d63e47c..d252fb7beb352 100644 --- a/decidim-meetings/config/locales/eu.yml +++ b/decidim-meetings/config/locales/eu.yml @@ -212,10 +212,11 @@ eu: email_subject: '"%{resource_title}" topaketan izena emateko epea ireki da.' notification_title: %{resource_title} topaketan izena emateko epea ireki da. upcoming_meeting: - email_intro: '"%{resource_title}" topaketa 48 ordu baino lehen hasiko da.' + default_body: '"{{meeting_title}}" topaketa {{before_hours}} ordu baino lehen hasiko da.' + email_intro: '"%{resource_title}" topaketa %{reminders_before_hours} ordu baino lehen hasiko da.' email_outro: Jakinarazpen hau jaso duzu "%{resource_title}" topaketa jarraitzen ari zarelako. Aurreko estekan sartu jarraitzeari uzteko. - email_subject: '"%{resource_title}" topaketa 48 ordu baino lehen hasiko da.' - notification_title: %{resource_title} topaketa 48 ordu baino lehen hasiko da. + email_subject: '"%{resource_title}" topaketa %{reminders_before_hours} ordu baino lehen hasiko da.' + notification_title: %{resource_title} topaketa %{reminders_before_hours} ordu baino lehen hasiko da. forms: meetings: attendees_count_help_text: Ez ahaztu tokapetara bertaratutakoen kopuru osoa sartzea, aurrez aurre, online edo hibridoan. diff --git a/decidim-meetings/config/locales/fi.yml b/decidim-meetings/config/locales/fi.yml index d977552d29fa8..55fb852f09a02 100644 --- a/decidim-meetings/config/locales/fi.yml +++ b/decidim-meetings/config/locales/fi.yml @@ -212,10 +212,11 @@ fi: email_subject: Ilmoittautumiset on avattu tapaamiseen "%{resource_title}". notification_title: Ilmoittautumiset on avattu tapaamiseen %{resource_title}. upcoming_meeting: - email_intro: Tapaamisen "%{resource_title}" alkuun on alle 48 tuntia. + default_body: Tapaamisen "{{meeting_title}}" alkuun on alle {{before_hours}} tuntia. + email_intro: Tapaamisen "%{resource_title}" alkuun on alle %{reminders_before_hours} tuntia. email_outro: Tämä ilmoitus on lähetetty sinulle, koska seuraat tapaamista "%{resource_title}". Voit lopettaa seuraamisen edellä esitetyn linkin kautta. - email_subject: Tapaamisen "%{resource_title}" alkuun on alle 48 tuntia. - notification_title: Tapaaminen %{resource_title} alkaa alle 48 tuntin kuluttua. + email_subject: Tapaamisen "%{resource_title}" alkuun on alle %{reminders_before_hours} tuntia. + notification_title: Tapaamisen %{resource_title} alkuun on alle %{reminders_before_hours} tuntia. forms: meetings: attendees_count_help_text: Älä unohda merkitä tapaamisesi osallistujien kokonaismäärää, olipa kyse fyysisestä, verkko- tai hybriditapaamisesta. diff --git a/decidim-meetings/config/locales/fr.yml b/decidim-meetings/config/locales/fr.yml index 895fd80a71f96..a891cf75da708 100644 --- a/decidim-meetings/config/locales/fr.yml +++ b/decidim-meetings/config/locales/fr.yml @@ -212,10 +212,11 @@ fr: email_subject: Les inscriptions pour la rencontre "%{resource_title}" sont ouvertes. notification_title: Les inscriptions pour la rencontre %{resource_title} sont ouvertes. upcoming_meeting: - email_intro: La rencontre "%{resource_title}" commencera dans moins de 48h. - email_outro: Vous avez reçu cette notification parce que vous suivez la réunion "%{resource_title}". Si vous souhaitez vous désabonner des notifications, connectez-vous à la plateforme, puis rendez-vous dans l'onglet “Mon compte” > “Paramètres des notifications”. - email_subject: La rencontre "%{resource_title}" commencera dans moins de 48h. - notification_title: La rencontre %{resource_title} débutera dans moins de 48h. + default_body: La réunion "{{meeting_title}}" débutera dans moins de {{before_hours}}h. + email_intro: La réunion "%{resource_title}" débutera dans moins de %{reminders_before_hours}h. + email_outro: Vous avez reçu cette notification parce que vous suivez la réunion %{resource_title} . Vous pouvez cesser de la suivre à partir du lien précédent. + email_subject: La réunion "%{resource_title}" débutera dans moins de %{reminders_before_hours}h. + notification_title: La réunion %{resource_title} commencera dans moins de %{reminders_before_hours}h. forms: meetings: attendees_count_help_text: N'oubliez pas d'inclure le nombre total de participants à votre réunion, que ce soit en personne, en ligne ou hybride. diff --git a/decidim-meetings/config/locales/ja.yml b/decidim-meetings/config/locales/ja.yml index 4eee48c4eaf04..49cf8b7b27e1b 100644 --- a/decidim-meetings/config/locales/ja.yml +++ b/decidim-meetings/config/locales/ja.yml @@ -209,10 +209,11 @@ ja: email_subject: '"%{resource_title}" ミーティングは登録を有効にしました.' notification_title: %{resource_title} のミーティングは登録を有効にしました。 upcoming_meeting: - email_intro: '"%{resource_title}" のミーティングは48時間以内に開始されます.' - email_outro: '「%{resource_title}」ミーティングをフォローしているため、この通知を受け取りました.前のリンクからフォローを解除することができます.' - email_subject: '"%{resource_title}" のミーティングは48時間以内に開始されます.' - notification_title: %{resource_title} のミーティングは48時間以内に開始されます. + default_body: '"{{meeting_title}}"ミーティングは、あと{{before_hours}}時間以内に開始されます。' + email_intro: '"%{resource_title}" ミーティングは、あと%{reminders_before_hours}時間以内に開始されます。' + email_outro: '"%{resource_title}"ミーティングをフォローしているため、この通知を受け取りました。フォローを解除するには、前のリンクから行ってください。' + email_subject: '"%{resource_title}"ミーティングは、%{reminders_before_hours}時間以内に開始されます。' + notification_title: %{resource_title} ミーティングは、%{reminders_before_hours}時間以内に開始されます。 forms: meetings: attendees_count_help_text: 対面、オンライン、ハイブリッドのどの形態でも、ミーティングの参加者の総数を含めることを忘れないでください。 diff --git a/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb b/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb index 6e1d0fef20b1c..1fa9cb6518acf 100644 --- a/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb +++ b/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb @@ -118,7 +118,7 @@ module Decidim::Meetings it "shows error message" do post(:answer, params:) - expect(flash[:alert]).to eq(I18n.t("response.invalid", scope: "decidim.forms.questionnaires")) + expect(flash[:alert]).to eq(I18n.t("answer.invalid", scope: "decidim.forms.questionnaires")) expect(response).to render_template("decidim/forms/questionnaires/show") end end From d6d17c566caa98f91112ff786e7af67cb0c4faa3 Mon Sep 17 00:00:00 2001 From: decidim-bot Date: Mon, 4 Aug 2025 14:46:44 +0200 Subject: [PATCH 13/61] New Crowdin updates (#14965) --- .../config/locales/sr-CS.yml | 2 + decidim-admin/config/locales/bg.yml | 2 +- decidim-admin/config/locales/cs.yml | 2 +- decidim-admin/config/locales/de.yml | 2 +- decidim-admin/config/locales/eu.yml | 2 +- decidim-admin/config/locales/fr-CA.yml | 2 +- decidim-admin/config/locales/fr.yml | 2 +- decidim-admin/config/locales/hu.yml | 2 +- decidim-admin/config/locales/it.yml | 36 +++++ decidim-admin/config/locales/ko.yml | 2 - decidim-admin/config/locales/lb.yml | 2 +- decidim-admin/config/locales/pt-BR.yml | 2 +- decidim-admin/config/locales/ro-RO.yml | 2 +- decidim-admin/config/locales/sq-AL.yml | 1 - decidim-assemblies/config/locales/it.yml | 7 + decidim-conferences/config/locales/ar.yml | 6 - decidim-conferences/config/locales/bg.yml | 6 - decidim-conferences/config/locales/ca-IT.yml | 2 +- decidim-conferences/config/locales/ca.yml | 2 +- decidim-conferences/config/locales/cs.yml | 6 - decidim-conferences/config/locales/de.yml | 8 +- decidim-conferences/config/locales/el.yml | 6 - decidim-conferences/config/locales/es-MX.yml | 6 +- decidim-conferences/config/locales/es-PY.yml | 8 +- decidim-conferences/config/locales/es.yml | 4 +- decidim-conferences/config/locales/eu.yml | 2 +- .../config/locales/fi-plain.yml | 4 +- decidim-conferences/config/locales/fi.yml | 4 +- decidim-conferences/config/locales/fr-CA.yml | 2 +- decidim-conferences/config/locales/fr.yml | 2 +- decidim-conferences/config/locales/gl.yml | 6 - decidim-conferences/config/locales/hu.yml | 6 - decidim-conferences/config/locales/id-ID.yml | 6 - decidim-conferences/config/locales/it.yml | 6 - decidim-conferences/config/locales/ja.yml | 2 +- decidim-conferences/config/locales/lb.yml | 4 - decidim-conferences/config/locales/lt.yml | 6 - decidim-conferences/config/locales/lv.yml | 6 - decidim-conferences/config/locales/nl.yml | 6 - decidim-conferences/config/locales/no.yml | 6 - decidim-conferences/config/locales/pl.yml | 6 - decidim-conferences/config/locales/pt-BR.yml | 8 +- decidim-conferences/config/locales/pt.yml | 6 - decidim-conferences/config/locales/ro-RO.yml | 6 - decidim-conferences/config/locales/sk.yml | 6 - decidim-conferences/config/locales/sv.yml | 6 - decidim-conferences/config/locales/tr-TR.yml | 6 - decidim-conferences/config/locales/zh-CN.yml | 6 - decidim-conferences/config/locales/zh-TW.yml | 6 - decidim-core/config/locales/ca-IT.yml | 2 + decidim-core/config/locales/ca.yml | 2 + decidim-core/config/locales/cs.yml | 3 +- decidim-core/config/locales/es-MX.yml | 2 + decidim-core/config/locales/es-PY.yml | 2 + decidim-core/config/locales/es.yml | 2 + decidim-core/config/locales/eu.yml | 3 + decidim-core/config/locales/fi-plain.yml | 2 + decidim-core/config/locales/fi.yml | 3 + decidim-core/config/locales/fr-CA.yml | 2 + decidim-core/config/locales/fr.yml | 2 + decidim-core/config/locales/it.yml | 124 ++++++++++++++++++ decidim-core/config/locales/ja.yml | 3 + decidim-core/config/locales/sk.yml | 2 + decidim-core/config/locales/sv.yml | 3 + 64 files changed, 232 insertions(+), 171 deletions(-) diff --git a/decidim-accountability/config/locales/sr-CS.yml b/decidim-accountability/config/locales/sr-CS.yml index 93a3a60192e52..2f5e598d32e5e 100644 --- a/decidim-accountability/config/locales/sr-CS.yml +++ b/decidim-accountability/config/locales/sr-CS.yml @@ -113,6 +113,8 @@ sr: other: "%{count} rezultata" filters: all: Sve + home: + subcategories_label: Pod kategorije home_header: global_status: Sveopšte stanje izvršenja nav_breadcrumb: diff --git a/decidim-admin/config/locales/bg.yml b/decidim-admin/config/locales/bg.yml index 4b8d7858c84b1..4e75bbea736bc 100644 --- a/decidim-admin/config/locales/bg.yml +++ b/decidim-admin/config/locales/bg.yml @@ -806,7 +806,7 @@ bg: reports: Доклади show_email: Показване на електронната поща status: Статус - unblock: Разблокирай Потребител + unblock: Разблокирай потребител unofficialize: Премахване на официализирането new: badge: Бадж за официализиране diff --git a/decidim-admin/config/locales/cs.yml b/decidim-admin/config/locales/cs.yml index de972e68de63d..48be67cb02c49 100644 --- a/decidim-admin/config/locales/cs.yml +++ b/decidim-admin/config/locales/cs.yml @@ -813,7 +813,7 @@ cs: index: actions: Akce badge: Odznak - block: Zablokovat uživatele + block: Blokovat uživatele created_at: Vytvořeno na name: Název nickname: Přezdívka diff --git a/decidim-admin/config/locales/de.yml b/decidim-admin/config/locales/de.yml index 5980d8bd88caf..703545e57e144 100644 --- a/decidim-admin/config/locales/de.yml +++ b/decidim-admin/config/locales/de.yml @@ -814,7 +814,7 @@ de: reports: Berichte show_email: E-Mail-Adresse anzeigen status: Status - unblock: Blockierung des Users aufheben + unblock: Blockierung des Benutzers aufheben unofficialize: Nicht offiziell new: badge: Offizielles Abzeichen diff --git a/decidim-admin/config/locales/eu.yml b/decidim-admin/config/locales/eu.yml index d02f75b158d05..681c1f88cb356 100644 --- a/decidim-admin/config/locales/eu.yml +++ b/decidim-admin/config/locales/eu.yml @@ -803,7 +803,7 @@ eu: index: actions: Ekintzak badge: Garaikurra - block: Blokeatu parte-hartzailea + block: Blokeatu erabiltzailea created_at: Noiz sortua name: Izena nickname: Ezizena diff --git a/decidim-admin/config/locales/fr-CA.yml b/decidim-admin/config/locales/fr-CA.yml index c06eb25e6850e..118a2e66ca352 100644 --- a/decidim-admin/config/locales/fr-CA.yml +++ b/decidim-admin/config/locales/fr-CA.yml @@ -876,7 +876,7 @@ fr-CA: external_domain: down: Descendre external_domain: Domaine externe - remove: Retirer + remove: Supprimer up: Monter form: add: Ajouter à la liste autorisée diff --git a/decidim-admin/config/locales/fr.yml b/decidim-admin/config/locales/fr.yml index 938f58557dd6a..5be21694f5f73 100644 --- a/decidim-admin/config/locales/fr.yml +++ b/decidim-admin/config/locales/fr.yml @@ -876,7 +876,7 @@ fr: external_domain: down: Descendre external_domain: Domaine externe - remove: Retirer + remove: Supprimer up: Monter form: add: Ajouter à la liste autorisée diff --git a/decidim-admin/config/locales/hu.yml b/decidim-admin/config/locales/hu.yml index b2ed74ed987b2..008e4c66cee39 100644 --- a/decidim-admin/config/locales/hu.yml +++ b/decidim-admin/config/locales/hu.yml @@ -798,7 +798,7 @@ hu: reports: Jelentések show_email: E-mail cím megjelenítése status: Állapot - unblock: Felhasználó tiltásának törlése + unblock: Felhasználó feloldása unofficialize: Hivatalossá tétel visszavonása new: badge: A hivatalosság jelvénye diff --git a/decidim-admin/config/locales/it.yml b/decidim-admin/config/locales/it.yml index 3104beacfdaeb..59f6acd94f80e 100644 --- a/decidim-admin/config/locales/it.yml +++ b/decidim-admin/config/locales/it.yml @@ -699,10 +699,14 @@ it: blocked: Bloccato unblocked: Non Bloccato moderations: + index: + title: Contenuto segnalato report: reasons: does_not_belong: Non appartiene + hidden_during_block: Nascosto durante il blocco dell'utente offensive: Offensivo + parent_hidden: Il contenuto padre è stato nascosto spam: Spam reports: index: @@ -846,6 +850,15 @@ it: edit: update: Aggiorna form: + colors: + choose_color: Scegli un colore primario + colors_title: Colori dell'organizzazione + colors_warning_html: Attenzione! Cambiare questi colori può violare l'accessibilità dei contrasti dei colori. Puoi controllare il contrasto della tua scelta dei colori con WebAIM Contrast Checker o altri strumenti simili. + explanation: Questo strumento ti aiuta a scegliere uno schema di colori, composto da tonalità ugualmente distanziate intorno alla gamma dei colori, che saranno utilizzate nel sito web dell'organizzazione. + legend_html: I principali colori dell'applicazione, basati sull'algoritmo Triadic. Il colore secondario è calcolato a partire dal colore primario e dalla saturazione che hai scelto. + saturation: Saturazione + title: Selettore colore + update_suggested_colors: Aggiorna i colori suggeriti cta_button_path_help_html: 'È possibile modificare la posizione in cui il pulsante "Chiamata all''Azione" nella home page viene mostrato. Per farlo utilizzare URL relativi, non URL assoluti. Accetta lettere, numeri, trattini e barre, e deve iniziare con una lettera. Il pulsante "Chiamata all''Azione" è mostrato nella home page tra il testo di benvenuto e la descrizione. Ad esempio: %{url}' cta_button_text_help: Puoi sovrascrivere il testo del pulsante Chiamata all'azione nella home page per ogni lingua disponibile nell'organizzazione. Se non è impostato, verrà utilizzato il valore predefinito. Il pulsante Chiamata all'azione è mostrato nella homepage tra il testo di benvenuto e la descrizione. header_snippets: @@ -867,6 +880,7 @@ it: up: Su form: add: Aggiungi alla lista consentita + title: Elenco di domini esterni consentiti participatory_space_private_users: create: error: Si è verificato un errore durante l'aggiunta di un utente privato per questo spazio partecipativo. @@ -881,9 +895,24 @@ it: create: Creare title: Nuovo utente privato dello spazio partecipativo. participatory_space_private_users_csv_imports: + create: + invalid: Si è verificato un problema durante la lettura del file CSV. Assicurati di aver seguito le istruzioni. + success: File CSV caricato con successo, stiamo inviando un'email di invito ai partecipanti. Potrebbe richiedere un po' di tempo. new: csv_upload: title: Carica il tuo file CSV + destroy: + button: Elimina tutti i partecipanti privati + confirm: Sei sicuro di voler eliminare tutti i partecipanti privati? Questa azione non può essere annullata, non sarai in grado di recuperarli. + empty: Non hai partecipanti privati. + explanation: Hai %{count} partecipanti privati. + title: Elimina tutti i partecipanti privati + example_file: 'File di esempio:' + explanation: 'Carica il file CSV. Il file deve contenere due colonne (e-mail e nome) senza intestazioni: nella prima sono elencati gli indirizzi e-mail degli utenti che si desidera aggiungere allo spazio partecipativo, nella seconda i loro nomi. Evita di usare caratteri non validi come `<>?%&^*#@()[]=+:;"{}\|` nella colonna dei nomi.' + explanation_example: | + john.doe@example.org%{csv_col_sep}John Doe + jane.doe@example.org%{csv_col_sep}Jane Doe + title: Importa partecipanti privati tramite CSV upload: Carica File reminders: create: @@ -919,6 +948,8 @@ it: create: error: Si è verificato un errore creando un nuovo ambito. success: OK, è stato creato il nuovo àmbito. + destroy: + success: Ambito eliminato correttamente. edit: title: Modifica l'àmbito update: Aggiorna @@ -1018,6 +1049,7 @@ it: verify_via_csv: Verifica tramite CSV user_groups_csv_verifications: new: + example_file: 'File di esempio:' explanation: Carica il tuo file CSV. Deve avere le e-mail ufficiali dei gruppi di utenti nella tua organizzazione nella prima colonna del file, senza intestazioni. Verranno convalidati solo i gruppi di utenti che hanno confermato la loro e-mail e che hanno una e-mail che appare nel file CSV. explanation_example: | acme@example.org @@ -1077,10 +1109,14 @@ it: models: moderation: fields: + deleted_resource: Risorsa eliminata hidden_at: Nascosto a participatory_space: Spazio partecipativo + report_count: Numero di segnalazioni + reportable_id: ID reportable_type: Tipo reported_content_url: URL del contenuto riportato + reports: Motivo visit_url: Visita URL report: fields: diff --git a/decidim-admin/config/locales/ko.yml b/decidim-admin/config/locales/ko.yml index 92e01f63cf604..f8bae96a14fe1 100644 --- a/decidim-admin/config/locales/ko.yml +++ b/decidim-admin/config/locales/ko.yml @@ -587,12 +587,10 @@ ko: success: 참가자가 성공적으로 차단되었습니다. index: badge: 뱃지 - block: 사용자 차단 name: 이름 nickname: 닉네임 show_email: 이메일 주소 보기 status: 상태 - unblock: 사용자 차단 해제 show_email_modal: email_address: 이메일 주소 full_name: 전체 이름 diff --git a/decidim-admin/config/locales/lb.yml b/decidim-admin/config/locales/lb.yml index 3c89e3f12d854..195b21c505754 100644 --- a/decidim-admin/config/locales/lb.yml +++ b/decidim-admin/config/locales/lb.yml @@ -638,7 +638,7 @@ lb: reports: Berichte show_email: E-Mail-Adresse anzeigen status: Status - unblock: Blockierung des Users aufheben + unblock: Blockierung des Benutzers aufheben unofficialize: Nicht offiziell new: badge: Offizielles Abzeichen diff --git a/decidim-admin/config/locales/pt-BR.yml b/decidim-admin/config/locales/pt-BR.yml index 6d784ae5e5706..edf0c6fc8230c 100644 --- a/decidim-admin/config/locales/pt-BR.yml +++ b/decidim-admin/config/locales/pt-BR.yml @@ -803,7 +803,7 @@ pt-BR: reports: Denúncias show_email: Mostrar endereço de e-mail status: Status - unblock: Desbloquear Usuário + unblock: Desbloquear usuário unofficialize: Não oficializar new: badge: Emblema de oficialização diff --git a/decidim-admin/config/locales/ro-RO.yml b/decidim-admin/config/locales/ro-RO.yml index 45fd8edde8a76..bbca4c669e8f5 100644 --- a/decidim-admin/config/locales/ro-RO.yml +++ b/decidim-admin/config/locales/ro-RO.yml @@ -761,7 +761,7 @@ ro: index: actions: Acțiuni badge: Insignă - block: Blochează utilizator + block: Blochează utilizatorul created_at: Creat la name: Nume nickname: Pseudonim diff --git a/decidim-admin/config/locales/sq-AL.yml b/decidim-admin/config/locales/sq-AL.yml index 7c60992c0f6a4..8dc8a4c9f3d7d 100644 --- a/decidim-admin/config/locales/sq-AL.yml +++ b/decidim-admin/config/locales/sq-AL.yml @@ -476,7 +476,6 @@ sq: officializations: index: actions: Veprimet - block: Blloko përdoruesin created_at: Krijuar më name: Emri nickname: Nofka diff --git a/decidim-assemblies/config/locales/it.yml b/decidim-assemblies/config/locales/it.yml index a476aae961b03..7ea7627b48c1d 100644 --- a/decidim-assemblies/config/locales/it.yml +++ b/decidim-assemblies/config/locales/it.yml @@ -185,7 +185,9 @@ it: success: Admin aggiornato correttamente per questa assemblea. filters: ceased_date_not_null: + label: Concluso values: + 'false': Non concluso 'true': Conclusq decidim_assemblies_type_id_eq: label: Tipo di assemblea @@ -402,6 +404,7 @@ it: assemblies: create_assembly_member: email_intro: Un amministratore dell'assemblea %{resource_name} ti ha aggiunto come membro. + email_outro: Hai ricevuto questa notifica perché sei stato invitato in un assemblea. Controlla la pagina assembly per contribuire! email_subject: Hai ricevuto un invito a far parte dell'Assemblea %{resource_name}! notification_title: Sei stato registrato come membro dell'Assemblea %{resource_name}. Controlla la pagina dell'assemblea per contribuire! assembly: @@ -442,6 +445,10 @@ it: take_part: Partecipa index: promoted_assemblies: Assemblee in evidenza + metadata: + children_item: + one: "%{count} assemblee" + other: "%{count} assemblee" order_by_assemblies: assemblies: one: "%{count} assemblee" diff --git a/decidim-conferences/config/locales/ar.yml b/decidim-conferences/config/locales/ar.yml index 12ed33c5110da..dbc7ae0b98a61 100644 --- a/decidim-conferences/config/locales/ar.yml +++ b/decidim-conferences/config/locales/ar.yml @@ -98,8 +98,6 @@ ar: conference_copies: new: copy: نسخ - select: حدد البيانات التي ترغب في تكرارها - title: مؤتمر مكرر conference_publications: create: error: كانت هناك مشكلة في نشر هذا المؤتمر. @@ -157,10 +155,6 @@ ar: update: error: حدثت مشكلة أثناء تحديث هذا المؤتمر. success: تم تحديث المؤتمر بنجاح. - conferences_copies: - create: - error: كانت هناك مشكلة في تكرار هذا المؤتمر. - success: تم تكرار المؤتمر بنجاح. media_links: create: error: حدثت مشكلة أثناء إنشاء رابط وسائط جديد. diff --git a/decidim-conferences/config/locales/bg.yml b/decidim-conferences/config/locales/bg.yml index 15baa1d9ca5dc..72437280fa1c2 100644 --- a/decidim-conferences/config/locales/bg.yml +++ b/decidim-conferences/config/locales/bg.yml @@ -100,8 +100,6 @@ bg: conference_copies: new: copy: Копиране - select: Изберете кои данни искате да дублирате - title: Дублиране на конференцията conference_publications: create: error: Възникна проблем при публикуването на тази конференция. @@ -168,10 +166,6 @@ bg: update: error: Възникна проблем при актуализирането на тази конференция. success: Конференцията беше актуализирана успешно. - conferences_copies: - create: - error: Възникна проблем при дублирането на тази конференция. - success: Конференцията беше дублирана успешно. media_links: create: error: Възникна проблем при създаването на нов медия линк. diff --git a/decidim-conferences/config/locales/ca-IT.yml b/decidim-conferences/config/locales/ca-IT.yml index b64699a475e37..987116b4bf331 100644 --- a/decidim-conferences/config/locales/ca-IT.yml +++ b/decidim-conferences/config/locales/ca-IT.yml @@ -103,7 +103,7 @@ ca-IT: new: copy: Copiar select: Selecciona quines dades vols duplicar - title: Jornada duplicada + title: Duplicar la jornada conference_publications: create: error: S'ha produït un error en publicar aquesta jornada. diff --git a/decidim-conferences/config/locales/ca.yml b/decidim-conferences/config/locales/ca.yml index cc9862843d9aa..81affcbe75653 100644 --- a/decidim-conferences/config/locales/ca.yml +++ b/decidim-conferences/config/locales/ca.yml @@ -103,7 +103,7 @@ ca: new: copy: Copiar select: Selecciona quines dades vols duplicar - title: Jornada duplicada + title: Duplicar la jornada conference_publications: create: error: S'ha produït un error en publicar aquesta jornada. diff --git a/decidim-conferences/config/locales/cs.yml b/decidim-conferences/config/locales/cs.yml index 61e593bc47eb9..d5b21cc169d97 100644 --- a/decidim-conferences/config/locales/cs.yml +++ b/decidim-conferences/config/locales/cs.yml @@ -108,8 +108,6 @@ cs: conference_copies: new: copy: Kopírovat - select: Vyberte, která data chcete duplikovat - title: Duplikovat konferenci conference_publications: create: error: Při publikování této konference došlo k chybě. @@ -180,10 +178,6 @@ cs: update: error: Při aktualizaci této konference došlo k chybě. success: Konference byla úspěšně aktualizována. - conferences_copies: - create: - error: Při kopírování této konference došlo k chybě. - success: Konference byla úspěšně duplikována. media_links: create: error: Při vytváření nového odkazu na média došlo k chybě. diff --git a/decidim-conferences/config/locales/de.yml b/decidim-conferences/config/locales/de.yml index f7dfdfd3f902d..0de3788384a08 100644 --- a/decidim-conferences/config/locales/de.yml +++ b/decidim-conferences/config/locales/de.yml @@ -102,8 +102,6 @@ de: conference_copies: new: copy: Kopieren - select: Wählen Sie die Daten aus, die Sie duplizieren möchten - title: Doppelte Konferenz conference_publications: create: error: Beim Veröffentlichen dieser Konferenz ist ein Fehler aufgetreten. @@ -174,10 +172,6 @@ de: update: error: Beim Aktualisieren dieser Konferenz ist ein Fehler aufgetreten. success: Konferenz wurde erfolgreich aktualisiert. - conferences_copies: - create: - error: Beim Duplizieren dieser Konferenz ist ein Fehler aufgetreten. - success: Konferenz wurde erfolgreich dupliziert. media_links: create: error: Beim Erstellen einer neuen Medienverknüpfung ist ein Fehler aufgetreten. @@ -344,7 +338,7 @@ de: admin: conference_copies: form: - slug_help_html: 'URL-Slugs werden zum Generieren der URLs verwendet, die auf diese Konferenz verweisen. Akzeptiert werden nur Buchstaben, Zahlen und Bindestriche und es muss mit einem Buchstaben beginnen. Beispiel: %{url}' + slug_help_html: 'URL-Slugs werden verwendet, um URLs zu generieren, die auf diese Konferenz verweisen. Akzeptiert nur Buchstaben, Zahlen und Bindestriche und muss mit einem Buchstaben beginnen. Beispiel: %{url}' conference_invites: create: error: Beim Einladen des Benutzers zur Teilnahme an der Konferenz ist ein Problem aufgetreten. diff --git a/decidim-conferences/config/locales/el.yml b/decidim-conferences/config/locales/el.yml index 0f30fc4f28aea..b586793ec130e 100644 --- a/decidim-conferences/config/locales/el.yml +++ b/decidim-conferences/config/locales/el.yml @@ -93,8 +93,6 @@ el: conference_copies: new: copy: Αντιγραφή - select: Επιλέξτε ποια δεδομένα θέλετε να αντιγράψετε - title: Αντιγραφή διάσκεψης conference_publications: create: error: Υπήρξε ένα πρόβλημα κατά τη δημοσίευση αυτής της διάσκεψης. @@ -154,10 +152,6 @@ el: update: error: Υπήρξε ένα πρόβλημα κατά την ενημέρωση αυτής της διάσκεψης. success: Η διάσκεψη ενημερώθηκε με επιτυχία. - conferences_copies: - create: - error: Υπήρξε ένα πρόβλημα κατά την αντιγραφή αυτής της διάσκεψης. - success: Η διάσκεψη αντιγράφτηκε με επιτυχία. media_links: create: error: Υπήρξε ένα πρόβλημα κατά τη δημιουργία ενός νέου συνδέσμου πολυμέσων. diff --git a/decidim-conferences/config/locales/es-MX.yml b/decidim-conferences/config/locales/es-MX.yml index 2a40015d103b8..a0c84bb533c0a 100644 --- a/decidim-conferences/config/locales/es-MX.yml +++ b/decidim-conferences/config/locales/es-MX.yml @@ -103,7 +103,7 @@ es-MX: new: copy: Copiar select: Selecciona qué datos quieres duplicar - title: Conferencia duplicada + title: Duplicar la jornada conference_publications: create: error: Hubo un error al publicar esta conferencia. @@ -176,8 +176,8 @@ es-MX: success: Conferencia actualizada con éxito. conferences_copies: create: - error: Hubo un error al duplicar esta conferencia. - success: Conferencia duplicada con éxito. + error: Se ha producido un error al duplicar esta jornada. + success: La jornada se ha duplicado correctamente. media_links: create: error: Se ha producido un error al crear un nuevo enlace multimedia. diff --git a/decidim-conferences/config/locales/es-PY.yml b/decidim-conferences/config/locales/es-PY.yml index a9206dcc3fe5b..636b75cb1ea6b 100644 --- a/decidim-conferences/config/locales/es-PY.yml +++ b/decidim-conferences/config/locales/es-PY.yml @@ -102,8 +102,8 @@ es-PY: conference_copies: new: copy: Dupdo - select: Seleccione qué datos quiere duplicar - title: Conferencia duplicada + select: Selecciona qué datos quieres duplicar + title: Duplicar la jornada conference_publications: create: error: Hubo un error al publicar esta conferencia. @@ -176,8 +176,8 @@ es-PY: success: Conferencia actualizada con éxito conferences_copies: create: - error: Hubo un error al duplicar esta conferencia. - success: Conferencia duplicada con éxito. + error: Se ha producido un error al duplicar esta jornada. + success: La jornada se ha duplicado correctamente. media_links: create: error: Se ha producido un error al crear un nuevo enlace de medios. diff --git a/decidim-conferences/config/locales/es.yml b/decidim-conferences/config/locales/es.yml index ff675862f0f1f..77bf58edf09e5 100644 --- a/decidim-conferences/config/locales/es.yml +++ b/decidim-conferences/config/locales/es.yml @@ -103,7 +103,7 @@ es: new: copy: Copiar select: Selecciona qué datos quieres duplicar - title: Jornada duplicada + title: Duplicar la jornada conference_publications: create: error: Se ha producido un error al publicar esta jornada. @@ -177,7 +177,7 @@ es: conferences_copies: create: error: Se ha producido un error al duplicar esta jornada. - success: Jornada duplicada correctamente. + success: La jornada se ha duplicado correctamente. media_links: create: error: Se ha producido un error al crear un nuevo enlace multimedia. diff --git a/decidim-conferences/config/locales/eu.yml b/decidim-conferences/config/locales/eu.yml index 23615dfa60147..8262f29caa3ca 100644 --- a/decidim-conferences/config/locales/eu.yml +++ b/decidim-conferences/config/locales/eu.yml @@ -177,7 +177,7 @@ eu: conferences_copies: create: error: Arazo bat egon da jardunaldi hau bikoiztean. - success: Jardunaldia zuzen eguneratua. + success: Jardunaldia zuzen bikoiztua. media_links: create: error: Arazo bat egon da beste komunikazio-esteka bat sortzean. diff --git a/decidim-conferences/config/locales/fi-plain.yml b/decidim-conferences/config/locales/fi-plain.yml index bbe9bbf4bfb7d..b5c5563684834 100644 --- a/decidim-conferences/config/locales/fi-plain.yml +++ b/decidim-conferences/config/locales/fi-plain.yml @@ -176,8 +176,8 @@ fi-pl: success: Konferenssi päivitetty onnistuneesti. conferences_copies: create: - error: Virhe kopioitaessa konferenssia. - success: Konferenssi kopioitu onnistuneesti. + error: Konferenssin kopioiminen epäonnistui. + success: Konferenssin kopioiminen onnistui. media_links: create: error: Luotaessa uutta medialinkkiä tapahtui virhe. diff --git a/decidim-conferences/config/locales/fi.yml b/decidim-conferences/config/locales/fi.yml index d5dc40a5f865f..4a49e36243ee0 100644 --- a/decidim-conferences/config/locales/fi.yml +++ b/decidim-conferences/config/locales/fi.yml @@ -176,8 +176,8 @@ fi: success: Konferenssin päivitys onnistui. conferences_copies: create: - error: Konferenssin kopiointi epäonnistui. - success: Konferenssin kopiointi onnistui. + error: Konferenssin kopioiminen epäonnistui. + success: Konferenssin kopioiminen onnistui. media_links: create: error: Uuden medialinkin luonti epäonnistui. diff --git a/decidim-conferences/config/locales/fr-CA.yml b/decidim-conferences/config/locales/fr-CA.yml index bb46e29cab961..37ed9ef4f94c8 100644 --- a/decidim-conferences/config/locales/fr-CA.yml +++ b/decidim-conferences/config/locales/fr-CA.yml @@ -176,7 +176,7 @@ fr-CA: success: Conférence mise à jour avec succès. conferences_copies: create: - error: Une erreur s'est produite lors de la duplication de cette conférence. + error: Un problème est survenu lors de la duplication de cette conférence. success: Conférence dupliquée avec succès. media_links: create: diff --git a/decidim-conferences/config/locales/fr.yml b/decidim-conferences/config/locales/fr.yml index 424582b5da7bf..ee27f42c615c4 100644 --- a/decidim-conferences/config/locales/fr.yml +++ b/decidim-conferences/config/locales/fr.yml @@ -176,7 +176,7 @@ fr: success: Conférence mise à jour avec succès. conferences_copies: create: - error: Une erreur s'est produite lors de la duplication de cette conférence. + error: Un problème est survenu lors de la duplication de cette conférence. success: Conférence dupliquée avec succès. media_links: create: diff --git a/decidim-conferences/config/locales/gl.yml b/decidim-conferences/config/locales/gl.yml index 9880f3fe1aaac..ddaf656198515 100644 --- a/decidim-conferences/config/locales/gl.yml +++ b/decidim-conferences/config/locales/gl.yml @@ -80,8 +80,6 @@ gl: conference_copies: new: copy: Copiar - select: Seleccione os datos que desexa duplicar - title: Conferencia duplicada conference_publications: create: error: Produciuse un erro ao publicar esta conferencia. @@ -139,10 +137,6 @@ gl: update: error: Houbo un erro ao actualizar esta conferencia. success: A conferencia actualizouse con éxito. - conferences_copies: - create: - error: Produciuse un erro ao duplicar esta conferencia. - success: A conferencia duplicouse con éxito. media_links: create: error: Produciuse un erro ao crear unha nova ligazón multimedia. diff --git a/decidim-conferences/config/locales/hu.yml b/decidim-conferences/config/locales/hu.yml index 2b9b5130e6d5b..a2dac2f588fc9 100644 --- a/decidim-conferences/config/locales/hu.yml +++ b/decidim-conferences/config/locales/hu.yml @@ -99,8 +99,6 @@ hu: conference_copies: new: copy: Másolat - select: Válassza ki, mely adatokat szeretné megismételni - title: Duplikált konferencia conference_publications: create: error: Hiba történt a konferencia közzétételében. @@ -161,10 +159,6 @@ hu: update: error: Hiba történt a konferencia frissítésekor. success: A konferencia sikeresen frissült. - conferences_copies: - create: - error: Hiba történt a konferencia másolása során. - success: A konferencia sikeresen megismétlődött. media_links: create: error: Hiba történt új média link létrehozása közben. diff --git a/decidim-conferences/config/locales/id-ID.yml b/decidim-conferences/config/locales/id-ID.yml index 9f3584e78627e..8231a40fcb777 100644 --- a/decidim-conferences/config/locales/id-ID.yml +++ b/decidim-conferences/config/locales/id-ID.yml @@ -44,8 +44,6 @@ id: conference_copies: new: copy: Salinan - select: Pilih data mana yang ingin Anda gandakan - title: Konferensi duplikat conference_publications: create: error: Terjadi kesalahan saat mempublikasikan konferensi ini. @@ -103,10 +101,6 @@ id: update: error: Ada kesalahan saat memperbarui konferensi ini. success: Konferensi berhasil diperbarui. - conferences_copies: - create: - error: Ada kesalahan saat menduplikasi konferensi ini. - success: Konferensi berhasil digandakan. media_links: create: error: Terjadi kesalahan saat membuat tautan media baru. diff --git a/decidim-conferences/config/locales/it.yml b/decidim-conferences/config/locales/it.yml index ab13403cf2f92..61b6fc8cc589f 100644 --- a/decidim-conferences/config/locales/it.yml +++ b/decidim-conferences/config/locales/it.yml @@ -80,8 +80,6 @@ it: conference_copies: new: copy: Copia - select: Seleziona i dati che desideri duplicare - title: Duplica Conferenza conference_publications: create: error: Si è verificato un errore durante la pubblicazione di questa conferenza. @@ -140,10 +138,6 @@ it: update: error: Si è verificato un errore durante l'aggiornamento di questa conferenza. success: Conferenza aggiornata con successo. - conferences_copies: - create: - error: Si è verificato un errore durante la duplicazione di questa conferenza. - success: Conferenza duplicata con successo. media_links: create: error: Si è verificato un errore durante la creazione di un nuovo collegamento multimediale. diff --git a/decidim-conferences/config/locales/ja.yml b/decidim-conferences/config/locales/ja.yml index ec9c8ab318752..bb7d39ca5a305 100644 --- a/decidim-conferences/config/locales/ja.yml +++ b/decidim-conferences/config/locales/ja.yml @@ -99,7 +99,7 @@ ja: conference_copies: new: copy: コピー - select: 複製したいデータを選択してください + select: title: カンファレンスを複製 conference_publications: create: diff --git a/decidim-conferences/config/locales/lb.yml b/decidim-conferences/config/locales/lb.yml index 9d33212764581..1d87906082c43 100644 --- a/decidim-conferences/config/locales/lb.yml +++ b/decidim-conferences/config/locales/lb.yml @@ -77,10 +77,6 @@ lb: update: error: Beim Aktualisieren dieser Konferenz ist ein Fehler aufgetreten. success: Konferenz wurde erfolgreich aktualisiert. - conferences_copies: - create: - error: Beim Duplizieren dieser Konferenz ist ein Fehler aufgetreten. - success: Konferenz wurde erfolgreich dupliziert. media_links: create: error: Beim Erstellen einer neuen Medienverknüpfung ist ein Fehler aufgetreten. diff --git a/decidim-conferences/config/locales/lt.yml b/decidim-conferences/config/locales/lt.yml index ccf36ec3bc386..f2349c6631db4 100644 --- a/decidim-conferences/config/locales/lt.yml +++ b/decidim-conferences/config/locales/lt.yml @@ -99,8 +99,6 @@ lt: conference_copies: new: copy: Kopijuoti - select: Pasirinkite, kuriuos duomenis norėtumėte dubliuoti - title: Dubliuoti konferenciją conference_publications: create: error: Publikuojant šią konferenciją iškilo problema. @@ -160,10 +158,6 @@ lt: update: error: Atnaujinant šią konferenciją iškilo problema. success: Konferencija atnaujinta. - conferences_copies: - create: - error: Dubliuojant šią konferenciją iškilo problema. - success: Konferencija dubliuota. media_links: create: error: Kuriant naują multimedijos nuorodą iškilo problema. diff --git a/decidim-conferences/config/locales/lv.yml b/decidim-conferences/config/locales/lv.yml index 2df097c1406cb..a976342ea5e3c 100644 --- a/decidim-conferences/config/locales/lv.yml +++ b/decidim-conferences/config/locales/lv.yml @@ -50,8 +50,6 @@ lv: conference_copies: new: copy: Kopēt - select: Atlasiet, kurus datus vēlaties dublēt - title: Dublēt konferenci conference_publications: create: error: Konferences publicēšanas laikā radās problēma. @@ -109,10 +107,6 @@ lv: update: error: Šīs konferences atjaunināšanas laikā radās problēma. success: Konference ir veiksmīgi atjaunināta. - conferences_copies: - create: - error: Radās problēma ar šīs konferences dublēšanu. - success: Konference ir veiksmīgi dublēta. media_links: create: error: Jaunas mediju saites izveides laikā radās problēma. diff --git a/decidim-conferences/config/locales/nl.yml b/decidim-conferences/config/locales/nl.yml index a53eccb1a19f8..81c75d92fd060 100644 --- a/decidim-conferences/config/locales/nl.yml +++ b/decidim-conferences/config/locales/nl.yml @@ -80,8 +80,6 @@ nl: conference_copies: new: copy: Kopiëren - select: Selecteer welke gegevens u wilt dupliceren - title: Dubbele conferentie conference_publications: create: error: Er is een fout opgetreden bij het publiceren van deze conferentie. @@ -139,10 +137,6 @@ nl: update: error: Er is een probleem opgetreden bij het bijwerken van deze conferentie. success: Conferentie succesvol bijgewerkt. - conferences_copies: - create: - error: Er was een probleem bij het dupliceren van deze conferentie. - success: Conferentie succesvol gedupliceerd. media_links: create: error: Er is een probleem opgetreden bij het maken van een nieuwe mediakoppeling. diff --git a/decidim-conferences/config/locales/no.yml b/decidim-conferences/config/locales/no.yml index c6e9e70758b87..cfe1f38b646f4 100644 --- a/decidim-conferences/config/locales/no.yml +++ b/decidim-conferences/config/locales/no.yml @@ -80,8 +80,6 @@ conference_copies: new: copy: Kopier - select: Velg hvilke data du vil duplisere - title: Duplisert konferanse conference_publications: create: error: Det oppstod et problem med å publisere denne konferansen. @@ -139,10 +137,6 @@ update: error: Det oppstod et problem med å oppdatere denne konferansen. success: Konferansen ble oppdatert. - conferences_copies: - create: - error: Det oppstod et problem med å duplisere denne konferansen. - success: Konferansen ble duplisert. media_links: create: error: Det oppstod et problem med å opprette en ny media lenke. diff --git a/decidim-conferences/config/locales/pl.yml b/decidim-conferences/config/locales/pl.yml index bd338afa0617c..29d91594f7f39 100644 --- a/decidim-conferences/config/locales/pl.yml +++ b/decidim-conferences/config/locales/pl.yml @@ -106,8 +106,6 @@ pl: conference_copies: new: copy: Kopiuj - select: Wybierz dane, które chcesz zduplikować - title: Zduplikuj konferencję conference_publications: create: error: Wystąpił błąd podczas publikowania tej konferencji. @@ -174,10 +172,6 @@ pl: update: error: Wystąpił błąd podczas aktualizowania konferencji. success: Konferencja została zaktualizowana pomyślnie. - conferences_copies: - create: - error: Wystąpił błąd podczas duplikowania konferencji. - success: Konferencja zduplikowana pomyślnie. media_links: create: error: Podczas tworzenia nowego linku do multimediów wystąpił błąd. diff --git a/decidim-conferences/config/locales/pt-BR.yml b/decidim-conferences/config/locales/pt-BR.yml index a5f29eb142912..0ca8edfa71ec2 100644 --- a/decidim-conferences/config/locales/pt-BR.yml +++ b/decidim-conferences/config/locales/pt-BR.yml @@ -99,8 +99,6 @@ pt-BR: conference_copies: new: copy: cópia de - select: Selecione quais dados você gostaria de duplicar - title: Conferência duplicada conference_publications: create: error: Ocorreu um erro ao publicar esta conferência. @@ -167,10 +165,6 @@ pt-BR: update: error: Houve um erro ao atualizar esta conferência. success: Conferência atualizada com sucesso. - conferences_copies: - create: - error: Houve um erro ao duplicar esta conferência. - success: Conferência duplicada com sucesso. media_links: create: error: Ocorreu um erro ao criar um novo link de mídia. @@ -321,7 +315,7 @@ pt-BR: admin: conference_copies: form: - slug_help_html: 'Os slugs de URL são usados ​​para gerar as URLs que apontam para esta conferência. Aceita apenas letras, números e traços e deve começar com uma letra. Exemplo: %{url}' + slug_help_html: 'Os slugs de URL são usados para gerar as URLs que apontam para esta conferência. Aceita apenas letras, números e traços e deve começar com uma letra. Exemplo: %{url}' conference_invites: create: error: Houve um problema ao convidar o usuário para participar da conferência. diff --git a/decidim-conferences/config/locales/pt.yml b/decidim-conferences/config/locales/pt.yml index d275d983265b8..08579b96ace9a 100644 --- a/decidim-conferences/config/locales/pt.yml +++ b/decidim-conferences/config/locales/pt.yml @@ -80,8 +80,6 @@ pt: conference_copies: new: copy: Copiar - select: Selecione os dados que pretende duplicar - title: Conferência duplicada conference_publications: create: error: Ocorreu um problema ao publicar esta conferência. @@ -139,10 +137,6 @@ pt: update: error: Ocorreu um problema ao atualizar esta conferência. success: Conferência atualizada corretamente. - conferences_copies: - create: - error: Ocorreu um problema ao duplicar esta conferência. - success: Conferência duplicada com sucesso. media_links: create: error: Ocorreu um problema ao criar uma nova hiperligação de multimédia. diff --git a/decidim-conferences/config/locales/ro-RO.yml b/decidim-conferences/config/locales/ro-RO.yml index 872cc47a36c57..0277993ea443a 100644 --- a/decidim-conferences/config/locales/ro-RO.yml +++ b/decidim-conferences/config/locales/ro-RO.yml @@ -98,8 +98,6 @@ ro: conference_copies: new: copy: Copiază - select: Selectați datele pe care doriți să le duplicați - title: Duplică conferinţa conference_publications: create: error: A apărut o problemă la publicarea acestei conferinţe. @@ -163,10 +161,6 @@ ro: update: error: A apărut o eroare la actualizarea conferinței. success: Conferință actualizată cu succes. - conferences_copies: - create: - error: A apărut o problemă la duplicarea acestei conferinţe. - success: Conferință duplicată cu succes. media_links: create: error: A apărut o problemă la crearea unei noi legături media. diff --git a/decidim-conferences/config/locales/sk.yml b/decidim-conferences/config/locales/sk.yml index 631e000d0f195..2c5e0ae42d6e3 100644 --- a/decidim-conferences/config/locales/sk.yml +++ b/decidim-conferences/config/locales/sk.yml @@ -53,8 +53,6 @@ sk: conference_copies: new: copy: Kopírovať - select: Označte údaje, ktoré by ste chceli duplikovať - title: Duplikovať konferenciu conference_publications: create: error: Vyskytol sa problém s publikovaním konferencie. @@ -112,10 +110,6 @@ sk: update: error: Vyskytol sa problém s aktualizáciou tejto konferencie. success: Konferencia úspešne aktualizovaná. - conferences_copies: - create: - error: Vyskytol sa problém s duplikáciou tejto konferencie. - success: Konferencia úspešne duplikovaná. media_links: create: error: Pri tvorbe mediálneho odkazu nastala chyba. diff --git a/decidim-conferences/config/locales/sv.yml b/decidim-conferences/config/locales/sv.yml index 4185b085246ae..b2dd17c1d53ed 100644 --- a/decidim-conferences/config/locales/sv.yml +++ b/decidim-conferences/config/locales/sv.yml @@ -102,8 +102,6 @@ sv: conference_copies: new: copy: Kopiera - select: Välj vilka data som du vill duplicera - title: Duplicera konferens conference_publications: create: error: Det gick inte att publicera konferensen. @@ -174,10 +172,6 @@ sv: update: error: Det gick inte att uppdatera konferensen. success: Konferensen har uppdaterats. - conferences_copies: - create: - error: Det gick inte att duplicera konferensen. - success: Konferensen har duplicerats. media_links: create: error: Det gick inte att skapa en ny medialänk. diff --git a/decidim-conferences/config/locales/tr-TR.yml b/decidim-conferences/config/locales/tr-TR.yml index d2d3d878b4223..f856afc592c10 100644 --- a/decidim-conferences/config/locales/tr-TR.yml +++ b/decidim-conferences/config/locales/tr-TR.yml @@ -53,8 +53,6 @@ tr: conference_copies: new: copy: Kopyala - select: Çoğaltmak istediğiniz verileri seçin - title: Konferansı çoğalt conference_publications: create: error: Bu konferansı yayınlarken bir sorun oluştu. @@ -112,10 +110,6 @@ tr: update: error: Bu konferans güncellenirken bir hata oluştu. success: Konferans başarıyla güncellendi. - conferences_copies: - create: - error: Bu konferansı çoğaltırken bir hata oluştu. - success: Konferans başarıyla kopyalandı. media_links: create: error: Yeni bir medya bağlantısı oluştururken bir hata oluştu. diff --git a/decidim-conferences/config/locales/zh-CN.yml b/decidim-conferences/config/locales/zh-CN.yml index d0ff1297f5dbf..d5997deda0ebf 100644 --- a/decidim-conferences/config/locales/zh-CN.yml +++ b/decidim-conferences/config/locales/zh-CN.yml @@ -44,8 +44,6 @@ zh-CN: conference_copies: new: copy: 复制 - select: 选择要重复的数据 - title: 复制会议 conference_publications: create: error: 发布此会议时出现问题。 @@ -103,10 +101,6 @@ zh-CN: update: error: 更新此会议时出现问题。 success: 会议已成功更新。 - conferences_copies: - create: - error: 复制此会议时出现问题。 - success: 会议成功重复。 media_links: create: error: 创建新媒体链接时出现问题。 diff --git a/decidim-conferences/config/locales/zh-TW.yml b/decidim-conferences/config/locales/zh-TW.yml index 147b5784554da..506fab10c03d6 100644 --- a/decidim-conferences/config/locales/zh-TW.yml +++ b/decidim-conferences/config/locales/zh-TW.yml @@ -90,8 +90,6 @@ zh-TW: conference_copies: new: copy: 複製 - select: 請選擇您想要複製的資料 - title: 複製研討會 conference_publications: create: error: 發布此研討會時出現問題。 @@ -151,10 +149,6 @@ zh-TW: update: error: 更新此研討會時發生問題。 success: 研討會更新成功。 - conferences_copies: - create: - error: 複製此研討會時發生問題。 - success: 研討會複製成功。 media_links: create: error: 創建新的媒體連結時出現問題。 diff --git a/decidim-core/config/locales/ca-IT.yml b/decidim-core/config/locales/ca-IT.yml index d8179d8998f80..4b5bd6f9c182e 100644 --- a/decidim-core/config/locales/ca-IT.yml +++ b/decidim-core/config/locales/ca-IT.yml @@ -2033,6 +2033,8 @@ ca-IT: profile: El meu compte public_profile: El meu perfil públic title: Enllaços del perfil + unread_conversations: Tens converses sense llegir + unread_notifications: Tens notificacions sense llegir user_profile: account: Compte authorizations: Autoritzacions diff --git a/decidim-core/config/locales/ca.yml b/decidim-core/config/locales/ca.yml index b0b535afc0bdc..84ce4c407c764 100644 --- a/decidim-core/config/locales/ca.yml +++ b/decidim-core/config/locales/ca.yml @@ -2033,6 +2033,8 @@ ca: profile: El meu compte public_profile: El meu perfil públic title: Enllaços del perfil + unread_conversations: Tens converses sense llegir + unread_notifications: Tens notificacions sense llegir user_profile: account: Compte authorizations: Autoritzacions diff --git a/decidim-core/config/locales/cs.yml b/decidim-core/config/locales/cs.yml index 5eb66f7c7e5a3..a91874e8ede15 100644 --- a/decidim-core/config/locales/cs.yml +++ b/decidim-core/config/locales/cs.yml @@ -1782,7 +1782,7 @@ cs: confirm_new_password: Potvrďte nové heslo new_password: Nové heslo old_password_help: Chcete-li potvrdit změny vašeho účtu, zadejte prosím své aktuální heslo. - password_help: "minimální počet znaků je %{minimun_characters}, nesmí být příliš obvyklý (např. 123456) a musí se lišit od vašeho uživatelského jména a e-mailu." + password_help: "minimální počet znaků je %{minimum_characters}, nesmí být příliš obvyklý (např. 123456) a musí se lišit od vašeho uživatelského jména a e-mailu." password_help_admin: "minimální počet znaků je %{minimum_characters}, nesmí být příliš časté (např. 123456), musí se lišit od vaší přezdívky a e-mailu a musí se lišit od vašich starých hesel." title: Změna hesla new: @@ -2062,6 +2062,7 @@ cs: profile: Můj účet public_profile: Můj veřejný profil title: Profilové odkazy + unread_notifications: Máte nepřečtené oznámení user_profile: account: Účet authorizations: Autorizace diff --git a/decidim-core/config/locales/es-MX.yml b/decidim-core/config/locales/es-MX.yml index 8d0fad70cd751..2c2f946b93252 100644 --- a/decidim-core/config/locales/es-MX.yml +++ b/decidim-core/config/locales/es-MX.yml @@ -2036,6 +2036,8 @@ es-MX: profile: Mi cuenta public_profile: Mi perfil público title: Enlaces de perfil + unread_conversations: Tienes conversaciones sin leer + unread_notifications: Tienes notificaciones sin leer user_profile: account: Cuenta authorizations: Autorizaciones diff --git a/decidim-core/config/locales/es-PY.yml b/decidim-core/config/locales/es-PY.yml index 179fb7d088b3c..9a1baa101c8d1 100644 --- a/decidim-core/config/locales/es-PY.yml +++ b/decidim-core/config/locales/es-PY.yml @@ -2036,6 +2036,8 @@ es-PY: profile: Mi cuenta public_profile: Mi perfil público title: Enlaces de perfil + unread_conversations: Tienes conversaciones sin leer + unread_notifications: Tienes notificaciones sin leer user_profile: account: Cuenta authorizations: Autorizaciones diff --git a/decidim-core/config/locales/es.yml b/decidim-core/config/locales/es.yml index ab123078148b7..a1e4c96ba8c89 100644 --- a/decidim-core/config/locales/es.yml +++ b/decidim-core/config/locales/es.yml @@ -2033,6 +2033,8 @@ es: profile: Mi cuenta public_profile: Mi perfil público title: Enlaces de perfil + unread_conversations: Tienes conversaciones sin leer + unread_notifications: Tienes notificaciones sin leer user_profile: account: Cuenta authorizations: Autorizaciones diff --git a/decidim-core/config/locales/eu.yml b/decidim-core/config/locales/eu.yml index 0799a1b0beaa7..6f8f7330224aa 100644 --- a/decidim-core/config/locales/eu.yml +++ b/decidim-core/config/locales/eu.yml @@ -1501,6 +1501,7 @@ eu: searches: filters: jump_to: 'Salto egin:' + resource: "%{label} %{collection} artean" search: Bilatu state: active: Aktiboak @@ -2033,6 +2034,8 @@ eu: profile: Nire kontua public_profile: Nire profil publikoa title: Profilaren estekak + unread_conversations: Irakurri gabe dituzu elkarrizketak + unread_notifications: Irakurri gabe dituzu jakinarazpenak user_profile: account: Kontua authorizations: Baimenak diff --git a/decidim-core/config/locales/fi-plain.yml b/decidim-core/config/locales/fi-plain.yml index 267ac09b4de83..88c993463d413 100644 --- a/decidim-core/config/locales/fi-plain.yml +++ b/decidim-core/config/locales/fi-plain.yml @@ -2034,6 +2034,8 @@ fi-pl: profile: Oma käyttäjätili public_profile: Oma julkinen profiili title: Profiilin linkit + unread_conversations: Sinulla on lukemattomia keskusteluja + unread_notifications: Sinulla on lukemattomia ilmoituksia user_profile: account: Käyttäjätili authorizations: Varmennukset diff --git a/decidim-core/config/locales/fi.yml b/decidim-core/config/locales/fi.yml index 639f204411e5a..ce6b2441c6758 100644 --- a/decidim-core/config/locales/fi.yml +++ b/decidim-core/config/locales/fi.yml @@ -1500,6 +1500,7 @@ fi: searches: filters: jump_to: 'Siirry kohtaan:' + resource: "%{label} kohteesta %{collection}" search: Hae state: active: Aktiiviset @@ -2033,6 +2034,8 @@ fi: profile: Oma käyttäjätili public_profile: Oma julkinen profiili title: Profiilin linkit + unread_conversations: Sinulla on lukemattomia keskusteluja + unread_notifications: Sinulla on lukemattomia ilmoituksia user_profile: account: Käyttäjätili authorizations: Vahvistukset diff --git a/decidim-core/config/locales/fr-CA.yml b/decidim-core/config/locales/fr-CA.yml index 2c01c1ccf64b3..d65840ccc4261 100644 --- a/decidim-core/config/locales/fr-CA.yml +++ b/decidim-core/config/locales/fr-CA.yml @@ -2035,6 +2035,8 @@ fr-CA: profile: Mon compte public_profile: Mon profil public title: Liens du profil + unread_conversations: Vous avez des conversations non lues + unread_notifications: Vous avez des notifications non lues user_profile: account: Compte authorizations: Autorisations diff --git a/decidim-core/config/locales/fr.yml b/decidim-core/config/locales/fr.yml index 8b66905729464..1ae6b89be7ca1 100644 --- a/decidim-core/config/locales/fr.yml +++ b/decidim-core/config/locales/fr.yml @@ -2035,6 +2035,8 @@ fr: profile: Mon compte public_profile: Mon profil public title: Liens du profil + unread_conversations: Vous avez des conversations non lues + unread_notifications: Vous avez des notifications non lues user_profile: account: Compte authorizations: Autorisations diff --git a/decidim-core/config/locales/it.yml b/decidim-core/config/locales/it.yml index 7cf189a5f70dd..38158b5ee7c7a 100644 --- a/decidim-core/config/locales/it.yml +++ b/decidim-core/config/locales/it.yml @@ -4,10 +4,19 @@ it: attributes: account: delete_reason: Motivo per il quale vuoi eliminare il tuo account + attachment: + documents: Documenti + image: Immagine + photos: Foto common: created_at: Creato il + content_block_attachment: + background_image: Immagine di sfondo + main_image: Immagine principale conversation: body: Testo + editor_image: + file: File group: about: Dettagli avatar: Immagine del profilo @@ -16,35 +25,56 @@ it: name: Nome nickname: Pseudonimo phone: Telefono + import: + file: File message: body: Testo report: details: Ulteriori commenti user: about: Di + avatar: Immagine del profilo email: La tua email + encrypted_password: Password + locale: Localizzazione name: Nome utente nickname: Nickname + old_password: Password corrente password: Password password_confirmation: Conferma la tua password personal_url: Pagina personale remove_avatar: Rimuovi avatar + tos_agreement: Condizioni contrattuali di servizio user_group: avatar: Immagine del profilo + errors: + models: + user: + attributes: + nickname: + format: Il nickname deve essere minuscolo e non contenere spazi models: decidim/attachment_created_event: Allegato decidim/component_published_event: Componente attivo decidim/demoted_membership: Non è più un amministratore di gruppo decidim/gamification/badge_earned_event: Badge guadagnato + decidim/gamification/level_up_event: Hai raggiunto il livello + decidim/invited_to_group_event: decidim/join_request_accepted_event: Richiesta di adesione accettata decidim/join_request_rejected_event: Richiesta di adesione rifiutata decidim/profile_updated_event: profilo aggiornato decidim/promote_to_admin: Promosso ad amministratore del gruppo + decidim/promoted_to_admin_event: Promosso a amministratore di gruppo decidim/removed_from_group: Rimosso dal gruppo decidim/resource_endorsed_event: Risorsa approvata + decidim/resource_hidden_event: Risorsa nascosta decidim/user_group_created_event: Gruppo utenti creato + decidim/welcome_notification_event: Messaggio di benvenuto activerecord: attributes: + decidim/action_log: + created_at: Ora + with_participatory_space: Spazio partecipativo decidim/user: current_password: Password attuale email: Email @@ -74,10 +104,32 @@ it: select: Selezionare formats: decidim_short: "%d/%m/%Y" + decidim_short_dashed: "%d-%m-%Y" decidim_short_with_month_name_short: "%d %b %Y" decidim_with_day_and_month_name: "%A %d %b %Y" decidim_with_month_name: "%d %B %Y" decidim_with_month_name_short: "%d %b" + help: + date_format: 'Formato: gg/mm/aaaa' + order: d-m-y + separator: "/" + datetime: + distance_in_words: + about_x_hours: + one: circa 1 ora + other: circa %{count} ore + half_a_minute: mezzo minuto + short: + about_x_days: "%{count} giorni" + about_x_hours: "%{count} ore" + about_x_minutes: "%{count} minuti" + about_x_years: "%{count} anni" + almost_x_years: "%{count} anni" + half_a_minute: 1m + less_than_x_minutes: "%{count} minuti" + less_than_x_seconds: "%{count} secondi" + over_x_years: "%{count} anni" + x_days: "%{count} giorni" decidim: accessibility: external_link: Collegamento esterno @@ -110,11 +162,27 @@ it: create: "%{user_name} ha creato l'area %{resource_name}" delete: "%{user_name} ha eliminato l'area %{resource_name}" update: "%{user_name} ha aggiornato l'area %{resource_name}" + attachment_collection: + delete: "%{user_name} ha eliminato la collezione di allegati %{resource_name}" + update: "%{user_name} ha aggiornato la collezione di allegati %{resource_name}" + category: + create: "%{user_name} ha aggiunto la categoria %{resource_name} allo spazio %{space_name}" + delete: "%{user_name} ha eliminato la categoria %{resource_name} dallo spazio %{space_name}" + update: "%{user_name} ha aggiornato la categoria %{resource_name} nello spazio %{space_name}" component: create: "%{user_name} ha aggiunto il componente %{resource_name} allo spazio %{space_name}" delete: "%{user_name} rimosso il componente %{resource_name} dallo spazio %{space_name}" + export_component: "%{user_name} ha esportato l' %{resource_name} %{component_name} in %{space_name} come %{format_name}" publish: "%{user_name} ha pubblicato il componente %{resource_name} nello spazio %{space_name}" unpublish: "%{user_name} non pubblicato il componente %{resource_name} dallo spazio %{space_name}" + update_permissions_with_space: "%{user_name} ha aggiornato i permessi di %{resource_name} in %{space_name}" + contextual_help_section: + update: "%{user_name} ha aggiornato la sezione di aiuto %{resource_name}" + helpers: + answers: risposte + comments: commenti + projects: progetti + results: resultati impersonation_log: manage: "%{user_name} è stato moderato %{resource_name} perché %{reason}" moderation: @@ -131,6 +199,7 @@ it: update: "%{user_name} ha aggiornato l'applicazione OAuth %{resource_name}" organization: update: "%{user_name} aggiornato le impostazioni dell'organizzazione" + update_external_domain: "%{user_name} ha aggiornato i domini esterni dell'organizzazione" participatory_space_private_user: create: "%{user_name} invitato %{resource_name} a essere un utente privato" create_via_csv: "%{user_name} ha invitato %{resource_name} tramite csv a unirsi come partecipante privato" @@ -142,6 +211,10 @@ it: delete_with_parent: "%{user_name} eliminato l'ambito %{resource_name} all'interno dell'ambito %{parent_scope}" update: "%{user_name} ha aggiornato l'ambito %{resource_name}" update_with_parent: "%{user_name} ha aggiornato l'ambito %{resource_name} all'interno dell'ambito %{parent_scope}" + scope_type: + create: "%{user_name} ha creato il tipo d'ambito %{resource_name}" + delete: "%{user_name} ha eliminato il tipo di ambito %{resource_name}" + update: "%{user_name} ha aggiornato il tipo di ambito %{resource_name}" static_page: create: "%{user_name} creato il %{resource_name} pagina statica" delete: "%{user_name} cancellato il %{resource_name} pagina statica" @@ -157,11 +230,15 @@ it: unblock: "%{user_name} ha sbloccato l'utente %{resource_name}" unofficialize: "%{user_name} utente non ufficiale %{resource_name}" user_group: + block: "%{user_name} ha bloccato il gruppo utente %{resource_name}" reject: "%{user_name} respinto il %{resource_name} verifica del gruppo di utenti" + unblock: "%{user_name} ha bloccato il gruppo utente %{resource_name}" verify: "%{user_name} verificato il %{resource_name} gruppo di utenti" verify_via_csv: "%{user_name} verificato il %{resource_name} gruppo di utenti tramite un file CSV" user_moderation: unreport: "%{user_name} ha de-segnalato un %{resource_type} - %{unreported_user_name}" + admin_terms_of_service: + default_body: "

TERMINI DI UTILIZZO PER L'AMMINISTRATORE

Ci auguriamo che tu abbia ricevuto la usuale raccomandazione dall'amministratore di sistema. Solitamente si riduce a queste tre cose:

  1. Rispetta la privacy degli altri.
  2. Pensa prima di cliccare.
  3. Da grande potenzialità derivano grandi responsabilità.
" alert: dismiss: Rimuovi notifica amendments: @@ -173,6 +250,7 @@ it: error: Si è verificato un errore durante la modifica di questa risorsa. promote_button: Promuovi a %{model_name} promote_confirm_text: Sei sicuro di voler promuovere questa emendazione? + promote_help_text: Puoi promuovere questa modifica e pubblicarla come %{model_name} indipendente. created: error: Si è verificato un errore durante la creazione della proposta di emendamento. success: La proposta di emendamento è stata creata con successo. @@ -658,37 +736,70 @@ it: group_admins: actions: demote_admin: Rimuovi l'amministratore + demote: + error: Si è verificato un errore durante la rimozione di questo utente dall'elenco degli amministratori. + success: Partecipante rimosso correttamente dall'elenco degli amministratori. index: current_admins: 'Amministratori attuali:' manage_admins: Gestisci amministratori group_invites: + accept: + error: Si è verificato un errore durante l'accettazione di questo invito. + success: Invito accettato con successo. accept_invitation: Accettato accept_or_reject_group_invitations: 'I seguenti gruppi ti hanno invitato a unirti a loro. Accetta o rifiuta le loro richieste:' index: invite: Invitare invite_user: Invita un utente + invite: + error: Si è verificato un problema nell'invitare questo partecipante. + success: Partecipante invitato con successo. + pending_invitations: Richieste in sospeso + reject: + error: Si è verificato un errore nel rifiutare questo invito. + success: Invito rifiutato con successo. reject_invitation: Rifiutato group_members: + accept: + error: Si è verificato un errore accettando questa richiesta di iscrizione. + success: Richiesta di iscrizione accettata con successo. actions: promote_to_admin: Rendere amministratore remove_from_group: Rimuovi utente index: current_members_without_admins: 'Membri attuali (senza amministratori):' manage_members: Gestisci i membri + promote: + error: Si è verificato un errore nel promuovere questo partecipante. + success: Partecipante promosso con successo. + reject: + error: Si è verificato un problema rifiutando questa richiesta di iscrizione. + success: Richiesta di iscrizione rifiutata con successo. + remove: + error: Si è verificato un errore durante la rimozione di questo utente dal gruppo. + success: Partecipante rimosso con successo dal gruppo. groups: actions: are_you_sure: Sei sicuro? + create: + error: Si è verificato un errore durante la creazione del gruppo. + success: Gruppo creato con successo. edit: edit_user_group: Modifica gruppo update_user_group: Gruppo di aggiornamento form: + document_number_help: Non usare trattini né spazi. email_help: Email della tua organizzazione, associazione, collettivo, gruppo, ecc. fill_in_for_verification: 'Compila questi campi se vuoi che il tuo gruppo sia verificato:' name_help: Nome della tua organizzazione, associazione, collettivo, gruppo, ecc. nickname_help: Nome utente della propria organizzazione, associazione, collettivo, gruppo, ecc. Non utilizzare spazi né accenti. + phone_help: Non usare trattini né spazi. join: + error: Si è verificato un problema ad unirsi al gruppo. success: Richiesta di unione creata con successo. Un amministratore esaminerà la tua richiesta prima di accettarti nel gruppo. leave: + error: Si è verificato un problema lasciando il gruppo. + last_admin: Non puoi rimuovere te stesso da questo gruppo perché sei l'ultimo amministratore. Rendi un altro membro un amministratore per lasciare il gruppo. success: Gruppo lasciato con successo. members: accept_or_reject_join_requests: 'I seguenti utenti hanno chiesto di unirsi a questo gruppo. Accetta o rifiuta le loro richieste:' @@ -698,9 +809,13 @@ it: create_user_group: Crea un gruppo new_user_group: Nuovo gruppo subtitle: Crea un gruppo e invita altri utenti a partecipare per partecipare a un livello collettivo. + no_user_groups: Non appartiene ancora a nessun gruppo. roles: admin: Amministratore member: Membro + update: + error: Si è verificato un errore durante l'aggiornamento del gruppo. + success: Gruppo aggiornato con successo. help: main_topic: default_page: @@ -746,6 +861,8 @@ it: static: latlng_text: 'latitudine: %{latitude}, longitudine: %{longitude}' map_service_brand: Mappa OpenStreet + members: + no_members: Questo gruppo non ha ancora membri. menu: help: Aiuto home: Home @@ -913,6 +1030,7 @@ it: profile: deleted: Utente eliminato profiles: + default_officialization_text_for_user_groups: Questo gruppo è verificato pubblicamente, il suo nome è stato verificato per corrispondere al suo vero nome. show: activity: Attività badges: badge @@ -933,9 +1051,12 @@ it: invite_user: Invita utente join_user_group: Richiesta di iscrizione al gruppo leave_user_group: Lascia il gruppo + manage_user_group: Gestisci gruppo manage_user_group_admins: Gestisci amministratori manage_user_group_users: Gestisci i membri resend_email_confirmation_instructions: Rispedire le istruzioni per la conferma dell'email + confirmation_instructions_sent: Istruzioni di conferma e-mail inviate. + fill_in_email_to_confirm_it: Per favore, inserisci l'email del tuo gruppo per confermarlo. reported_mailer: report: authors: Autori @@ -1063,6 +1184,7 @@ it: user_contact_disabled: Questo partecipante non accetta messaggi diretti. user_conversations: create: + error: Impossibile creare il messaggio. Riprova più tardi. existing_error: Il messaggio non può essere creato. La conversazione esiste già, riprova ora. success: Conversazione iniziata con successo! index: @@ -1077,6 +1199,8 @@ it: user_interests: show: my_interests: I miei interessi + no_scopes: Questa organizzazione non ha ancora alcun scopo! + select_your_interests: Seleziona gli argomenti a cui sei interessato per ricevere gli eventi correlati nella scheda Timeline del tuo profilo. update_my_interests: Aggiorna i miei interessi update: error: Si è verificato un errore durante l'aggiornamento dei tuoi interessi. diff --git a/decidim-core/config/locales/ja.yml b/decidim-core/config/locales/ja.yml index 84da730ce3459..3c2586e4af147 100644 --- a/decidim-core/config/locales/ja.yml +++ b/decidim-core/config/locales/ja.yml @@ -1482,6 +1482,7 @@ ja: searches: filters: jump_to: 'ジャンプ先:' + resource: "%{collection} の中の %{label}" search: 検索 state: active: アクティブ @@ -2011,6 +2012,8 @@ ja: profile: マイアカウント public_profile: 公開プロフィール title: プロフィールリンク + unread_conversations: 未読の会話があります + unread_notifications: 未読の通知があります user_profile: account: アカウント authorizations: 権限 diff --git a/decidim-core/config/locales/sk.yml b/decidim-core/config/locales/sk.yml index d98f466dd5aef..cf1eae9159e2e 100644 --- a/decidim-core/config/locales/sk.yml +++ b/decidim-core/config/locales/sk.yml @@ -962,6 +962,8 @@ sk: change_my_password: Zmeňte heslo confirm_new_password: Potvrďte nové heslo new_password: Nové heslo + password_help: "%{minimum_characters} je minimum znakov, nesmie byť príliš bežné (napr. 123456) a musí byť iné ako vaše prezývka a váš e-mail." + password_help_admin: "%{minimum_characters} je minimum znakov, nesmie byť príliš bežné (napr. 123456) a musí byť iné ako vaše prezývka a váš e-mail." new: forgot_your_password: Zabudli ste heslo? send_me_reset_password_instructions: Pošlite mi pokyny na resetovanie hesla diff --git a/decidim-core/config/locales/sv.yml b/decidim-core/config/locales/sv.yml index 1d2998d83259d..539934510a247 100644 --- a/decidim-core/config/locales/sv.yml +++ b/decidim-core/config/locales/sv.yml @@ -1473,6 +1473,7 @@ sv: searches: filters: jump_to: 'Gå till:' + resource: "%{label} bland %{collection}" search: Sök state: active: Pågående @@ -1999,6 +2000,8 @@ sv: profile: Mitt konto public_profile: Min offentliga profil title: Profillänkar + unread_conversations: Du har olästa konversationer + unread_notifications: Du har olästa meddelanden user_profile: account: Konto authorizations: Auktoriseringar From 10c775df6abe244a5f1de067131826720bd03c11 Mon Sep 17 00:00:00 2001 From: decidim-bot Date: Tue, 12 Aug 2025 08:20:47 +0200 Subject: [PATCH 14/61] Fixed proposal creation for admin when "Participants can create proposals" is unchecked (#15050) Co-authored-by: andra-panaite <135139066+andra-panaite@users.noreply.github.com> Co-authored-by: Tom Greenwood <101816158+greenwoodt@users.noreply.github.com> --- .../permissions/decidim/proposals/admin/permissions.rb | 8 ++++---- .../decidim/proposals/admin/permissions_spec.rb | 8 ++++++++ .../spec/shared/manage_proposals_examples.rb | 1 + 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/decidim-proposals/app/permissions/decidim/proposals/admin/permissions.rb b/decidim-proposals/app/permissions/decidim/proposals/admin/permissions.rb index 8581ecd0caf35..4cd24c6775b2f 100644 --- a/decidim-proposals/app/permissions/decidim/proposals/admin/permissions.rb +++ b/decidim-proposals/app/permissions/decidim/proposals/admin/permissions.rb @@ -101,8 +101,7 @@ def valuator_assigned_to_proposal? end def admin_creation_is_enabled? - current_settings.try(:creation_enabled?) && - component_settings.try(:official_proposals_enabled) + component_settings.try(:official_proposals_enabled) end def admin_edition_is_available? @@ -134,9 +133,10 @@ def can_create_proposal_note? # corresponding setting is enabled. # This setting is incompatible with participatory texts. def can_create_proposal_from_admin? - return disallow! if participatory_texts_are_enabled? && permission_action.subject == :proposal + return unless permission_action.subject == :proposal + return disallow! if participatory_texts_are_enabled? - toggle_allow(admin_creation_is_enabled?) if permission_action.subject == :proposal + toggle_allow(admin_creation_is_enabled?) end # Proposals can only be answered from the admin when the diff --git a/decidim-proposals/spec/permissions/decidim/proposals/admin/permissions_spec.rb b/decidim-proposals/spec/permissions/decidim/proposals/admin/permissions_spec.rb index 16232349c1e4a..32f3773662996 100644 --- a/decidim-proposals/spec/permissions/decidim/proposals/admin/permissions_spec.rb +++ b/decidim-proposals/spec/permissions/decidim/proposals/admin/permissions_spec.rb @@ -118,10 +118,18 @@ context "when creation is disabled" do let(:creation_enabled?) { false } + let(:official_proposals_enabled?) { false } it { is_expected.to be false } end + context "when official proposals are enabled and participants can create proposals is disabled" do + let(:official_proposals_enabled?) { true } + let(:creation_enabled?) { false } + + it { is_expected.to be true } + end + context "when official proposals are disabled" do let(:official_proposals_enabled?) { false } diff --git a/decidim-proposals/spec/shared/manage_proposals_examples.rb b/decidim-proposals/spec/shared/manage_proposals_examples.rb index 61236823b7b64..84380ea6121de 100644 --- a/decidim-proposals/spec/shared/manage_proposals_examples.rb +++ b/decidim-proposals/spec/shared/manage_proposals_examples.rb @@ -290,6 +290,7 @@ context "when creation is not enabled" do before do current_component.update!( + settings: { official_proposals_enabled: false }, step_settings: { current_component.participatory_space.active_step.id => { creation_enabled: false From fea0bf84991e819a7c1e7d6bef86328e23149c5a Mon Sep 17 00:00:00 2001 From: Alexandru Emil Lupu Date: Wed, 20 Aug 2025 15:01:08 +0300 Subject: [PATCH 15/61] Backport 'Upgrade Chrome and ChromeDriver to 139.0.7258.66' to v0.29 (#15072) --- .github/workflows/test_app.yml | 2 +- .../headers/browser_feature_permissions.rb | 50 ++++++ .../decidim/application_controller.rb | 1 + decidim-core/app/packs/src/decidim/confirm.js | 152 +++++++++--------- .../app/packs/src/decidim/form_remote.js | 2 +- .../app/packs/src/decidim/impersonation.js | 2 +- decidim-core/app/packs/src/decidim/index.js | 5 +- .../packs/src/decidim/session_timeouter.js | 2 +- .../app/packs/src/decidim/utilities/dom.js | 148 +++++++++++++++++ .../decidim/_js_configuration.html.erb | 1 + decidim-core/config/locales/en.yml | 1 + .../rspec_support/confirmation_helpers.rb | 8 +- .../app/packs/src/decidim/forms/forms.js | 40 ++--- 13 files changed, 314 insertions(+), 100 deletions(-) create mode 100644 decidim-core/app/controllers/concerns/decidim/headers/browser_feature_permissions.rb create mode 100644 decidim-core/app/packs/src/decidim/utilities/dom.js diff --git a/.github/workflows/test_app.yml b/.github/workflows/test_app.yml index e809bbbd79d92..38fe9263d4b70 100644 --- a/.github/workflows/test_app.yml +++ b/.github/workflows/test_app.yml @@ -41,7 +41,7 @@ on: chrome_version: description: 'Chrome & Chromedriver version' required: false - default: "126.0.6478.182" + default: "139.0.7258.66" type: string jobs: diff --git a/decidim-core/app/controllers/concerns/decidim/headers/browser_feature_permissions.rb b/decidim-core/app/controllers/concerns/decidim/headers/browser_feature_permissions.rb new file mode 100644 index 0000000000000..0c94e3b66f7dc --- /dev/null +++ b/decidim-core/app/controllers/concerns/decidim/headers/browser_feature_permissions.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "active_support/concern" + +module Decidim + module Headers + # This module controls the "Permissions-Policy" header to define the + # specific sets of browser features that the website is able to use. + module BrowserFeaturePermissions + extend ActiveSupport::Concern + + included do + after_action :define_permissions_policy + end + + private + + def define_permissions_policy + return if response.media_type != "text/html" + return if response.headers["Permissions-Policy"].present? + + # Allow the "unload" and "onbeforeunload" events to be used at the + # current domain to prevent the user unintentionally changing the page + # when they have something important to do on the page, such as an + # unsaved form. + # + # This header is required because Chrome is phasing this event out due + # to some performance issues with the back/forward cache feature of the + # browser. However, currently there are no alternative events that would + # allow preventing accidental page reloads, tab closing or window + # closing. + # + # For further information, see: + # https://developer.chrome.com/docs/web-platform/deprecating-unload + # https://github.com/fergald/docs/blob/master/explainers/permissions-policy-unload.md + # + # Note that even Google suggests using the "beforeunload" for this + # particular use case: + # https://developer.chrome.com/docs/web-platform/page-lifecycle-api#events + # + # beforeunload + # Important: the beforeunload event should only be used to alert the + # user of unsaved changes. Once those changes are saved, the event + # should be removed. It should never be added unconditionally to the + # page, as doing so can hurt performance in some cases. + response.headers["Permissions-Policy"] = "unload=(self)" + end + end + end +end diff --git a/decidim-core/app/controllers/decidim/application_controller.rb b/decidim-core/app/controllers/decidim/application_controller.rb index 40bd3c53423eb..8b8cc44b564a2 100644 --- a/decidim-core/app/controllers/decidim/application_controller.rb +++ b/decidim-core/app/controllers/decidim/application_controller.rb @@ -16,6 +16,7 @@ class ApplicationController < ::DecidimController include NeedsTosAccepted include Headers::HttpCachingDisabler include Headers::ContentSecurityPolicy + include Headers::BrowserFeaturePermissions include ActionAuthorization include ForceAuthentication include SafeRedirect diff --git a/decidim-core/app/packs/src/decidim/confirm.js b/decidim-core/app/packs/src/decidim/confirm.js index 7304dbafca93a..f1ddfd3ca8ded 100644 --- a/decidim-core/app/packs/src/decidim/confirm.js +++ b/decidim-core/app/packs/src/decidim/confirm.js @@ -5,29 +5,17 @@ * it to gain control over the confirm events BEFORE rails-ujs is loaded. */ -import Rails from "@rails/ujs" +const { Rails } = window; class ConfirmDialog { constructor(sourceElement) { this.$modal = $("#confirm-modal"); - this.$content = $("[data-confirm-modal-content]", this.$modal); - this.$title = $("[data-dialog-title]", this.$modal); - this.$buttonConfirm = $("[data-confirm-ok]", this.$modal); - this.$buttonCancel = $("[data-confirm-cancel]", this.$modal); - if (sourceElement) { this.$source = $(sourceElement); - - const confirmTitle = this.$source.data("confirm-title"); - const confirmButton = this.$source.data("confirm-button"); - - this.$title.text(confirmTitle) - - const $buttonSpan = this.$buttonConfirm.find("span"); - if ($buttonSpan.length > 0) { - $buttonSpan.text(confirmButton); - } } + this.$content = $("[data-confirm-modal-content]", this.$modal); + this.$buttonConfirm = $("[data-confirm-ok]", this.$modal); + this.$buttonCancel = $("[data-confirm-cancel]", this.$modal); window.Decidim.currentDialogs["confirm-modal"].open() } @@ -43,22 +31,37 @@ class ConfirmDialog { this.$buttonConfirm.on("click", (ev) => { ev.preventDefault(); - window.Decidim.currentDialogs["confirm-modal"].close() - resolve(true); - this.$source.focus(); + this.close(() => resolve(true)); }); this.$buttonCancel.on("click", (ev) => { ev.preventDefault(); - window.Decidim.currentDialogs["confirm-modal"].close() - resolve(false); - this.$source.focus(); + this.close(() => resolve(false)); }); }); } + + close(afterClose) { + window.Decidim.currentDialogs["confirm-modal"].close() + afterClose(); + if (this.$source) { + this.$source.focus(); + } + } } +const runConfirm = (message, sourceElement = null) => new Promise((resolve) => { + const dialog = new ConfirmDialog(sourceElement); + dialog.confirm(message).then((answer) => { + let completed = true; + if (sourceElement) { + completed = Rails.fire(sourceElement, "confirm:complete", [answer]); + } + resolve(answer && completed); + }); +}); + // Override the default confirm dialog by Rails // See: // https://github.com/rails/rails/blob/fba1064153d8e2f4654df7762a7d3664b93e9fc8/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee @@ -78,37 +81,35 @@ const allowAction = (ev, element) => { return false; } - const dialog = new ConfirmDialog( - $(element) - ); - dialog.confirm(message).then((answer) => { - const completed = Rails.fire(element, "confirm:complete", [answer]); - if (answer && completed) { - // Allow the event to propagate normally and re-dispatch it without - // the confirm data attribute which the Rails internal method is - // checking. - $(element).data("confirm", null); - $(element).removeAttr("data-confirm"); - - // The submit button click events will not do anything if they are - // dispatched as is. In these cases, just submit the underlying form. - if (ev.type === "click" && - ( - $(element).is('button[type="submit"]') || - $(element).is('input[type="submit"]') - ) - ) { - $(element).parents("form").submit(); - } else { - let origEv = ev.originalEvent || ev; - let newEv = origEv; - if (typeof Event === "function") { - // Clone the event because otherwise some click events may not - // work properly when re-dispatched. - newEv = new origEv.constructor(origEv.type, origEv); - } - ev.target.dispatchEvent(newEv); + runConfirm(message, element).then((answer) => { + if (!answer) { + return; + } + + // Allow the event to propagate normally and re-dispatch it without + // the confirm data attribute which the Rails internal method is + // checking. + $(element).data("confirm", null); + $(element).removeAttr("data-confirm"); + + // The submit button click events will not do anything if they are + // dispatched as is. In these cases, just submit the underlying form. + if (ev.type === "click" && + ( + $(element).is('button[type="submit"]') || + $(element).is('input[type="submit"]') + ) + ) { + $(element).parents("form").submit(); + } else { + let origEv = ev.originalEvent || ev; + let newEv = origEv; + if (typeof Event === "function") { + // Clone the event because otherwise some click events may not + // work properly when re-dispatched. + newEv = new origEv.constructor(origEv.type, origEv); } + ev.target.dispatchEvent(newEv); } }); @@ -143,26 +144,31 @@ const handleDocumentEvent = (ev, matchSelectors) => { }); }; -document.addEventListener("click", (ev) => { - return handleDocumentEvent(ev, [ - Rails.linkClickSelector, - Rails.buttonClickSelector, - Rails.formInputClickSelector - ]); -}); -document.addEventListener("change", (ev) => { - return handleDocumentEvent(ev, [Rails.inputChangeSelector]); -}); -document.addEventListener("submit", (ev) => { - return handleDocumentEvent(ev, [Rails.formSubmitSelector]); -}); +// Note that this needs to be run **before** Rails.start() +export const initializeConfirm = () => { + document.addEventListener("click", (ev) => { + return handleDocumentEvent(ev, [ + Rails.linkClickSelector, + Rails.buttonClickSelector, + Rails.formInputClickSelector + ]); + }); + document.addEventListener("change", (ev) => { + return handleDocumentEvent(ev, [Rails.inputChangeSelector]); + }); + document.addEventListener("submit", (ev) => { + return handleDocumentEvent(ev, [Rails.formSubmitSelector]); + }); -// This is needed for the confirm dialog to work with Foundation Abide. -// Abide registers its own submit click listeners since Foundation 5.6.x -// which will be handled before the document listeners above. This would -// break the custom confirm functionality when used with Foundation Abide. -document.addEventListener("DOMContentLoaded", function() { - $(Rails.formInputClickSelector).on("click.confirm", (ev) => { - handleConfirm(ev, getMatchingEventTarget(ev, Rails.formInputClickSelector)); + // This is needed for the confirm dialog to work with Foundation Abide. + // Abide registers its own submit click listeners since Foundation 5.6.x + // which will be handled before the document listeners above. This would + // break the custom confirm functionality when used with Foundation Abide. + document.addEventListener("DOMContentLoaded", function() { + $(Rails.formInputClickSelector).on("click.confirm", (ev) => { + handleConfirm(ev, getMatchingEventTarget(ev, Rails.formInputClickSelector)); + }); }); -}); +}; + +export default runConfirm; diff --git a/decidim-core/app/packs/src/decidim/form_remote.js b/decidim-core/app/packs/src/decidim/form_remote.js index 40c5be91e958d..36fa646abf164 100644 --- a/decidim-core/app/packs/src/decidim/form_remote.js +++ b/decidim-core/app/packs/src/decidim/form_remote.js @@ -1,4 +1,4 @@ -import Rails from "@rails/ujs"; +const { Rails } = window; // Make the remote form submit buttons disabled when the form is being // submitted to avoid multiple submits. diff --git a/decidim-core/app/packs/src/decidim/impersonation.js b/decidim-core/app/packs/src/decidim/impersonation.js index d3de232db77df..d9ddd1ee503c7 100644 --- a/decidim-core/app/packs/src/decidim/impersonation.js +++ b/decidim-core/app/packs/src/decidim/impersonation.js @@ -15,7 +15,7 @@ $(() => { }, 1000); // Prevent reload when page is already unloading, otherwise it may cause infinite reloads. - window.addEventListener("beforeunload", () => { + window.addEventListener("pagehide", () => { clearInterval(exitInterval); return; }); diff --git a/decidim-core/app/packs/src/decidim/index.js b/decidim-core/app/packs/src/decidim/index.js index 9d50979b7912a..7785edbf2e199 100644 --- a/decidim-core/app/packs/src/decidim/index.js +++ b/decidim-core/app/packs/src/decidim/index.js @@ -41,7 +41,6 @@ import "src/decidim/vizzs" import "src/decidim/responsive_horizontal_tabs" import "src/decidim/security/selfxss_warning" import "src/decidim/session_timeouter" -import "src/decidim/confirm" import "src/decidim/results_listing" import "src/decidim/impersonation" import "src/decidim/gallery" @@ -54,6 +53,7 @@ import "src/decidim/attachments" import "src/decidim/dropdown_menu" // local deps that require initialization +import ConfirmDialog, { initializeConfirm } from "src/decidim/confirm" import formDatePicker from "src/decidim/datepicker/form_datepicker" import Configuration from "src/decidim/configuration" import ExternalLink from "src/decidim/external_link" @@ -92,6 +92,7 @@ window.Decidim = window.Decidim || { addInputEmoji, EmojiButton, Dialogs, + ConfirmDialog, announceForScreenReader }; @@ -124,6 +125,8 @@ window.initFoundation = (element) => { }); }; +// Confirm initialization needs to happen before Rails.start() +initializeConfirm(); Rails.start() /** diff --git a/decidim-core/app/packs/src/decidim/session_timeouter.js b/decidim-core/app/packs/src/decidim/session_timeouter.js index 051d2f6dc2238..e4a6bc0f5c9c2 100644 --- a/decidim-core/app/packs/src/decidim/session_timeouter.js +++ b/decidim-core/app/packs/src/decidim/session_timeouter.js @@ -127,7 +127,7 @@ $(() => { setTimer(timeoutInSeconds); }); - window.addEventListener("beforeunload", () => { + window.addEventListener("pagehide", () => { clearInterval(exitInterval); return; }); diff --git a/decidim-core/app/packs/src/decidim/utilities/dom.js b/decidim-core/app/packs/src/decidim/utilities/dom.js new file mode 100644 index 0000000000000..c272c19e17bc9 --- /dev/null +++ b/decidim-core/app/packs/src/decidim/utilities/dom.js @@ -0,0 +1,148 @@ +import confirmAction from "src/decidim/confirm" +import { getMessages } from "src/decidim/i18n" + +const { Rails } = window; + +const createUnloadPreventer = () => { + const preventUnloadConditions = []; + + const confirmMessage = getMessages("confirmUnload") || "Are you sure you want to leave this page?"; + + const canUnload = (event) => !preventUnloadConditions.some((condition) => condition(event)); + + // TLDR: + // The beforeunload event does not work during tests due to the deprecation of + // the unload event and ChromeDriver automatically accepting these dialogs. + // --- + // + // Even when there are custom listeners on links and forms, the beforeunload + // event is to ensure that the user does not accidentally reload the page or + // close the browser or the tab. Note that this does not work during the tests + // with ChromeDriver due to the deprecation of the unload event and + // ChromeDriver automatically accepting these dialogs. For the time being, + // this should work when a real user interacts with the browser along with the + // "Permissions-Policy" header set by the backend. For more information about + // the header, see Decidim::Headers::BrowserFeaturePermissions). + const unloadListener = (event) => { + if (canUnload(event)) { + return; + } + + // According to: + // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event + // + // > [...] best practice is to trigger the dialog by invoking + // > preventDefault() on the event object, while also setting returnValue to + // > support legacy cases. + event.preventDefault(); + event.returnValue = true; + }; + + // The beforeunload event listener has to be registered AFTER a user + // interaction which is why it is wrapped around the next click event that + // happens after the first unload listener was registered. Otherwise it might + // not work due to the deprecation of the unload APIs in Chromium based + // browsers and possibly in the web standards in the future. + // + // According to: + // https://developer.chrome.com/docs/web-platform/page-lifecycle-api#the_beforeunload_event + // + // > Never add a beforeunload listener unconditionally or use it as an + // > end-of-session signal. Only add it when a user has unsaved work, and + // > remove it as soon as that work has been saved. + const registerBeforeUnload = () => { + window.removeEventListener("click", registerBeforeUnload); + window.addEventListener("beforeunload", unloadListener); + }; + + const disableBeforeUnload = () => { + window.removeEventListener("click", registerBeforeUnload); + window.removeEventListener("beforeunload", unloadListener); + }; + + const linkClickListener = (ev) => { + const link = ev.target?.closest("a"); + if (!link) { + return; + } + + if (canUnload(ev)) { + disableBeforeUnload(); + document.removeEventListener("click", linkClickListener); + return; + } + + window.exitUrl = link.href; + + ev.preventDefault(); + ev.stopPropagation(); + + confirmAction(confirmMessage, link).then((answer) => { + if (!answer) { + return; + } + + disableBeforeUnload(); + document.removeEventListener("click", linkClickListener); + link.click(); + }); + }; + + const formSubmitListener = (ev) => { + const source = ev.target?.closest("form"); + if (!source) { + return; + } + + if (canUnload(ev)) { + disableBeforeUnload(); + document.removeEventListener("submit", formSubmitListener); + return; + } + + const button = source.closest(Rails.formSubmitSelector); + if (!button) { + return; + } + + ev.preventDefault(); + ev.stopImmediatePropagation(); + ev.stopPropagation(); + + confirmAction(confirmMessage, button).then((answer) => { + if (!answer) { + return; + } + + disableBeforeUnload(); + document.removeEventListener("submit", formSubmitListener); + source.submit(); + }); + }; + + const registerPreventUnloadListeners = () => { + window.addEventListener("click", registerBeforeUnload); + document.addEventListener("click", linkClickListener); + document.addEventListener("submit", formSubmitListener); + }; + + return { + addPreventCondition: (condition) => { + if (typeof condition !== "function") { + return; + } + + if (preventUnloadConditions.length < 1) { + // The unload listeners are global, so only the first call to this + // function should result to registering these listeners. + registerPreventUnloadListeners(); + } + + preventUnloadConditions.push(condition); + } + }; +}; + +const unloadPreventer = createUnloadPreventer(); + +export const preventUnload = (condition) => unloadPreventer.addPreventCondition(condition); diff --git a/decidim-core/app/views/layouts/decidim/_js_configuration.html.erb b/decidim-core/app/views/layouts/decidim/_js_configuration.html.erb index 43b73d0ed5a92..8086da8df1f4f 100644 --- a/decidim-core/app/views/layouts/decidim/_js_configuration.html.erb +++ b/decidim-core/app/views/layouts/decidim/_js_configuration.html.erb @@ -7,6 +7,7 @@ js_configs = { "mentionsModal": { "removeRecipient": t("decidim.shared.mentions_modal.remove_recipient", name: "%name%") }, + "confirmUnload": t("decidim.shared.confirm_unload"), emojis: I18n.t("emojis").deep_transform_keys { |k| k.to_s.camelize(:lower) }, editor: I18n.t("editor"), date: I18n.t("date"), diff --git a/decidim-core/config/locales/en.yml b/decidim-core/config/locales/en.yml index ccb6bec8f2fed..81f29fadddd53 100644 --- a/decidim-core/config/locales/en.yml +++ b/decidim-core/config/locales/en.yml @@ -1530,6 +1530,7 @@ en: close_modal: Close modal ok: OK title: Confirm + confirm_unload: This page contains unsaved changes. Are you sure you want to leave this page? embed: title: Embedded video content extended_navigation_bar: diff --git a/decidim-dev/lib/decidim/dev/test/rspec_support/confirmation_helpers.rb b/decidim-dev/lib/decidim/dev/test/rspec_support/confirmation_helpers.rb index 1a01b9413f5d8..fb1473c6d185b 100644 --- a/decidim-dev/lib/decidim/dev/test/rspec_support/confirmation_helpers.rb +++ b/decidim-dev/lib/decidim/dev/test/rspec_support/confirmation_helpers.rb @@ -47,15 +47,15 @@ def dismiss_confirm(_text = nil) # Used to accept the "onbeforeunload" event's normal browser confirm modal # as this cannot be overridden. Original confirm dismiss implementation in # Capybara. - def accept_page_unload(text = nil, **options, &) - page.send(:accept_modal, :confirm, text, options, &) + def accept_page_unload(text = nil, **, &) + accept_confirm(text, &) end # Used to dismiss the "onbeforeunload" event's normal browser confirm modal # as this cannot be overridden. Original confirm dismiss implementation in # Capybara. - def dismiss_page_unload(text = nil, **options, &) - page.send(:dismiss_modal, :confirm, text, options, &) + def dismiss_page_unload(text = nil, **, &) + dismiss_confirm(text, &) end end diff --git a/decidim-forms/app/packs/src/decidim/forms/forms.js b/decidim-forms/app/packs/src/decidim/forms/forms.js index 646d748bc7272..a4dd5ad4f41df 100644 --- a/decidim-forms/app/packs/src/decidim/forms/forms.js +++ b/decidim-forms/app/packs/src/decidim/forms/forms.js @@ -11,6 +11,7 @@ import "dragula/dist/dragula.css"; import createOptionAttachedInputs from "src/decidim/forms/option_attached_inputs.component" import createDisplayConditions from "src/decidim/forms/display_conditions.component" import createMaxChoicesAlertComponent from "src/decidim/forms/max_choices_alert.component" +import { preventUnload } from "src/decidim/utilities/dom" $(() => { $(".js-radio-button-collection, .js-check-box-collection").each((idx, el) => { @@ -45,30 +46,33 @@ $(() => { }); }); - const $form = $("form.answer-questionnaire"); - if ($form.length > 0) { - $form.find("input, textarea, select").on("change", () => { - $form.data("changed", true); + const form = document.querySelector("form.answer-questionnaire"); + if (form) { + const safePath = form.dataset.safePath.split("?")[0]; + let exitUrl = ""; + document.addEventListener("click", (event) => { + const link = event.target?.closest("a"); + if (link) { + exitUrl = link.href; + } }); - const safePath = $form.data("safe-path").split("?")[0]; - $(document).on("click", "a", (event) => { - window.exitUrl = event.currentTarget.href; - }); + // The submit listener has to be registered through jQuery because the + // custom confirm dialog does not dispatch the "submit" event normally. $(document).on("submit", "form", (event) => { - window.exitUrl = event.currentTarget.action; + exitUrl = event.currentTarget.action; }); - window.addEventListener("beforeunload", (event) => { - const exitUrl = window.exitUrl; - const hasChanged = $form.data("changed"); - window.exitUrl = null; + let hasChanged = false; + const controls = form.querySelectorAll("input, textarea, select"); + const changeListener = () => { + if (!hasChanged) { + hasChanged = true; + controls.forEach((control) => control.removeEventListener("change", changeListener)); - if (!hasChanged || (exitUrl && exitUrl.includes(safePath))) { - return; + preventUnload(() => !exitUrl.includes(safePath)); } - - event.returnValue = true; - }); + }; + controls.forEach((control) => control.addEventListener("change", changeListener)); } }) From fee1e7b22d47ac2f375d695c61334b39222fa46c Mon Sep 17 00:00:00 2001 From: decidim-bot Date: Wed, 20 Aug 2025 22:49:31 +0200 Subject: [PATCH 16/61] Fix specs running on local development environments (#15069) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andrés Pereira de Lucena Co-authored-by: Alexandru Emil Lupu --- decidim-core/lib/decidim/core/test/factories.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/decidim-core/lib/decidim/core/test/factories.rb b/decidim-core/lib/decidim/core/test/factories.rb index 5d2110c2c93c8..128d9251a0bc9 100644 --- a/decidim-core/lib/decidim/core/test/factories.rb +++ b/decidim-core/lib/decidim/core/test/factories.rb @@ -127,8 +127,8 @@ def generate_localized_title(field = nil, skip_injection: false) sequence(:host) { |n| "#{n}.lvh.me" } description { generate_localized_description(:organization_description, skip_injection:) } favicon { Decidim::Dev.test_file("icon.png", "image/png") } - default_locale { Decidim.default_locale } - available_locales { Decidim.available_locales } + default_locale { "en" } + available_locales { %w(en ca es) } users_registration_mode { :enabled } official_img_footer { Decidim::Dev.test_file("avatar.jpg", "image/jpeg") } official_url { Faker::Internet.url } From 101fdf249a27f9907b85db75bc3b30cb7122c20c Mon Sep 17 00:00:00 2001 From: decidim-bot Date: Thu, 21 Aug 2025 12:01:16 +0200 Subject: [PATCH 17/61] New Crowdin updates (#15042) --- .../config/locales/bs-BA.yml | 2 ++ decidim-accountability/config/locales/de.yml | 2 +- decidim-assemblies/config/locales/ar.yml | 6 ------ decidim-assemblies/config/locales/bg.yml | 6 ------ decidim-assemblies/config/locales/cs.yml | 6 ------ decidim-assemblies/config/locales/de.yml | 2 +- decidim-assemblies/config/locales/el.yml | 6 ------ decidim-assemblies/config/locales/es-MX.yml | 6 +++--- decidim-assemblies/config/locales/es-PY.yml | 6 +++--- decidim-assemblies/config/locales/es.yml | 2 +- decidim-assemblies/config/locales/eu.yml | 6 ------ .../config/locales/fi-plain.yml | 4 ++-- decidim-assemblies/config/locales/fi.yml | 4 ++-- decidim-assemblies/config/locales/fr-CA.yml | 2 +- decidim-assemblies/config/locales/fr.yml | 2 +- decidim-assemblies/config/locales/gl.yml | 6 ------ decidim-assemblies/config/locales/he-IL.yml | 5 ----- decidim-assemblies/config/locales/hu.yml | 6 ------ decidim-assemblies/config/locales/id-ID.yml | 6 ------ decidim-assemblies/config/locales/is-IS.yml | 2 -- decidim-assemblies/config/locales/it.yml | 6 ------ decidim-assemblies/config/locales/ja.yml | 6 ------ decidim-assemblies/config/locales/lb.yml | 6 ------ decidim-assemblies/config/locales/lt.yml | 6 ------ decidim-assemblies/config/locales/lv.yml | 6 ------ decidim-assemblies/config/locales/nl.yml | 6 ------ decidim-assemblies/config/locales/no.yml | 6 ------ decidim-assemblies/config/locales/pl.yml | 6 ------ decidim-assemblies/config/locales/pt-BR.yml | 6 ------ decidim-assemblies/config/locales/pt.yml | 6 ------ decidim-assemblies/config/locales/ro-RO.yml | 6 ------ decidim-assemblies/config/locales/ru.yml | 6 ------ decidim-assemblies/config/locales/sl.yml | 6 ------ decidim-assemblies/config/locales/sq-AL.yml | 6 ------ decidim-assemblies/config/locales/sv.yml | 2 +- decidim-assemblies/config/locales/tr-TR.yml | 6 ------ decidim-assemblies/config/locales/uk.yml | 6 ------ decidim-assemblies/config/locales/zh-CN.yml | 6 ------ decidim-assemblies/config/locales/zh-TW.yml | 6 ------ decidim-conferences/config/locales/de.yml | 5 +++++ decidim-core/config/locales/ca-IT.yml | 1 + decidim-core/config/locales/ca.yml | 1 + decidim-core/config/locales/cs.yml | 4 ++++ decidim-core/config/locales/de.yml | 18 +++++++++++++++-- decidim-core/config/locales/es-MX.yml | 1 + decidim-core/config/locales/es-PY.yml | 1 + decidim-core/config/locales/es.yml | 1 + decidim-core/config/locales/eu.yml | 1 + decidim-core/config/locales/fi-plain.yml | 1 + decidim-core/config/locales/fi.yml | 1 + decidim-core/config/locales/fr-CA.yml | 1 + decidim-core/config/locales/fr.yml | 1 + decidim-core/config/locales/ja.yml | 1 + decidim-core/config/locales/pt-BR.yml | 1 + decidim-core/config/locales/sv.yml | 1 + decidim-design/config/locales/de.yml | 2 +- decidim-meetings/config/locales/de.yml | 20 +++++++++---------- .../config/locales/ar.yml | 6 ------ .../config/locales/bg.yml | 6 ------ .../config/locales/ca-IT.yml | 4 ++-- .../config/locales/ca.yml | 4 ++-- .../config/locales/cs.yml | 6 ------ .../config/locales/de.yml | 13 ++++-------- .../config/locales/el.yml | 6 ------ .../config/locales/es-MX.yml | 8 ++++---- .../config/locales/es-PY.yml | 8 ++++---- .../config/locales/es.yml | 4 ++-- .../config/locales/eu.yml | 6 ------ .../config/locales/fi-plain.yml | 6 +++--- .../config/locales/fr-CA.yml | 6 +++--- .../config/locales/fr.yml | 6 +++--- .../config/locales/gl.yml | 6 ------ .../config/locales/hu.yml | 6 ------ .../config/locales/id-ID.yml | 6 ------ .../config/locales/is-IS.yml | 2 -- .../config/locales/it.yml | 6 ------ .../config/locales/ja.yml | 6 ------ .../config/locales/lb.yml | 2 -- .../config/locales/lt.yml | 6 ------ .../config/locales/lv.yml | 6 ------ .../config/locales/nl.yml | 6 ------ .../config/locales/no.yml | 6 ------ .../config/locales/pl.yml | 8 +------- .../config/locales/pt-BR.yml | 6 ------ .../config/locales/pt.yml | 6 ------ .../config/locales/ro-RO.yml | 6 ------ .../config/locales/ru.yml | 6 ------ .../config/locales/sk.yml | 6 ------ .../config/locales/sv.yml | 8 ++++---- .../config/locales/tr-TR.yml | 6 ------ .../config/locales/uk.yml | 6 ------ .../config/locales/zh-CN.yml | 6 ------ .../config/locales/zh-TW.yml | 6 ------ 93 files changed, 99 insertions(+), 377 deletions(-) diff --git a/decidim-accountability/config/locales/bs-BA.yml b/decidim-accountability/config/locales/bs-BA.yml index c14bdf378f32e..64b3755e869c3 100644 --- a/decidim-accountability/config/locales/bs-BA.yml +++ b/decidim-accountability/config/locales/bs-BA.yml @@ -21,6 +21,7 @@ bs: progress: Napredak timeline_entry: description: Opis + entry_date: Datum title: Naslov models: decidim/accountability/proposal_linked_event: Predlog uključen u rezultat @@ -104,6 +105,7 @@ bs: progress: Napredak timeline_entry: fields: + entry_date: Datum title: Naslov results: count: diff --git a/decidim-accountability/config/locales/de.yml b/decidim-accountability/config/locales/de.yml index 312c80615ed0c..8c06c7887312e 100644 --- a/decidim-accountability/config/locales/de.yml +++ b/decidim-accountability/config/locales/de.yml @@ -8,7 +8,7 @@ de: decidim_scope_id: Umfang description: Beschreibung end_date: Enddatum - meetings_ids: Enthaltene Sitzungen + meetings_ids: Enthaltene Veranstaltungen progress: Fortschritt project_ids: Enthaltene Projekte proposals: Enthaltene Vorschläge diff --git a/decidim-assemblies/config/locales/ar.yml b/decidim-assemblies/config/locales/ar.yml index 8dba287dc9049..ac4d908c79119 100644 --- a/decidim-assemblies/config/locales/ar.yml +++ b/decidim-assemblies/config/locales/ar.yml @@ -119,10 +119,6 @@ ar: update: error: حدثت مشكلة أثناء تحديث هذه الجمعية. success: تم تحديث الجمعية بنجاح. - assemblies_copies: - create: - error: حدثت مشكلة في تكرار هذا التجميع. - success: الجمعية مكررة بنجاح. assemblies_types: create: error: حدثت مشكلة أثناء إنشاء جمعية جديدة. @@ -138,8 +134,6 @@ ar: assembly_copies: new: copy: نسخ - select: حدد البيانات التي ترغب في تكرارها - title: نسخ الجمعية assembly_imports: create: error: حدثت مشكلة أثناء استيراد هذه الجمعية. diff --git a/decidim-assemblies/config/locales/bg.yml b/decidim-assemblies/config/locales/bg.yml index 51dca0de987f7..da54c29c75d06 100644 --- a/decidim-assemblies/config/locales/bg.yml +++ b/decidim-assemblies/config/locales/bg.yml @@ -116,10 +116,6 @@ bg: update: error: Възникна проблем при актуализирането на това събрание. success: Събранието беше актуализирано успешно. - assemblies_copies: - create: - error: Възникна проблем при дублирането на това събрание. - success: Събранието беше дублирано успешно. assemblies_types: create: error: Възникна проблем при създаването на нов тип събрание. @@ -135,8 +131,6 @@ bg: assembly_copies: new: copy: Копиране - select: Изберете кои данни искате да дублирате - title: Дублиране на събрание assembly_imports: create: error: Възникна проблем при импортирането на това събрание. diff --git a/decidim-assemblies/config/locales/cs.yml b/decidim-assemblies/config/locales/cs.yml index fa7b7f444177c..20c7414d716d0 100644 --- a/decidim-assemblies/config/locales/cs.yml +++ b/decidim-assemblies/config/locales/cs.yml @@ -122,10 +122,6 @@ cs: update: error: Při aktualizaci tohoto shromáždění došlo k chybě. success: Shromáždění úspěšně aktualizováno. - assemblies_copies: - create: - error: Při kopírování tohoto shromáždění došlo k chybě. - success: Shromáždění úspěšně duplikováno. assemblies_types: create: error: Při vytváření nového typu shromáždění došlo k chybě. @@ -141,8 +137,6 @@ cs: assembly_copies: new: copy: Kopírovat - select: Vyberte, která data chcete duplikovat - title: Zduplikovat shromáždění assembly_imports: create: error: Při importu tohoto shromáždění došlo k chybě. diff --git a/decidim-assemblies/config/locales/de.yml b/decidim-assemblies/config/locales/de.yml index 7e2b4d60aca21..f6e3e98b4ce91 100644 --- a/decidim-assemblies/config/locales/de.yml +++ b/decidim-assemblies/config/locales/de.yml @@ -135,7 +135,7 @@ de: assembly_copies: new: copy: Kopieren - select: Wählen Sie die Daten aus, die Sie duplizieren möchten + select: Wählen Sie, welche Daten Sie duplizieren möchten title: Dupliziertes Gremium assembly_imports: create: diff --git a/decidim-assemblies/config/locales/el.yml b/decidim-assemblies/config/locales/el.yml index eceac95b084f4..bab2c0032c5c0 100644 --- a/decidim-assemblies/config/locales/el.yml +++ b/decidim-assemblies/config/locales/el.yml @@ -112,10 +112,6 @@ el: update: error: Υπήρξε ένα πρόβλημα κατά την ενημέρωση αυτής της συνέλευσης. success: Η συνέλευση ενημερώθηκε με επιτυχία. - assemblies_copies: - create: - error: Υπήρξε ένα πρόβλημα κατά την αντιγραφή αυτής της συνέλευσης. - success: Η συνέλευση αντιγράφτηκε με επιτυχία. assemblies_types: create: error: Υπήρξε ένα πρόβλημα κατά τη δημιουργία ενός νέου τύπου συνέλευσης. @@ -131,8 +127,6 @@ el: assembly_copies: new: copy: Αντιγραφή - select: Επιλέξτε ποια δεδομένα θέλετε να αντιγράψετε - title: Αντιγραφή συνέλευσης assembly_imports: create: error: Προέκυψε ένα πρόβλημα κατά την εισαγωγή αυτής της συνέλευσης. diff --git a/decidim-assemblies/config/locales/es-MX.yml b/decidim-assemblies/config/locales/es-MX.yml index fbf83f6357f6f..f576fc1497e3c 100644 --- a/decidim-assemblies/config/locales/es-MX.yml +++ b/decidim-assemblies/config/locales/es-MX.yml @@ -118,8 +118,8 @@ es-MX: success: La asamblea se ha actualizado correctamente. assemblies_copies: create: - error: Se produjo un error al duplicar esta asamblea. - success: Asamblea duplicada correctamente. + error: Se ha producido un error al duplicar esta asamblea. + success: La asamblea se ha duplicado correctamente. assemblies_types: create: error: Se ha producido un error al crear un nuevo tipo de asamblea. @@ -135,7 +135,7 @@ es-MX: assembly_copies: new: copy: Copiar - select: Seleccione los datos que desea duplicar + select: Selecciona qué datos quieres duplicar title: Duplicar asamblea assembly_imports: create: diff --git a/decidim-assemblies/config/locales/es-PY.yml b/decidim-assemblies/config/locales/es-PY.yml index 06fd70799feac..f63be6043aec4 100644 --- a/decidim-assemblies/config/locales/es-PY.yml +++ b/decidim-assemblies/config/locales/es-PY.yml @@ -118,8 +118,8 @@ es-PY: success: La asamblea se ha actualizado correctamente. assemblies_copies: create: - error: Se produjo un error al duplicar esta asamblea. - success: Asamblea duplicada correctamente. + error: Se ha producido un error al duplicar esta asamblea. + success: La asamblea se ha duplicado correctamente. assemblies_types: create: error: Se ha producido un error al crear un nuevo tipo de asamblea. @@ -135,7 +135,7 @@ es-PY: assembly_copies: new: copy: Copiar - select: Seleccione los datos que desea duplicar + select: Selecciona qué datos quieres duplicar title: Duplicar asamblea assembly_imports: create: diff --git a/decidim-assemblies/config/locales/es.yml b/decidim-assemblies/config/locales/es.yml index 6f37a22026e70..40bf7be220b47 100644 --- a/decidim-assemblies/config/locales/es.yml +++ b/decidim-assemblies/config/locales/es.yml @@ -119,7 +119,7 @@ es: assemblies_copies: create: error: Se ha producido un error al duplicar esta asamblea. - success: Asamblea duplicada correctamente. + success: La asamblea se ha duplicado correctamente. assemblies_types: create: error: Se ha producido un error al crear un nuevo tipo de asamblea. diff --git a/decidim-assemblies/config/locales/eu.yml b/decidim-assemblies/config/locales/eu.yml index 500240488f5da..8eefe5b10a63e 100644 --- a/decidim-assemblies/config/locales/eu.yml +++ b/decidim-assemblies/config/locales/eu.yml @@ -116,10 +116,6 @@ eu: update: error: Arazo bat egon da batzar hau eguneratzean. success: Batzarra zuzen eguneratua. - assemblies_copies: - create: - error: Arazo bat egon da batzar hau bikoiztean. - success: Batzarra zuzen bikoiztua. assemblies_types: create: error: Arazo bat egon da beste batzar mota bat sortzean. @@ -135,8 +131,6 @@ eu: assembly_copies: new: copy: Kopiatu - select: Hautatu bikoiztu nahi dituzun datuak - title: Kopiatu batzarra assembly_imports: create: error: Arazo bat egon da batzar hau inportatzean. diff --git a/decidim-assemblies/config/locales/fi-plain.yml b/decidim-assemblies/config/locales/fi-plain.yml index 2236417102db6..803b6c364fe7e 100644 --- a/decidim-assemblies/config/locales/fi-plain.yml +++ b/decidim-assemblies/config/locales/fi-plain.yml @@ -118,8 +118,8 @@ fi-pl: success: Ryhmä päivitettiin onnistuneesti. assemblies_copies: create: - error: Virhe kopioitaessa ryhmää. - success: Ryhmä kopioitu onnistuneesti. + error: Ryhmän kopioiminen epäonnistui. + success: Ryhmän kopioiminen onnistui. assemblies_types: create: error: Uuden ryhmätyypin luonti epäonnistui. diff --git a/decidim-assemblies/config/locales/fi.yml b/decidim-assemblies/config/locales/fi.yml index b612d1bf4a705..6a9f1267b8ce3 100644 --- a/decidim-assemblies/config/locales/fi.yml +++ b/decidim-assemblies/config/locales/fi.yml @@ -118,8 +118,8 @@ fi: success: Ryhmän päivitys onnistui. assemblies_copies: create: - error: Ryhmän kopiointi epäonnistui. - success: Ryhmän kopiointi onnistui. + error: Ryhmän kopioiminen epäonnistui. + success: Ryhmän kopioiminen onnistui. assemblies_types: create: error: Uuden ryhmätyypin luonti epäonnistui. diff --git a/decidim-assemblies/config/locales/fr-CA.yml b/decidim-assemblies/config/locales/fr-CA.yml index 191ae636a6ecf..69e45e82eed29 100644 --- a/decidim-assemblies/config/locales/fr-CA.yml +++ b/decidim-assemblies/config/locales/fr-CA.yml @@ -302,7 +302,7 @@ fr-CA: title: Titre assembly_copies: form: - slug_help_html: 'Les identifiants d''URL sont utilisés pour générer les URL qui pointent vers cette assemblée. N''accepte que des lettres, des chiffres et des tirets et doit commencer par une lettre. Exemple: %{url}' + slug_help_html: 'Les identifiants d''URL sont utilisés pour générer les URL qui pointent vers cette assemblée. N''accepte que des lettres, des chiffres et des tirets et doit commencer par une lettre. Exemple : %{url}' assembly_imports: form: slug_help_html: 'Les identifiants d''URL sont utilisés pour générer les URL qui pointent vers cette assemblée. N''accepte que des lettres, des chiffres et des tirets et doit commencer par une lettre. Exemple: %{url}' diff --git a/decidim-assemblies/config/locales/fr.yml b/decidim-assemblies/config/locales/fr.yml index 1c37063d0ec49..ddd3a3a5b0727 100644 --- a/decidim-assemblies/config/locales/fr.yml +++ b/decidim-assemblies/config/locales/fr.yml @@ -302,7 +302,7 @@ fr: title: Modifier le type d'assemblée assembly_copies: form: - slug_help_html: 'Les identifiants d''URL sont utilisés pour générer les URL qui pointent vers cette assemblée. N''accepte que des lettres, des chiffres et des tirets et doit commencer par une lettre. Exemple: %{url}' + slug_help_html: 'Les identifiants d''URL sont utilisés pour générer les URL qui pointent vers cette assemblée. N''accepte que des lettres, des chiffres et des tirets et doit commencer par une lettre. Exemple : %{url}' assembly_imports: form: slug_help_html: 'Les identifiants d''URL sont utilisés pour générer les URL qui pointent vers cette assemblée. N''accepte que des lettres, des chiffres et des tirets et doit commencer par une lettre. Exemple: %{url}' diff --git a/decidim-assemblies/config/locales/gl.yml b/decidim-assemblies/config/locales/gl.yml index 4a2e9132298c7..456c5805f651f 100644 --- a/decidim-assemblies/config/locales/gl.yml +++ b/decidim-assemblies/config/locales/gl.yml @@ -100,10 +100,6 @@ gl: update: error: Produciuse un erro ao actualizar este conxunto. success: Asemblea actualizouse con éxito. - assemblies_copies: - create: - error: Produciuse un erro ao duplicar este conxunto. - success: Asemblea duplicouse con éxito. assemblies_types: create: error: Produciuse un problema ao crear un novo tipo de xuntanza. @@ -119,8 +115,6 @@ gl: assembly_copies: new: copy: Copiar - select: Seleccione os datos que desexa duplicar - title: Conxunto duplicado assembly_imports: create: error: Produciuse un problema ao importar esta xuntanza. diff --git a/decidim-assemblies/config/locales/he-IL.yml b/decidim-assemblies/config/locales/he-IL.yml index 195cce0eab1da..226ffe7961a13 100644 --- a/decidim-assemblies/config/locales/he-IL.yml +++ b/decidim-assemblies/config/locales/he-IL.yml @@ -114,10 +114,6 @@ he: update: error: יש בעיה בעדכון אסיפה זו. success: האסיפה עודכנה בהצלחה. - assemblies_copies: - create: - error: יש בעיה בשכפול אסיפה זו. - success: אסיפה השתכפלה בהצלחה. assemblies_types: create: error: יש בעיה ביצירת סוג אסיפה חדש. @@ -133,7 +129,6 @@ he: assembly_copies: new: copy: העתקה - title: שכפל אסיפה assembly_imports: create: error: הייתה בעיה בייבוא ​​אסיפה זו. diff --git a/decidim-assemblies/config/locales/hu.yml b/decidim-assemblies/config/locales/hu.yml index 0b7428c472b30..f92d2c31f04e8 100644 --- a/decidim-assemblies/config/locales/hu.yml +++ b/decidim-assemblies/config/locales/hu.yml @@ -116,10 +116,6 @@ hu: update: error: Hiba történt a gyűlés frissítése során. success: Gyűlés frissítése sikeres. - assemblies_copies: - create: - error: Hiba történt a gyűlés duplikációja során. - success: Gyűlés duplikációja sikeres. assemblies_types: create: error: Hiba történt az új gyűlés létrehozása során. @@ -135,8 +131,6 @@ hu: assembly_copies: new: copy: Másolás - select: Válaszd ki, mely adatokat szeretnéd duplikálni - title: Gyűlés duplikálása assembly_imports: create: error: Hiba történt a gyűlés importálása során. diff --git a/decidim-assemblies/config/locales/id-ID.yml b/decidim-assemblies/config/locales/id-ID.yml index ac5ce1b90a62c..0345776d22bf8 100644 --- a/decidim-assemblies/config/locales/id-ID.yml +++ b/decidim-assemblies/config/locales/id-ID.yml @@ -92,18 +92,12 @@ id: update: error: Ada kesalahan saat memperbarui perakitan ini. success: Majelis berhasil diperbarui. - assemblies_copies: - create: - error: Ada kesalahan saat menduplikasi majelis ini. - success: Majelis berhasil digandakan. assemblies_types: new: create: Membuat assembly_copies: new: copy: Salinan - select: Pilih data mana yang ingin Anda gandakan - title: Rakitan duplikat assembly_members: create: error: Terjadi kesalahan saat menambahkan anggota untuk majelis ini. diff --git a/decidim-assemblies/config/locales/is-IS.yml b/decidim-assemblies/config/locales/is-IS.yml index ca4c1d796e3ff..272e18976ee36 100644 --- a/decidim-assemblies/config/locales/is-IS.yml +++ b/decidim-assemblies/config/locales/is-IS.yml @@ -92,8 +92,6 @@ is: assembly_copies: new: copy: Afrita - select: Veldu hvaða gögn þú vilt afrita - title: Afrit samkoma assembly_imports: new: import: Flytja inn diff --git a/decidim-assemblies/config/locales/it.yml b/decidim-assemblies/config/locales/it.yml index 7ea7627b48c1d..631720a86409a 100644 --- a/decidim-assemblies/config/locales/it.yml +++ b/decidim-assemblies/config/locales/it.yml @@ -115,10 +115,6 @@ it: update: error: Si è verificato un errore durante l'aggiornamento di questa assemblea. success: L'assemblea è stata aggiornata correttamente. - assemblies_copies: - create: - error: Si è verificato un errore durante la duplicazione di questa assemblea. - success: L'assemblea è stata duplicata con successo. assemblies_types: create: error: Si è verificato un errore durante la creazione di un nuovo tipo di assemblea. @@ -134,8 +130,6 @@ it: assembly_copies: new: copy: Copia - select: Scegli quale dato vuoi duplicare - title: Duplica l'assemblea assembly_imports: create: error: Si è verificato un errore durante l'aggiornamento di questa assemblea. diff --git a/decidim-assemblies/config/locales/ja.yml b/decidim-assemblies/config/locales/ja.yml index 78d13c021dfbc..f10625eccd2c1 100644 --- a/decidim-assemblies/config/locales/ja.yml +++ b/decidim-assemblies/config/locales/ja.yml @@ -113,10 +113,6 @@ ja: update: error: 参加スペースの更新に問題がありました。 success: 参加スペースを更新しました。 - assemblies_copies: - create: - error: この参加スペースの複製中に問題が発生しました。 - success: 参加スペースを複製しました。 assemblies_types: create: error: 新しい参加スペース種別の作成に問題がありました。 @@ -132,8 +128,6 @@ ja: assembly_copies: new: copy: コピー - select: 複製したいデータを選択してください - title: 参加スペースを複製 assembly_imports: create: error: この参加スペースのインポート中に問題が発生しました。 diff --git a/decidim-assemblies/config/locales/lb.yml b/decidim-assemblies/config/locales/lb.yml index 3a2cdc042c007..ddcbf55a7243c 100644 --- a/decidim-assemblies/config/locales/lb.yml +++ b/decidim-assemblies/config/locales/lb.yml @@ -100,10 +100,6 @@ lb: update: error: Beim Aktualisieren dieses Gremiums ist ein Fehler aufgetreten. success: Das Gremium wurde erfolgreich aktualisiert. - assemblies_copies: - create: - error: Beim Duplizieren dieses Gremiums ist ein Fehler aufgetreten. - success: Das Gremium wurde erfolgreich dupliziert. assemblies_types: create: error: Beim Erstellen eines neuen Gremiumtyps ist ein Fehler aufgetreten. @@ -119,8 +115,6 @@ lb: assembly_copies: new: copy: Kopieren - select: Wählen Sie die Daten aus, die Sie duplizieren möchten - title: Doppelte Montage assembly_imports: create: error: Beim Importieren dieses Gremiums ist ein Fehler aufgetreten. diff --git a/decidim-assemblies/config/locales/lt.yml b/decidim-assemblies/config/locales/lt.yml index 5d5d38a6b2825..872685d9c8407 100644 --- a/decidim-assemblies/config/locales/lt.yml +++ b/decidim-assemblies/config/locales/lt.yml @@ -118,10 +118,6 @@ lt: update: error: Atnaujinant šią asamblėją iškilo problema. success: Asamblėja atnaujinta. - assemblies_copies: - create: - error: Dubliuojant šią asamblėją iškilo problema. - success: Asamblėja dubliuota. assemblies_types: create: error: Kuriant naujos asamblėjos tipą iškilo problema. @@ -137,8 +133,6 @@ lt: assembly_copies: new: copy: Kopijuoti - select: Pasirinkite, kuriuos duomenis norėtumėte dubliuoti - title: Dublikuoti asamblėją assembly_imports: create: error: Importuojant asamblėją iškilo problema. diff --git a/decidim-assemblies/config/locales/lv.yml b/decidim-assemblies/config/locales/lv.yml index e3aef39cbe6c8..c08b5c1b5e1ab 100644 --- a/decidim-assemblies/config/locales/lv.yml +++ b/decidim-assemblies/config/locales/lv.yml @@ -95,10 +95,6 @@ lv: update: error: Šīs asamblejas atjaunināšanas laikā radās problēma. success: Asambleja ir veiksmīgi atjaunināta. - assemblies_copies: - create: - error: Šīs asamblejas dublēšanas laikā radās problēma. - success: Asamblejas dublēšana ir veiksmīgi pabeigta. assemblies_types: create: error: Jaunas asamblejas veida izveides laikā radās problēma. @@ -114,8 +110,6 @@ lv: assembly_copies: new: copy: Kopēt - select: Atlasiet, kurus datus vēlaties dublēt - title: Dublēt asambleju assembly_members: create: error: Pievienojot dalībnieku šai asamblejai, radās problēma. diff --git a/decidim-assemblies/config/locales/nl.yml b/decidim-assemblies/config/locales/nl.yml index 53dfa03258e07..abb37d011184e 100644 --- a/decidim-assemblies/config/locales/nl.yml +++ b/decidim-assemblies/config/locales/nl.yml @@ -100,10 +100,6 @@ nl: update: error: Er is een fout opgetreden bij het bijwerken van deze groep. success: Groep is succesvol bijgewerkt. - assemblies_copies: - create: - error: Er was een fout bij het dupliceren van deze groep. - success: Groep succesvol gedupliceerd. assemblies_types: create: error: Er is een fout opgetreden bij het maken van een nieuw groepstype. @@ -119,8 +115,6 @@ nl: assembly_copies: new: copy: Kopiëren - select: Selecteer welke gegevens u wilt dupliceren - title: Dupliceer bijeenkomst assembly_imports: create: error: Er is een probleem opgetreden bij het importeren van deze groep. diff --git a/decidim-assemblies/config/locales/no.yml b/decidim-assemblies/config/locales/no.yml index 636fc469db2e9..d8a90c6af3680 100644 --- a/decidim-assemblies/config/locales/no.yml +++ b/decidim-assemblies/config/locales/no.yml @@ -100,10 +100,6 @@ update: error: Det oppstod et problem med å oppdatere denne forsamlingen. success: Forsamlingen ble oppdatert. - assemblies_copies: - create: - error: Det oppstod et problem med å publisere denne forsamlingen. - success: Forsamlingen ble duplisert. assemblies_types: create: error: Det oppstod et problem med å lage en ny forsamlingstype. @@ -119,8 +115,6 @@ assembly_copies: new: copy: Kopier - select: Velg hvilke data du vil duplisere - title: Dupliser forsamling assembly_imports: create: error: Det oppstod et problem med å importere denne forsamlingen. diff --git a/decidim-assemblies/config/locales/pl.yml b/decidim-assemblies/config/locales/pl.yml index 43d45263c1dc8..2b30ffd7765ee 100644 --- a/decidim-assemblies/config/locales/pl.yml +++ b/decidim-assemblies/config/locales/pl.yml @@ -122,10 +122,6 @@ pl: update: error: Podczas aktualizowania tego zespołu wystąpił błąd. success: Zespół został zaktualizowany. - assemblies_copies: - create: - error: Podczas duplikowania tego zespołu wystąpił błąd. - success: Zespół został zduplikowany. assemblies_types: create: error: Podczas tworzenia nowego typu zespołu wystąpił błąd. @@ -141,8 +137,6 @@ pl: assembly_copies: new: copy: Kopiuj - select: Wybierz dane, które chcesz zduplikować - title: Zduplikuj zespół assembly_imports: create: error: Wystąpił błąd podczas importowania tego zespołu. diff --git a/decidim-assemblies/config/locales/pt-BR.yml b/decidim-assemblies/config/locales/pt-BR.yml index 626f5d616ecd2..d7be97c5db3b9 100644 --- a/decidim-assemblies/config/locales/pt-BR.yml +++ b/decidim-assemblies/config/locales/pt-BR.yml @@ -116,10 +116,6 @@ pt-BR: update: error: Ocorreu um erro ao atualizar esta assembleia. success: Assembleia atualizada com sucesso. - assemblies_copies: - create: - error: Ocorreu um erro ao duplicar esta assembleia. - success: Assembleia duplicada com sucesso. assemblies_types: create: error: Ocorreu um erro ao criar um novo tipo de assembleia. @@ -135,8 +131,6 @@ pt-BR: assembly_copies: new: copy: Cópia - select: Selecione os dados que você gostaria de duplicar - title: Duplicar assembleia assembly_imports: create: error: Ocorreu um erro ao importar esta assembleia. diff --git a/decidim-assemblies/config/locales/pt.yml b/decidim-assemblies/config/locales/pt.yml index de5ead485b9f5..a67b4553944ca 100644 --- a/decidim-assemblies/config/locales/pt.yml +++ b/decidim-assemblies/config/locales/pt.yml @@ -115,10 +115,6 @@ pt: update: error: Ocorreu um problema ao atualizar esta reunião. success: Reunião atualizada corretamente. - assemblies_copies: - create: - error: Ocorreu um problema ao duplicar esta reunião. - success: Reunião duplicada corretamente. assemblies_types: create: error: Ocorreu um problema ao criar um novo tipo de reunião. @@ -134,8 +130,6 @@ pt: assembly_copies: new: copy: Cópia - select: Selecione os dados que pretende duplicar - title: Duplicar assembléia assembly_imports: create: error: Ocorreu um problema ao atualizar esta reunião. diff --git a/decidim-assemblies/config/locales/ro-RO.yml b/decidim-assemblies/config/locales/ro-RO.yml index 375110a39b770..9f8c894310fbc 100644 --- a/decidim-assemblies/config/locales/ro-RO.yml +++ b/decidim-assemblies/config/locales/ro-RO.yml @@ -115,10 +115,6 @@ ro: update: error: A apărut o eroare la actualizarea acestui grup de lucru. success: Grupul de lucru a fost actualizat cu succes. - assemblies_copies: - create: - error: A apărut o problemă la duplicarea acestui grup de lucru. - success: Grupul de lucru a fost duplicat cu succes. assemblies_types: create: error: A apărut o problemă la crearea unui nou tip de grup de lucru. @@ -134,8 +130,6 @@ ro: assembly_copies: new: copy: Copiază - select: Selectați datele pe care doriți să le duplicați - title: Duplică grupul de lucru assembly_imports: create: error: A apărut o problemă la importul acestui grup de lucru. diff --git a/decidim-assemblies/config/locales/ru.yml b/decidim-assemblies/config/locales/ru.yml index bb59def9251ae..e4cc3ae3aa487 100644 --- a/decidim-assemblies/config/locales/ru.yml +++ b/decidim-assemblies/config/locales/ru.yml @@ -100,18 +100,12 @@ ru: update: error: При попытке обновить это собрание произошла ошибка. success: Собрание успешно обновлено. - assemblies_copies: - create: - error: При попытке создать копию этого собрания произошла ошибка. - success: Успешно создана копия собрания. assemblies_types: new: create: Добавить assembly_copies: new: copy: Копировать - select: Выберите, какие данные вы хотите продублировать - title: Создать копию собрания assembly_members: create: error: При попытке добавить члена президиума этого собрания произошла ошибка. diff --git a/decidim-assemblies/config/locales/sl.yml b/decidim-assemblies/config/locales/sl.yml index e40be44e1b619..a66cb3c72cee0 100644 --- a/decidim-assemblies/config/locales/sl.yml +++ b/decidim-assemblies/config/locales/sl.yml @@ -99,10 +99,6 @@ sl: update: error: Pojavila se je težava pri urejanju združbe. success: Združba uspešno posodobljena. - assemblies_copies: - create: - error: Pojavila se je težava pri podvajanju združbe. - success: Združba uspešno podvojena. assemblies_types: create: error: Pojavila se je težava pri kreiranju nove združbe. @@ -118,8 +114,6 @@ sl: assembly_copies: new: copy: Kopiraj - select: Izberi, katere podatke želiš podvojiti - title: Podvoji združbo assembly_imports: create: error: Pojavila se je težava pri uvozu te združbe. diff --git a/decidim-assemblies/config/locales/sq-AL.yml b/decidim-assemblies/config/locales/sq-AL.yml index aeffdc23e555a..cdf7bb09f15c9 100644 --- a/decidim-assemblies/config/locales/sq-AL.yml +++ b/decidim-assemblies/config/locales/sq-AL.yml @@ -104,10 +104,6 @@ sq: update: error: Ndodhi një gabim gjatë përditësimit të asamblesë. success: Asambleja u përditësua me sukses. - assemblies_copies: - create: - error: Ndodhi një gabim gjatë duplikimit të asamblesë. - success: Asambleja e duplikua me sukses. assemblies_types: create: error: Ndodhi një gabim gjatë krijimit të tipit të ri të asamblesë. @@ -123,8 +119,6 @@ sq: assembly_copies: new: copy: Kopjo - select: Zgjidh cilën të dhënë dëshiron të duplikosh - title: Dupliko asamblenë assembly_imports: create: error: Ndodhi një gabim gjatë importimit të asamblesë. diff --git a/decidim-assemblies/config/locales/sv.yml b/decidim-assemblies/config/locales/sv.yml index 08cbb97d4eb50..5d0c56f6145a0 100644 --- a/decidim-assemblies/config/locales/sv.yml +++ b/decidim-assemblies/config/locales/sv.yml @@ -135,7 +135,7 @@ sv: assembly_copies: new: copy: Kopiera - select: Välj vilka data som ska dupliceras + select: Välj vilka data som du vill duplicera title: Duplicera samråd assembly_imports: create: diff --git a/decidim-assemblies/config/locales/tr-TR.yml b/decidim-assemblies/config/locales/tr-TR.yml index b6997dcb86f4d..1f842b0084765 100644 --- a/decidim-assemblies/config/locales/tr-TR.yml +++ b/decidim-assemblies/config/locales/tr-TR.yml @@ -108,10 +108,6 @@ tr: update: error: Bu kurul güncellenirken bir hata oluştu. success: Kurul başarıyla güncellendi. - assemblies_copies: - create: - error: Bu kurul çoğaltırken bir hata oluştu. - success: Kurul başarıyla kopyalandı. assemblies_types: create: error: Yeni bir kurul türü oluşturulurken bir sorun meydana geldi. @@ -127,8 +123,6 @@ tr: assembly_copies: new: copy: Kopya - select: Çoğaltmak istediğiniz verileri seçin - title: Kurulu çoğalt assembly_imports: create: error: Bu kurul derlenirken, içeri aktarılırken bir hata oluştu. diff --git a/decidim-assemblies/config/locales/uk.yml b/decidim-assemblies/config/locales/uk.yml index 5414d9bbad612..c523d49664047 100644 --- a/decidim-assemblies/config/locales/uk.yml +++ b/decidim-assemblies/config/locales/uk.yml @@ -100,18 +100,12 @@ uk: update: error: При спробі оновити ці збори сталася помилка. success: Збори успішно оновлено. - assemblies_copies: - create: - error: При спробі створити копію цих зборів сталася помилка. - success: Успішно створено копію зборів. assemblies_types: new: create: Додати assembly_copies: new: copy: Скопіювати - select: Оберіть відомості, копію яких ви хотіли б створити - title: Створити копію зборів assembly_members: create: error: При спробі додати члена президії цих зборів сталася помилка. diff --git a/decidim-assemblies/config/locales/zh-CN.yml b/decidim-assemblies/config/locales/zh-CN.yml index db5e95825e893..78354bd554818 100644 --- a/decidim-assemblies/config/locales/zh-CN.yml +++ b/decidim-assemblies/config/locales/zh-CN.yml @@ -94,10 +94,6 @@ zh-CN: update: error: 更新这个组件时出现问题。 success: 程序更新成功。 - assemblies_copies: - create: - error: 复制这个组件时出现问题。 - success: 已成功复制组合。 assemblies_types: create: error: 创建新组装类型时出现问题。 @@ -113,8 +109,6 @@ zh-CN: assembly_copies: new: copy: 复制 - select: 选择要重复的数据 - title: 复制程序集 assembly_imports: create: error: 导入此组件时出现问题。 diff --git a/decidim-assemblies/config/locales/zh-TW.yml b/decidim-assemblies/config/locales/zh-TW.yml index 435f5b42bd741..9ac92a4046497 100644 --- a/decidim-assemblies/config/locales/zh-TW.yml +++ b/decidim-assemblies/config/locales/zh-TW.yml @@ -109,10 +109,6 @@ zh-TW: update: error: 更新此大會時出現問題 success: 此大會成功更新 - assemblies_copies: - create: - error: 複製此大會時出現問題 - success: 大會已成功複製 assemblies_types: create: error: 創建新大會類型時出現問題 @@ -128,8 +124,6 @@ zh-TW: assembly_copies: new: copy: 複製 - select: 請選擇您想要複製的資料 - title: 複製大會 assembly_imports: create: error: 這個大會匯入時發生了問題 diff --git a/decidim-conferences/config/locales/de.yml b/decidim-conferences/config/locales/de.yml index 0de3788384a08..4c213d32290ba 100644 --- a/decidim-conferences/config/locales/de.yml +++ b/decidim-conferences/config/locales/de.yml @@ -102,6 +102,7 @@ de: conference_copies: new: copy: Kopieren + title: Konferenz duplizieren conference_publications: create: error: Beim Veröffentlichen dieser Konferenz ist ein Fehler aufgetreten. @@ -172,6 +173,10 @@ de: update: error: Beim Aktualisieren dieser Konferenz ist ein Fehler aufgetreten. success: Konferenz wurde erfolgreich aktualisiert. + conferences_copies: + create: + error: Beim Duplizieren dieser Konferenz ist ein Fehler aufgetreten. + success: Konferenz wurde erfolgreich dupliziert. media_links: create: error: Beim Erstellen einer neuen Medienverknüpfung ist ein Fehler aufgetreten. diff --git a/decidim-core/config/locales/ca-IT.yml b/decidim-core/config/locales/ca-IT.yml index 4b5bd6f9c182e..2e0adb8bb395f 100644 --- a/decidim-core/config/locales/ca-IT.yml +++ b/decidim-core/config/locales/ca-IT.yml @@ -1523,6 +1523,7 @@ ca-IT: close_modal: Tancar el modal ok: D'acord title: Confirmar + confirm_unload: Aquesta pàgina conté canvis sense desar. Segur que vols abandonar aquesta pàgina? embed: title: Contingut del vídeo incrustat extended_navigation_bar: diff --git a/decidim-core/config/locales/ca.yml b/decidim-core/config/locales/ca.yml index 84ce4c407c764..3e8dd17309a21 100644 --- a/decidim-core/config/locales/ca.yml +++ b/decidim-core/config/locales/ca.yml @@ -1523,6 +1523,7 @@ ca: close_modal: Tancar el modal ok: D'acord title: Confirmar + confirm_unload: Aquesta pàgina conté canvis sense desar. Segur que vols abandonar aquesta pàgina? embed: title: Contingut del vídeo incrustat extended_navigation_bar: diff --git a/decidim-core/config/locales/cs.yml b/decidim-core/config/locales/cs.yml index a91874e8ede15..60b7a74010fb3 100644 --- a/decidim-core/config/locales/cs.yml +++ b/decidim-core/config/locales/cs.yml @@ -1464,8 +1464,10 @@ cs: fill_in_email_to_confirm_it: Pro potvrzení vyplňte e-mailovou adresu skupiny. reported_mailer: hidden_automatically: + content: Nahlášený obsah details: Podrobnosti hello: Dobrý den %{name}, + manage_moderations: Spravovat moderace participatory_space: Participativní prostor reason: Důvod report_html:

Následující obsah byl automaticky skryt.

@@ -1545,6 +1547,7 @@ cs: close_modal: Zavřít okno ok: OK title: Potvrdit + confirm_unload: Tato stránka obsahuje neuložené změny. Opravdu chcete opustit tuto stránku? embed: title: Vložený obsah videa extended_navigation_bar: @@ -2062,6 +2065,7 @@ cs: profile: Můj účet public_profile: Můj veřejný profil title: Profilové odkazy + unread_conversations: Máte nepřečtené konverzace unread_notifications: Máte nepřečtené oznámení user_profile: account: Účet diff --git a/decidim-core/config/locales/de.yml b/decidim-core/config/locales/de.yml index d9abaaf99dadf..a20d2cb1fefd0 100644 --- a/decidim-core/config/locales/de.yml +++ b/decidim-core/config/locales/de.yml @@ -176,7 +176,7 @@ de: close: Fenster schließen ok: Ja, ich möchte mein Konto löschen question: Sind Sie sicher, dass Sie Ihr Konto löschen möchten? - title: Mein Konto löschen + title: Konto löschen explanation: Bitte geben Sie den Grund an, warum Sie Ihr Konto löschen möchten (optional). leaving_authorizations_behind: Einige Daten, die an Ihre Autorisierung gebunden sind, werden aus Sicherheitsgründen gespeichert. Wenn Sie ein anderes Konto erstellen und erneut autorisieren, werden diese Daten auf Ihr neues Konto wiederhergestellt. destroy: @@ -987,6 +987,12 @@ de: title: Was sind Abzeichen? group_admins: actions: + confirm_modal: + ok_add: Neuen Admin hinzufügen + ok_remove: Admin entfernen + title_add: Neuer Admin bestätigen + title_remove: Admin entfernen + confirm_remove_from_admin: Sind Sie sicher, dass Sie den Admin dieser Gruppe entfernen möchten? demote_admin: Entferne Admin demote: error: Beim Entfernen dieses Teilnehmers aus der Adminliste ist ein Fehler aufgetreten. @@ -1016,6 +1022,11 @@ de: error: Bei der Annahme dieser Beitrittsanfrage ist ein Fehler aufgetreten. success: Beitrittsanfrage erfolgreich angenommen. actions: + confirm_modal: + ok_remove: Mitglied entfernen + title_remove: Mitglied entfernen + confirm_promote_to_admin: Sind Sie sicher, dass Sie dieses Konto als Admin hinzufügen möchten? + confirm_remove_from_group: Sind Sie sicher, dass Sie dieses Konto von der Gruppe entfernen möchten? promote_to_admin: Zum Admin ernennen remove_from_group: Benutzer entfernen index: @@ -1508,6 +1519,7 @@ de: close_modal: Fenster schließen ok: OK title: Bestätigen + confirm_unload: Diese Seite enthält ungespeicherte Änderungen. Möchten Sie die Seite dennoch verlassen? embed: title: Eingebetteter Videoinhalt extended_navigation_bar: @@ -2019,10 +2031,12 @@ de: profile: Mein Konto public_profile: Mein öffentliches Profil title: Profil-Links + unread_conversations: Sie haben ungelesene Unterhaltungen + unread_notifications: Sie haben ungelesene Benachrichtigungen user_profile: account: Konto authorizations: Berechtigungen - delete_my_account: Löschen Sie mein Konto + delete_my_account: Konto löschen my_data: Meine Daten my_interests: Meine Interessen notifications_settings: Benachrichtigungseinstellungen diff --git a/decidim-core/config/locales/es-MX.yml b/decidim-core/config/locales/es-MX.yml index 2c2f946b93252..926cbdd406eaf 100644 --- a/decidim-core/config/locales/es-MX.yml +++ b/decidim-core/config/locales/es-MX.yml @@ -1526,6 +1526,7 @@ es-MX: close_modal: Cerrar el modal ok: De acuerdo title: Confirmar + confirm_unload: Esta página contiene cambios sin guardar. ¿Seguro que quieres abandonar esta página? embed: title: Contenido de vídeo incrustado extended_navigation_bar: diff --git a/decidim-core/config/locales/es-PY.yml b/decidim-core/config/locales/es-PY.yml index 9a1baa101c8d1..38d9be8181bbd 100644 --- a/decidim-core/config/locales/es-PY.yml +++ b/decidim-core/config/locales/es-PY.yml @@ -1526,6 +1526,7 @@ es-PY: close_modal: Cerrar el modal ok: De acuerdo title: Confirmar + confirm_unload: Esta página contiene cambios sin guardar. ¿Seguro que quieres abandonar esta página? embed: title: Contenido de vídeo incrustado extended_navigation_bar: diff --git a/decidim-core/config/locales/es.yml b/decidim-core/config/locales/es.yml index a1e4c96ba8c89..1c921fe7d7517 100644 --- a/decidim-core/config/locales/es.yml +++ b/decidim-core/config/locales/es.yml @@ -1523,6 +1523,7 @@ es: close_modal: Cerrar el modal ok: De acuerdo title: Confirmar + confirm_unload: Esta página contiene cambios sin guardar. ¿Seguro que quieres abandonar esta página? embed: title: Contenido de vídeo incrustado extended_navigation_bar: diff --git a/decidim-core/config/locales/eu.yml b/decidim-core/config/locales/eu.yml index 6f8f7330224aa..90ed5408d36ec 100644 --- a/decidim-core/config/locales/eu.yml +++ b/decidim-core/config/locales/eu.yml @@ -1524,6 +1524,7 @@ eu: close_modal: Itxi leihoa ok: Ados title: Baieztatu + confirm_unload: Orrialde honetan gorde gabeko aldaketak daude. Ziur zaude orri hau utzi nahi duzula? embed: title: Bideo-eduki txertatuak extended_navigation_bar: diff --git a/decidim-core/config/locales/fi-plain.yml b/decidim-core/config/locales/fi-plain.yml index 88c993463d413..fdc3807a2c7f7 100644 --- a/decidim-core/config/locales/fi-plain.yml +++ b/decidim-core/config/locales/fi-plain.yml @@ -1523,6 +1523,7 @@ fi-pl: close_modal: Sulje ikkuna ok: OK title: Vahvista + confirm_unload: Sinulla on tallentamattomia muutoksia. Oletko varma, että haluat poistua tältä sivulta? embed: title: Upotettu videosisältö extended_navigation_bar: diff --git a/decidim-core/config/locales/fi.yml b/decidim-core/config/locales/fi.yml index ce6b2441c6758..a1d9951664208 100644 --- a/decidim-core/config/locales/fi.yml +++ b/decidim-core/config/locales/fi.yml @@ -1523,6 +1523,7 @@ fi: close_modal: Sulje ikkuna ok: OK title: Vahvista + confirm_unload: Sinulla on tallentamattomia muutoksia. Oletko varma, että haluat poistua tältä sivulta? embed: title: Upotettu videosisältö extended_navigation_bar: diff --git a/decidim-core/config/locales/fr-CA.yml b/decidim-core/config/locales/fr-CA.yml index d65840ccc4261..4646ad66a4874 100644 --- a/decidim-core/config/locales/fr-CA.yml +++ b/decidim-core/config/locales/fr-CA.yml @@ -1525,6 +1525,7 @@ fr-CA: close_modal: Fermer la fenêtre modale ok: Ok title: Valider + confirm_unload: Cette page contient des modifications non enregistrées. Êtes-vous sûr(e) de vouloir quitter cette page ? embed: title: Contenu vidéo intégré extended_navigation_bar: diff --git a/decidim-core/config/locales/fr.yml b/decidim-core/config/locales/fr.yml index 1ae6b89be7ca1..e840d87a06ead 100644 --- a/decidim-core/config/locales/fr.yml +++ b/decidim-core/config/locales/fr.yml @@ -1525,6 +1525,7 @@ fr: close_modal: Fermer la fenêtre de dialogue ok: Ok title: Valider + confirm_unload: Cette page contient des modifications non enregistrées. Êtes-vous sûr(e) de vouloir quitter cette page ? embed: title: Contenu vidéo intégré extended_navigation_bar: diff --git a/decidim-core/config/locales/ja.yml b/decidim-core/config/locales/ja.yml index 3c2586e4af147..eb70a786bb3df 100644 --- a/decidim-core/config/locales/ja.yml +++ b/decidim-core/config/locales/ja.yml @@ -1505,6 +1505,7 @@ ja: close_modal: ウィンドウを閉じる ok: OK title: 確定 + confirm_unload: このページには保存されていない変更が含まれています。このページから移動してもよいですか? embed: title: 埋め込み動画コンテンツ extended_navigation_bar: diff --git a/decidim-core/config/locales/pt-BR.yml b/decidim-core/config/locales/pt-BR.yml index d998106977658..b0904acb5340d 100644 --- a/decidim-core/config/locales/pt-BR.yml +++ b/decidim-core/config/locales/pt-BR.yml @@ -1371,6 +1371,7 @@ pt-BR: close_modal: Fechar modal ok: OK title: Confirmar + confirm_unload: Esta página contém alterações não salvas. Tem certeza que deseja sair desta página? extended_navigation_bar: more: Mais unfold: Desdobrar diff --git a/decidim-core/config/locales/sv.yml b/decidim-core/config/locales/sv.yml index 539934510a247..cd363438e25de 100644 --- a/decidim-core/config/locales/sv.yml +++ b/decidim-core/config/locales/sv.yml @@ -1496,6 +1496,7 @@ sv: close_modal: Stäng fönster ok: Ok title: Bekräfta + confirm_unload: Denna sida innehåller osparade ändringar. Är du säker på att du vill lämna denna sida? embed: title: Inbäddat videoinnehåll extended_navigation_bar: diff --git a/decidim-design/config/locales/de.yml b/decidim-design/config/locales/de.yml index 51497bfea7dee..fada75e4bd771 100644 --- a/decidim-design/config/locales/de.yml +++ b/decidim-design/config/locales/de.yml @@ -227,7 +227,7 @@ de: main_colors: Hauptfarben meeting_l: Sitzung L meeting_s: Sitzung S - meetings: Sitzung + meetings: Veranstaltungen meetings_html: Wird von Meetings-Karten verwendet metadata_items: Metadaten-Elemente metadata_text: Jede Ressource definiert eigene Metadaten diff --git a/decidim-meetings/config/locales/de.yml b/decidim-meetings/config/locales/de.yml index 4d445e98f55d4..b61fa20a0e05b 100644 --- a/decidim-meetings/config/locales/de.yml +++ b/decidim-meetings/config/locales/de.yml @@ -174,8 +174,8 @@ de: meetings: meeting_closed: affected_user: - email_intro: 'Ihre Sitzung "%{resource_title}" wurde geschlossen. Sie können die Ergebnisse auf dieser Seite lesen:' - email_outro: Sie haben diese Benachrichtigung erhalten, weil Sie die Sitzung "%{resource_title}" organisiert haben. + email_intro: 'Ihre Veranstaltung "%{resource_title}" wurde geschlossen. Sie können die Ergebnisse auf dieser Seite lesen:' + email_outro: Sie haben diese Benachrichtigung erhalten, weil Sie die Veranstaltung "%{resource_title}" organisiert haben. email_subject: Die Veranstaltung "%{resource_title}" wurde geschlossen notification_title: Die Veranstaltung %{resource_title} wurde geschlossen. follower: @@ -214,7 +214,7 @@ de: upcoming_meeting: email_intro: Die Veranstaltung "%{resource_title}" beginnt in weniger als 48 Stunden. email_outro: Sie haben diese Benachrichtigung erhalten, weil Sie der Veranstaltung "%{resource_title}" folgen. Falls Sie keine solchen Benachrichtigungen mehr erhalten möchten, besuchen Sie den obigen Link. - email_subject: Die Sitzung "%{resource_title}" beginnt in weniger als 48 Stunden. + email_subject: Die Veranstaltung "%{resource_title}" beginnt in weniger als 48 Stunden. notification_title: Die Veranstaltung %{resource_title} beginnt in weniger als 48 Stunden. forms: meetings: @@ -315,7 +315,7 @@ de: success: Veranstaltung erfolgreich abgeschlossen. create: invalid: Beim Erstellen dieser Veranstaltung ist ein Problem aufgetreten. - success: Sitzung erfolgreich erstellt. Beachten Sie, dass dies noch nicht veröffentlicht wurde. Sie müssen es manuell veröffentlichen. + success: Veranstaltung erfolgreich erstellt. Beachten Sie, dass dies noch nicht veröffentlicht wurde. Sie müssen es manuell veröffentlichen. destroy: invalid: proposals_count: @@ -330,7 +330,7 @@ de: disclaimer: 'Haftungsausschluss: Durch Verwendung eines externen Registrierungssystems ist Ihnen bewusst, dass die Organisatoren von %{organization} nicht für die Daten verantwortlich sind, welche die Benutzenden dem externen Dienst zur Verfügung stellen.' iframe_embed_type_html: 'Folgende Dienste erlauben das Einbetten einer Veranstaltung oder eines Livestreams: %{domains}' location_help: 'Ort: an die Benutzer gerichtete Nachricht mit dem Treffpunkt' - location_hints_help: 'Hinweise zum Standort: Zusätzliche Informationen. Beispiel: das Stockwerk im Gebäude wenn es eine Sitzung vor Ort ist, oder das Passwort wenn es eine Online-Sitzung mit eingeschränktem Zugriff ist.' + location_hints_help: 'Hinweise zum Standort: Zusätzliche Informationen. Beispiel: das Stockwerk im Gebäude, wenn es eine Veranstaltung vor Ort ist, oder das Passwort wenn es eine Online-Sitzung mit eingeschränktem Zugriff ist.' online_meeting_url_help: 'Link: Erlaubt Teilnehmenden, sich direkt mit Ihrer Veranstaltung zu verbinden' registration_url_help: 'Link: Erlaubt den Teilnehmern den externen Service zu nutzen, den Sie für die Registrierung verwenden' select_a_meeting_type: Bitte einen Veranstaltungstyp auswählen @@ -355,11 +355,11 @@ de: add_service: Dienst hinzufügen services: Dienstleistungen unpublish: - invalid: Beim Zurückziehen der Veröffentlichung dieser Sitzung ist ein Fehler aufgetreten. - success: Veröffentlichung der Sitzung erfolgreich rückgängig gemacht. + invalid: Beim Zurückziehen der Veröffentlichung dieser Veranstaltung ist ein Fehler aufgetreten. + success: Veröffentlichung der Veranstaltung erfolgreich rückgängig gemacht. update: - invalid: Beim Aktualisieren dieser Sitzung ist ein Problem aufgetreten. - success: Sitzung erfolgreich aktualisiert. + invalid: Beim Aktualisieren dieser Veranstaltung ist ein Problem aufgetreten. + success: Veranstaltung erfolgreich aktualisiert. meetings_poll: form: title: Fragebogen für %{questionnaire_for} bearbeiten @@ -686,7 +686,7 @@ de: meeting_proposal: 'Ähnliche Vorschläge:' proposal_meeting: 'Ähnliche Veranstaltungen:' statistics: - meetings_count: Sitzungen + meetings_count: Veranstaltungen devise: mailer: join_meeting: diff --git a/decidim-participatory_processes/config/locales/ar.yml b/decidim-participatory_processes/config/locales/ar.yml index 65503eeed19af..9f3f999194468 100644 --- a/decidim-participatory_processes/config/locales/ar.yml +++ b/decidim-participatory_processes/config/locales/ar.yml @@ -143,8 +143,6 @@ ar: participatory_process_copies: new: copy: نسخ - select: حدد البيانات التي ترغب في تكرارها - title: عملية تشاركية مكررة participatory_process_groups: destroy: success: تم حذف مجموعة العملية التشاركية بنجاح. @@ -228,10 +226,6 @@ ar: update: error: حدثت مشكلة أثناء تحديث هذه العملية التشاركية. success: عملية المشاركة تم تحديثها بنجاح. - participatory_processes_copies: - create: - error: كانت هناك مشكلة في تكرار هذه العملية التشاركية. - success: عملية المشاركة تتكرر بنجاح. participatory_processes_group: create: error: كانت هناك مشكلة في إنشاء مجموعة عملية تشاركية جديدة. diff --git a/decidim-participatory_processes/config/locales/bg.yml b/decidim-participatory_processes/config/locales/bg.yml index 7cb7b5e6b6ef8..1486797d6f22f 100644 --- a/decidim-participatory_processes/config/locales/bg.yml +++ b/decidim-participatory_processes/config/locales/bg.yml @@ -172,8 +172,6 @@ bg: participatory_process_copies: new: copy: Копиране - select: Изберете кои данни искате да дублирате - title: Дублиране на процеси на участие participatory_process_groups: destroy: error: Възникна грешка при премахването на групата на процеса на участие. @@ -285,10 +283,6 @@ bg: update: error: Възникна проблем при актуализирането на този процес на участие. success: Процесът на участие беше актуализиран успешно. - participatory_processes_copies: - create: - error: Възникна проблем при дублирането на този процес на участие. - success: Процесът на участие беше дублиран успешно. participatory_processes_group: create: error: Възникна проблем при създаването на нова група на процеса на участие. diff --git a/decidim-participatory_processes/config/locales/ca-IT.yml b/decidim-participatory_processes/config/locales/ca-IT.yml index 3ac9c55420670..a5fba19c9fd5e 100644 --- a/decidim-participatory_processes/config/locales/ca-IT.yml +++ b/decidim-participatory_processes/config/locales/ca-IT.yml @@ -173,7 +173,7 @@ ca-IT: new: copy: Còpia select: Selecciona quines dades vols duplicar - title: Duplica procés participatiu + title: Duplicar el procés participatiu participatory_process_groups: destroy: error: Hi ha hagut un error eliminant aquest grup de processos participatius. @@ -286,7 +286,7 @@ ca-IT: success: El procés participatiu s'ha actualitzat correctament. participatory_processes_copies: create: - error: S'ha produït un error en duplicar el procés participatiu. + error: S'ha produït un error en duplicar aquest procés participatiu. success: El procés participatiu ha estat duplicat correctament. participatory_processes_group: create: diff --git a/decidim-participatory_processes/config/locales/ca.yml b/decidim-participatory_processes/config/locales/ca.yml index 1099005960a94..697e3385d0405 100644 --- a/decidim-participatory_processes/config/locales/ca.yml +++ b/decidim-participatory_processes/config/locales/ca.yml @@ -173,7 +173,7 @@ ca: new: copy: Còpia select: Selecciona quines dades vols duplicar - title: Duplica procés participatiu + title: Duplicar el procés participatiu participatory_process_groups: destroy: error: Hi ha hagut un error eliminant aquest grup de processos participatius. @@ -286,7 +286,7 @@ ca: success: El procés participatiu s'ha actualitzat correctament. participatory_processes_copies: create: - error: S'ha produït un error en duplicar el procés participatiu. + error: S'ha produït un error en duplicar aquest procés participatiu. success: El procés participatiu ha estat duplicat correctament. participatory_processes_group: create: diff --git a/decidim-participatory_processes/config/locales/cs.yml b/decidim-participatory_processes/config/locales/cs.yml index b35d2c172427f..85c8545027cd7 100644 --- a/decidim-participatory_processes/config/locales/cs.yml +++ b/decidim-participatory_processes/config/locales/cs.yml @@ -178,8 +178,6 @@ cs: participatory_process_copies: new: copy: Kopírovat - select: Vyberte, která data chcete duplikovat - title: Duplicitní participační proces participatory_process_groups: destroy: error: Došlo k chybě při zrušení skupiny účastnických procesů. @@ -291,10 +289,6 @@ cs: update: error: Při aktualizaci tohoto participačního procesu došlo k chybě. success: Participační proces byl úspěšně aktualizován. - participatory_processes_copies: - create: - error: Při kopírování tohoto participačního procesu došlo k chybě. - success: Účastnící proces byl úspěšně zdvojen. participatory_processes_group: create: error: Došlo k chybě při vytváření nové skupiny účastnických procesů. diff --git a/decidim-participatory_processes/config/locales/de.yml b/decidim-participatory_processes/config/locales/de.yml index 26a4779360906..d6c5b4ef42a7c 100644 --- a/decidim-participatory_processes/config/locales/de.yml +++ b/decidim-participatory_processes/config/locales/de.yml @@ -172,7 +172,6 @@ de: participatory_process_copies: new: copy: Kopieren - select: Wählen Sie aus, welche Daten Sie duplizieren möchten title: Beteiligungsprozess duplizieren participatory_process_groups: destroy: @@ -271,7 +270,7 @@ de: participatory_processes: create: error: Beim Erstellen eines neuen Beteiligungsprozesses ist ein Fehler aufgetreten. - success: Partizipativer Prozess erfolgreich erstellt. Konfigurieren Sie jetzt seine Schritte. + success: Beteiligungsprozess erfolgreich erstellt. Konfigurieren Sie als nächstes die Phasen. edit: update: Aktualisieren index: @@ -281,14 +280,10 @@ de: unpublished: Nicht veröffentlicht new: create: Erstellen - title: Neuer partizipativer Prozess + title: Neuer Beteiligungsprozess update: error: Beim Aktualisieren dieses Beteiligungsprozesses ist ein Fehler aufgetreten. success: Participatory-Prozess erfolgreich aktualisiert. - participatory_processes_copies: - create: - error: Beim Duplizieren dieses Beteiligungsprozesses ist ein Fehler aufgetreten. - success: Partizipativer Prozess erfolgreich dupliziert. participatory_processes_group: create: error: Beim Erstellen einer neuen partizipativen Prozessgruppe ist ein Fehler aufgetreten. @@ -349,7 +344,7 @@ de: participatory_processes: contextual: "

Ein Partizipationsprozess ist eine Folge von Partizipations-Aktivitäten (z.B. zuerst eine Umfrage ausfüllen, dann Vorschläge machen, diese face-to-face oder an virtuellen Sitzungen diskutieren, und sie schließlich zu priorisieren) mit dem Ziel, ein bestimmtes Thema zu definieren und Entscheidungen dazu zu treffen.

Beispiele für partizipatorische Prozesse sind: ein Verfahren zur Wahl von Ausschussmitgliedern (bei dem die Kandidaturen zuerst präsentiert und anschließend debattiert werden, und schließlich eine Kandidatur ausgewählt wird), die partizipative Budgets (bei denen Vorschläge gemacht und wirtschaftlich bewertet werden und über das verfügbare Geld abgestimmt wird), ein strategischer Planungsprozess, die gemeinschaftliche Ausarbeitung einer Vorschrift oder Norm, die Gestaltung eines städtischen Raums oder die Erstellung einer öffentlichen Strategie.

\n" page: "

Ein Partizipationsprozess ist eine Folge von Partizipations-Aktivitäten (z.B. zuerst eine Umfrage ausfüllen, dann Vorschläge machen, diese face-to-face oder an virtuellen Sitzungen diskutieren, und sie schließlich zu priorisieren) mit dem Ziel, ein bestimmtes Thema zu definieren und Entscheidungen dazu zu treffen.

Beispiele für partizipatorische Prozesse sind: ein Verfahren zur Wahl von Ausschussmitgliedern (bei dem die Kandidaturen zuerst präsentiert und anschließend debattiert werden, und schließlich eine Kandidatur ausgewählt wird), die partizipative Budgets (bei denen Vorschläge gemacht und wirtschaftlich bewertet werden und über das verfügbare Geld abgestimmt wird), ein strategischer Planungsprozess, die gemeinschaftliche Ausarbeitung einer Vorschrift oder Norm, die Gestaltung eines städtischen Raums oder die Erstellung einer öffentlichen Strategie.

\n" - title: Was ist ein partizipativer Prozess? + title: Was ist ein Beteiligungsprozess? log: value_types: participatory_process_type_presenter: @@ -451,7 +446,7 @@ de: index: title: Beteiligungsprozesse last_activity: - new_participatory_process: 'Neuer partizipativer Prozess:' + new_participatory_process: 'Neuer Beteiligungsprozess:' pages: home: highlighted_processes: diff --git a/decidim-participatory_processes/config/locales/el.yml b/decidim-participatory_processes/config/locales/el.yml index c6eab0d89893a..70d5eb6b39c16 100644 --- a/decidim-participatory_processes/config/locales/el.yml +++ b/decidim-participatory_processes/config/locales/el.yml @@ -166,8 +166,6 @@ el: participatory_process_copies: new: copy: Αντιγραφή - select: Επιλέξτε ποια δεδομένα θέλετε να αντιγράψετε - title: Αντιγραφή διαδικασίας συμμετοχής participatory_process_groups: destroy: error: Παρουσιάστηκε σφάλμα κατά την καταστροφή της ομάδας διαδικασιών συμμετοχής. @@ -271,10 +269,6 @@ el: update: error: Υπήρξε ένα πρόβλημα κατά την ενημέρωση αυτής της διαδικασίας συμμετοχής. success: Η διαδικασία συμμετοχής ενημερώθηκε με επιτυχία. - participatory_processes_copies: - create: - error: Υπήρξε ένα πρόβλημα κατά την αντιγραφή αυτής της διαδικασίας συμμετοχής. - success: Η διαδικασία συμμετοχής αντιγράφτηκε με επιτυχία. participatory_processes_group: create: error: Υπήρξε ένα πρόβλημα κατά τη δημιουργία μιας νέας ομάδας διαδικασίας συμμετοχής. diff --git a/decidim-participatory_processes/config/locales/es-MX.yml b/decidim-participatory_processes/config/locales/es-MX.yml index 4393aa9b9759e..5280fde8178cb 100644 --- a/decidim-participatory_processes/config/locales/es-MX.yml +++ b/decidim-participatory_processes/config/locales/es-MX.yml @@ -172,8 +172,8 @@ es-MX: participatory_process_copies: new: copy: Copiar - select: Seleccione los datos que desea duplicar - title: Duplicar proceso participativo + select: Selecciona qué datos quieres duplicar + title: Duplicar el proceso participativo participatory_process_groups: destroy: error: Se ha producido un error al eliminar el grupo de procesos participativos. @@ -287,8 +287,8 @@ es-MX: success: El proceso participativo se ha actualizado correctamente. participatory_processes_copies: create: - error: Hubo un error al duplicar este proceso participativo. - success: El proceso participativo se duplicó con éxito. + error: Se ha producido un error al duplicar este proceso participativo. + success: El proceso participativo se ha duplicado correctamente. participatory_processes_group: create: error: Se ha producido un error al crear este grupo. diff --git a/decidim-participatory_processes/config/locales/es-PY.yml b/decidim-participatory_processes/config/locales/es-PY.yml index 7b964cf760b31..fd76a83ea46e5 100644 --- a/decidim-participatory_processes/config/locales/es-PY.yml +++ b/decidim-participatory_processes/config/locales/es-PY.yml @@ -172,8 +172,8 @@ es-PY: participatory_process_copies: new: copy: Copiar - select: Seleccione los datos que desea duplicar - title: Duplicar proceso participativo + select: Selecciona qué datos quieres duplicar + title: Duplicar el proceso participativo participatory_process_groups: destroy: error: Se ha producido un error al eliminar el grupo de procesos participativos. @@ -287,8 +287,8 @@ es-PY: success: El proceso participativo se ha actualizado correctamente. participatory_processes_copies: create: - error: Hubo un error al duplicar este proceso participativo. - success: El proceso participativo se duplicó con éxito. + error: Se ha producido un error al duplicar este proceso participativo. + success: El proceso participativo se ha duplicado correctamente. participatory_processes_group: create: error: Se ha producido un error al crear este grupo. diff --git a/decidim-participatory_processes/config/locales/es.yml b/decidim-participatory_processes/config/locales/es.yml index 80b983e6e5e89..afafaf639d071 100644 --- a/decidim-participatory_processes/config/locales/es.yml +++ b/decidim-participatory_processes/config/locales/es.yml @@ -172,8 +172,8 @@ es: participatory_process_copies: new: copy: Copiar - select: Selecciona los datos que deseas duplicar - title: Duplicar proceso participativo + select: Selecciona qué datos quieres duplicar + title: Duplicar el proceso participativo participatory_process_groups: destroy: error: Se ha producido un error al eliminar el grupo de procesos participativos. diff --git a/decidim-participatory_processes/config/locales/eu.yml b/decidim-participatory_processes/config/locales/eu.yml index bd20374772f09..c6f1058f63aca 100644 --- a/decidim-participatory_processes/config/locales/eu.yml +++ b/decidim-participatory_processes/config/locales/eu.yml @@ -172,8 +172,6 @@ eu: participatory_process_copies: new: copy: Kopiatu - select: Aukeratu itzazu kopiatu nahi dituzun datuak - title: Bikoiztu partaidetza-prozesua participatory_process_groups: destroy: error: Arazo bat izan da partaidetza-prozesu multzoa ezabatzean. @@ -285,10 +283,6 @@ eu: update: error: Arazo bat egon da prozesu partaidetza-prozesu hau eguneratzean. success: Partaidetza-prozesua zuzen eguneratua. - participatory_processes_copies: - create: - error: Arazo bat egon da partaidetza-prozesu hau bikoiztean. - success: Partaidetza-prozesua zuzen kopiatua. participatory_processes_group: create: error: Arazo bat egon da beste partaidetza-prozesuen multzo bat sortzean. diff --git a/decidim-participatory_processes/config/locales/fi-plain.yml b/decidim-participatory_processes/config/locales/fi-plain.yml index 02cb4bd4f576b..14ef73514036d 100644 --- a/decidim-participatory_processes/config/locales/fi-plain.yml +++ b/decidim-participatory_processes/config/locales/fi-plain.yml @@ -173,7 +173,7 @@ fi-pl: new: copy: Kopio select: Valitse, mitkä tiedot haluat kopioida - title: Kopioi osallisuusprosessi + title: Kopioi osallistumisprosessi participatory_process_groups: destroy: error: Osallisuusprosessien ryhmän poisto epäonnistui. @@ -286,8 +286,8 @@ fi-pl: success: Osallisuusprosessi päivitetty onnistuneesti. participatory_processes_copies: create: - error: Tämän osallisuusprosessin kopioinnissa tapahtui virhe. - success: Osallisuusprosessi kopioitu onnistuneesti. + error: Osallistumisprosessin kopiointi epäonnistui. + success: Osallistumisprosessin kopiointi onnistui. participatory_processes_group: create: error: Uuden osallisuusprosessiryhmän luonnissa tapahtui virhe. diff --git a/decidim-participatory_processes/config/locales/fr-CA.yml b/decidim-participatory_processes/config/locales/fr-CA.yml index aa00ded26297b..0a4b195399806 100644 --- a/decidim-participatory_processes/config/locales/fr-CA.yml +++ b/decidim-participatory_processes/config/locales/fr-CA.yml @@ -173,7 +173,7 @@ fr-CA: new: copy: Copier select: Sélectionnez les données que vous souhaitez dupliquer - title: Dupliquer la concertation + title: Dupliquer le processus participatif participatory_process_groups: destroy: error: Une erreur s'est produite lors de la suppression du groupe de concertations. @@ -286,8 +286,8 @@ fr-CA: success: Concertation mise à jour avec succès. participatory_processes_copies: create: - error: Une erreur s'est produite lors de la duplication de cette concertation. - success: Concertation dupliquée avec succès. + error: Un problème est survenu lors de la duplication de ce processus participatif. + success: Processus participatif dupliqué avec succès. participatory_processes_group: create: error: Une erreur s'est produite lors de la création d'un nouveau groupe de concertations. diff --git a/decidim-participatory_processes/config/locales/fr.yml b/decidim-participatory_processes/config/locales/fr.yml index bda1f7b3b5e91..2c22d9169adf3 100644 --- a/decidim-participatory_processes/config/locales/fr.yml +++ b/decidim-participatory_processes/config/locales/fr.yml @@ -173,7 +173,7 @@ fr: new: copy: Copier select: Sélectionnez les données que vous souhaitez dupliquer - title: Dupliquer la concertation + title: Dupliquer le processus participatif participatory_process_groups: destroy: error: Une erreur s'est produite lors de la suppression du groupe de concertations. @@ -286,8 +286,8 @@ fr: success: Concertation mise à jour avec succès. participatory_processes_copies: create: - error: Une erreur s'est produite lors de la duplication de cette concertation. - success: Concertation dupliquée avec succès. + error: Un problème est survenu lors de la duplication de ce processus participatif. + success: Processus participatif dupliqué avec succès. participatory_processes_group: create: error: Une erreur s'est produite lors de la création d'un nouveau groupe de concertations. diff --git a/decidim-participatory_processes/config/locales/gl.yml b/decidim-participatory_processes/config/locales/gl.yml index 333e0c0e8f454..55a310301d408 100644 --- a/decidim-participatory_processes/config/locales/gl.yml +++ b/decidim-participatory_processes/config/locales/gl.yml @@ -144,8 +144,6 @@ gl: participatory_process_copies: new: copy: Copiar - select: Seleccione os datos que desexa duplicar - title: Duplicar o proceso participativo participatory_process_groups: destroy: success: Grupo de proceso participativo eliminado con éxito. @@ -229,10 +227,6 @@ gl: update: error: Produciuse un erro ao actualizar este proceso participativo. success: Proceso participativo actualizado con éxito. - participatory_processes_copies: - create: - error: Produciuse un erro ao duplicar este proceso participativo. - success: O proceso participativo duplicouse con éxito. participatory_processes_group: create: error: Produciuse un erro ao crear un novo grupo de procesos participativos. diff --git a/decidim-participatory_processes/config/locales/hu.yml b/decidim-participatory_processes/config/locales/hu.yml index 824cd247d5e32..80b81a010b238 100644 --- a/decidim-participatory_processes/config/locales/hu.yml +++ b/decidim-participatory_processes/config/locales/hu.yml @@ -166,8 +166,6 @@ hu: participatory_process_copies: new: copy: Másolás - select: Válaszd ki, mely adatokat szeretnéd duplikálni - title: Részvételi folyamat duplikálása participatory_process_groups: destroy: error: Hiba történt a részvételi folyamatcsoport törlése során. @@ -271,10 +269,6 @@ hu: update: error: Hiba történt a részvételi folyamat frissítése során. success: Részvételi folyamat frissítése sikeres. - participatory_processes_copies: - create: - error: Hiba történt a részvételi folyamat duplikációja során. - success: Részvételi folyamat duplikálása sikeres. participatory_processes_group: create: error: Hiba történt egy új részvételi folyamat csoport létrehozása során. diff --git a/decidim-participatory_processes/config/locales/id-ID.yml b/decidim-participatory_processes/config/locales/id-ID.yml index 6b4cd5cc2b8ab..674d3147f353d 100644 --- a/decidim-participatory_processes/config/locales/id-ID.yml +++ b/decidim-participatory_processes/config/locales/id-ID.yml @@ -126,8 +126,6 @@ id: participatory_process_copies: new: copy: Salinan - select: Pilih data mana yang ingin Anda gandakan - title: Proses partisipatif duplikat participatory_process_groups: destroy: success: Kelompok proses partisipatif berhasil dihapus. @@ -207,10 +205,6 @@ id: update: error: Ada kesalahan saat memperbarui proses partisipatif ini. success: Proses partisipatif berhasil diperbarui. - participatory_processes_copies: - create: - error: Ada kesalahan saat menduplikasi proses partisipatif ini. - success: Proses partisipatif berhasil digandakan. participatory_processes_group: create: error: Terjadi kesalahan saat membuat grup proses partisipatif baru. diff --git a/decidim-participatory_processes/config/locales/is-IS.yml b/decidim-participatory_processes/config/locales/is-IS.yml index 722930a93439f..92949c84b852c 100644 --- a/decidim-participatory_processes/config/locales/is-IS.yml +++ b/decidim-participatory_processes/config/locales/is-IS.yml @@ -94,8 +94,6 @@ is: participatory_process_copies: new: copy: Afrita - select: Veldu hvaða gögn þú vilt afrita - title: Afrit þátttakandi ferli participatory_process_groups: edit: title: Breyta ferli hópsins diff --git a/decidim-participatory_processes/config/locales/it.yml b/decidim-participatory_processes/config/locales/it.yml index 53e1838877952..eabe132c4aab6 100644 --- a/decidim-participatory_processes/config/locales/it.yml +++ b/decidim-participatory_processes/config/locales/it.yml @@ -153,8 +153,6 @@ it: participatory_process_copies: new: copy: Copia - select: Scegli quale dato vuoi duplicare - title: Duplica il processo partecipativo participatory_process_groups: destroy: error: Si è verificato un errore durante la cancellazione del gruppo di processi partecipativi. @@ -255,10 +253,6 @@ it: update: error: Si è verificato un errore durante l'aggiornamento di questo processo partecipativo. success: OK, il processo partecipativo è stato aggiornato. - participatory_processes_copies: - create: - error: Si è verificato un errore cercando di duplicare questo processo partecipativo. - success: OK, il processo partecipativo è stato duplicato. participatory_processes_group: create: error: Si è verificato un errore cercando di creare il nuovo gruppo di processi partecipativi. diff --git a/decidim-participatory_processes/config/locales/ja.yml b/decidim-participatory_processes/config/locales/ja.yml index fab3126c72337..ed1996cea2239 100644 --- a/decidim-participatory_processes/config/locales/ja.yml +++ b/decidim-participatory_processes/config/locales/ja.yml @@ -169,8 +169,6 @@ ja: participatory_process_copies: new: copy: コピー - select: 複製したいデータを選択してください - title: 重複した参加型プロセス participatory_process_groups: destroy: error: 参加型プロセスグループの削除中にエラーが発生しました。 @@ -281,10 +279,6 @@ ja: update: error: この参加型プロセスの更新に問題がありました。 success: 参加型プロセスが正常に更新されました。 - participatory_processes_copies: - create: - error: この参加型プロセスの複製中に問題が発生しました。 - success: 参加型プロセスを複製しました。 participatory_processes_group: create: error: 新しい参加型プロセスグループの作成中に問題が発生しました。 diff --git a/decidim-participatory_processes/config/locales/lb.yml b/decidim-participatory_processes/config/locales/lb.yml index f10a4ea154bb1..b178be7d2c9c7 100644 --- a/decidim-participatory_processes/config/locales/lb.yml +++ b/decidim-participatory_processes/config/locales/lb.yml @@ -149,8 +149,6 @@ lb: participatory_process_copies: new: copy: Kopieren - select: Wählen Sie aus, welche Daten Sie duplizieren möchten - title: Beteiligungsprozess duplizieren participatory_process_groups: destroy: error: Beim Löschen der Beteiligungsprozessgruppe ist ein Fehler aufgetreten. diff --git a/decidim-participatory_processes/config/locales/lt.yml b/decidim-participatory_processes/config/locales/lt.yml index 4cebb92e1e17e..97e0b4dcd4d8a 100644 --- a/decidim-participatory_processes/config/locales/lt.yml +++ b/decidim-participatory_processes/config/locales/lt.yml @@ -172,8 +172,6 @@ lt: participatory_process_copies: new: copy: Kopijuoti - select: Pasirinkite, kuriuos duomenis norėtumėte dubliuoti - title: Dubliuoti dalyvaujamąjį procesą participatory_process_groups: destroy: error: Naikinant Dalyvaujamojo proceso grupę įvyko klaida. @@ -277,10 +275,6 @@ lt: update: error: Atnaujinant šį dalyvaujamąjį procesą iškilo problema. success: Dalyvaujamasis procesas atnaujintas. - participatory_processes_copies: - create: - error: Dubliuojant šį dalyvaujamąjį procesą iškilo problema. - success: Dalyvaujamasis procesas dubliuotas. participatory_processes_group: create: error: Kuriant naują dalyvaujamojo proceso grupę iškilo problema. diff --git a/decidim-participatory_processes/config/locales/lv.yml b/decidim-participatory_processes/config/locales/lv.yml index 1a2e9a4d5fb5e..f391e133936d1 100644 --- a/decidim-participatory_processes/config/locales/lv.yml +++ b/decidim-participatory_processes/config/locales/lv.yml @@ -135,8 +135,6 @@ lv: participatory_process_copies: new: copy: Kopēt - select: Atlasiet, kurus datus vēlaties dublēt - title: Dublēt līdzdalības procesu participatory_process_groups: destroy: error: Līdzdalības procesa grupas dzēšanas laikā radās kļūda. @@ -227,10 +225,6 @@ lv: update: error: Šī līdzdalības procesa atjaunināšanas laikā radās problēma. success: Līdzdalības process ir veiksmīgi atjaunināts. - participatory_processes_copies: - create: - error: Dublējot šo līdzdalības procesu, radās problēma. - success: Līdzdalības process ir veiksmīgi dublēts. participatory_processes_group: create: error: Jaunas līdzdalības procesa grupas izveides laikā radās problēma. diff --git a/decidim-participatory_processes/config/locales/nl.yml b/decidim-participatory_processes/config/locales/nl.yml index 8224140a52cfd..06706fa1ad19c 100644 --- a/decidim-participatory_processes/config/locales/nl.yml +++ b/decidim-participatory_processes/config/locales/nl.yml @@ -149,8 +149,6 @@ nl: participatory_process_copies: new: copy: Kopiëren - select: Selecteer welke gegevens u wilt dupliceren - title: Inspraakproces dupliceren participatory_process_groups: destroy: error: Er is een fout opgetreden tijdens het vernietigen van de Participatory procesgroep. @@ -241,10 +239,6 @@ nl: update: error: Er was een probleem bij het bijwerken van dit participatief proces. success: Participatief proces succesvol bijgewerkt. - participatory_processes_copies: - create: - error: Er is een fout opgetreden bij het dupliceren van dit participatief proces. - success: Participatief proces is succesvol gedupliceerd. participatory_processes_group: create: error: Er is een fout opgetreden bij het maken van een nieuwe groep voor participatieve processen. diff --git a/decidim-participatory_processes/config/locales/no.yml b/decidim-participatory_processes/config/locales/no.yml index 200b049886d78..81a0cff6961b6 100644 --- a/decidim-participatory_processes/config/locales/no.yml +++ b/decidim-participatory_processes/config/locales/no.yml @@ -172,8 +172,6 @@ participatory_process_copies: new: copy: Kopier - select: Velg hvilke data du vil kopiere - title: Kopier deltakende prosess participatory_process_groups: destroy: error: Det oppstod en feil under slettingen av prosessgruppen. @@ -285,10 +283,6 @@ update: error: Det oppstod et problem med å oppdatere denne prosessen. success: Prosessen er oppdatert. - participatory_processes_copies: - create: - error: Det oppstod et problem med å duplisere denne deltaker prosessen. - success: Prosessen er kopiert. participatory_processes_group: create: error: Det oppstod et problem med å lage en ny prosessgruppe. diff --git a/decidim-participatory_processes/config/locales/pl.yml b/decidim-participatory_processes/config/locales/pl.yml index e7a3ceeea7daa..a459f56167b75 100644 --- a/decidim-participatory_processes/config/locales/pl.yml +++ b/decidim-participatory_processes/config/locales/pl.yml @@ -178,8 +178,6 @@ pl: participatory_process_copies: new: copy: Kopiuj - select: Wybierz dane, które chcesz zduplikować - title: Duplikuj proces partycypacyjny participatory_process_groups: destroy: error: Wystąpił błąd podczas usuwania grupy procesu partycypacyjnego. @@ -291,10 +289,6 @@ pl: update: error: Wystąpił błąd podczas aktualizowania tego procesu partycypacyjnego. success: Proces partycypacyjny został zaktualizowany. - participatory_processes_copies: - create: - error: Podczas duplikowania tego procesu partycypacyjnego wystąpił błąd. - success: Proces partycypacyjny został zduplikowany. participatory_processes_group: create: error: Podczas tworzenia nowej grupy procesu partycypacyjnego wystąpił błąd. @@ -414,7 +408,7 @@ pl: json: JSON participatory_process_copies: form: - slug_help_html: 'Slugi URL wykorzystuje się do generowania adresów URL odsyłających do procesu. Można używać jedynie liter, cyfr i łączników, przy czym należy rozpocząć od litery. Przykład: %{url}' + slug_help_html: 'Slugi URL wykorzystuje się do generowania adresów URL odsyłających do tego procesu. Można używać jedynie liter, cyfr i łączników, przy czym należy rozpocząć od litery. Przykład: %{url}' participatory_process_groups: form: metadata: Metadane diff --git a/decidim-participatory_processes/config/locales/pt-BR.yml b/decidim-participatory_processes/config/locales/pt-BR.yml index c513e18496cf6..8c9efd681f960 100644 --- a/decidim-participatory_processes/config/locales/pt-BR.yml +++ b/decidim-participatory_processes/config/locales/pt-BR.yml @@ -160,8 +160,6 @@ pt-BR: participatory_process_copies: new: copy: cópia de - select: Selecione os dados que você gostaria de duplicar - title: Duplicar processo participativo participatory_process_groups: destroy: error: Ocorreu um erro ao destruir o grupo de processos participativos. @@ -261,10 +259,6 @@ pt-BR: update: error: Ocorreu um erro ao atualizar este processo participativo. success: Processo participativo atualizado com sucesso. - participatory_processes_copies: - create: - error: Ocorreu um erro ao duplicar este processo participativo. - success: Processo participativo duplicado com sucesso. participatory_processes_group: create: error: Ocorreu um erro ao criar um novo grupo de processos participativos. diff --git a/decidim-participatory_processes/config/locales/pt.yml b/decidim-participatory_processes/config/locales/pt.yml index 67a7c8b527772..4e91daf180556 100644 --- a/decidim-participatory_processes/config/locales/pt.yml +++ b/decidim-participatory_processes/config/locales/pt.yml @@ -150,8 +150,6 @@ pt: participatory_process_copies: new: copy: Copiar - select: Selecione os dados que pretende duplicar - title: Duplicar processo participativo participatory_process_groups: destroy: error: Ocorreu um erro ao destruir o grupo do processo Participativo. @@ -242,10 +240,6 @@ pt: update: error: Ocorreu um problema ao atualizar este processo participativo. success: Processo participativo atualizado corretamente. - participatory_processes_copies: - create: - error: Ocorreu um problema ao duplicar este processo participativo. - success: Processo participativo duplicado corretamente. participatory_processes_group: create: error: Ocorreu um problema ao criar um novo grupo de processo participativo. diff --git a/decidim-participatory_processes/config/locales/ro-RO.yml b/decidim-participatory_processes/config/locales/ro-RO.yml index 0428089d473ea..6df32cdfb182f 100644 --- a/decidim-participatory_processes/config/locales/ro-RO.yml +++ b/decidim-participatory_processes/config/locales/ro-RO.yml @@ -169,8 +169,6 @@ ro: participatory_process_copies: new: copy: Copiază - select: Selectează datele pe care dorești să le duplici - title: Duplicarea procesului participativ participatory_process_groups: destroy: error: A apărut o eroare la eliminarea grupului de proces participativ. @@ -274,10 +272,6 @@ ro: update: error: A apărut o eroare la actualizarea acestui proces participativ. success: Procesul participativ a fost actualizat cu succes. - participatory_processes_copies: - create: - error: A apărut o problemă la duplicarea acestui proces participativ. - success: Procesul participativ a fost duplicat cu succes. participatory_processes_group: create: error: A apărut o problemă la crearea unui nou grup implicat în procesul participativ. diff --git a/decidim-participatory_processes/config/locales/ru.yml b/decidim-participatory_processes/config/locales/ru.yml index 59f6ed6118f3a..4568eb8f439ff 100644 --- a/decidim-participatory_processes/config/locales/ru.yml +++ b/decidim-participatory_processes/config/locales/ru.yml @@ -131,8 +131,6 @@ ru: participatory_process_copies: new: copy: Копировать - select: Выберите, какие данные вы хотите продублировать - title: Создать копию движения соучастия participatory_process_groups: destroy: success: Группа движений соучастия успешно удалена. @@ -212,10 +210,6 @@ ru: update: error: При попытке обновить это движение соучастия произошла ошибка. success: Движение соучастия успешно обновлено. - participatory_processes_copies: - create: - error: При попытке создать копию этого движения соучастия произошла ошибка. - success: Движение соучастия успешно продублировано. participatory_processes_group: create: error: При попытке создать новую группу движений соучастия произошла ошибка. diff --git a/decidim-participatory_processes/config/locales/sk.yml b/decidim-participatory_processes/config/locales/sk.yml index a77ab66377cec..95392a06c1f9f 100644 --- a/decidim-participatory_processes/config/locales/sk.yml +++ b/decidim-participatory_processes/config/locales/sk.yml @@ -138,8 +138,6 @@ sk: participatory_process_copies: new: copy: kopírovať - select: Vyberte, ktoré dáta chcete duplikovať - title: Duplicitné participačné proces participatory_process_groups: destroy: error: Došlo k chybe pri zrušení skupiny účastníckych procesov. @@ -230,10 +228,6 @@ sk: update: error: Pri aktualizácii tohto participatívneho procesu došlo k chybe. success: Participatívne proces bol úspešne aktualizovaný. - participatory_processes_copies: - create: - error: Pri kopírovaní tohto participatívneho procesu došlo k chybe. - success: Účastníci proces bol úspešne zdvojený. participatory_processes_group: create: error: Došlo k chybe pri vytváraní novej skupiny účastníckych procesov. diff --git a/decidim-participatory_processes/config/locales/sv.yml b/decidim-participatory_processes/config/locales/sv.yml index 9d87a806244ed..a38ed0e3b95e1 100644 --- a/decidim-participatory_processes/config/locales/sv.yml +++ b/decidim-participatory_processes/config/locales/sv.yml @@ -172,8 +172,8 @@ sv: participatory_process_copies: new: copy: Kopiera - select: Välj vilka data som du vill duplicera - title: Duplicera process + select: Välj vilken data du vill duplicera + title: Duplicera deltagarprocess participatory_process_groups: destroy: error: Det gick inte att radera dialoggruppen. @@ -286,8 +286,8 @@ sv: success: Processen har uppdaterats. participatory_processes_copies: create: - error: Det gick inte att duplicera processen. - success: Processen har duplicerats. + error: Det gick inte att duplicera deltagarprocessen. + success: Deltagandeprocessen har duplicerats. participatory_processes_group: create: error: Det gick inte att skapa en ny grupp för processer. diff --git a/decidim-participatory_processes/config/locales/tr-TR.yml b/decidim-participatory_processes/config/locales/tr-TR.yml index 4bec551086221..54baef1404222 100644 --- a/decidim-participatory_processes/config/locales/tr-TR.yml +++ b/decidim-participatory_processes/config/locales/tr-TR.yml @@ -151,8 +151,6 @@ tr: participatory_process_copies: new: copy: kopya - select: Çoğaltmak istediğiniz verileri seçin - title: Yinelenen katılımcı süreç participatory_process_groups: destroy: error: Katılımcı süreç grubu yok edilirken bir hata oluştu. @@ -244,10 +242,6 @@ tr: update: error: Bu katılımcı süreci güncellerken bir hata oluştu. success: Katılımcı süreç başarıyla güncellendi. - participatory_processes_copies: - create: - error: Bu katılımcı süreci çoğaltırken bir hata oluştu. - success: Katılımcı süreç başarıyla kopyalandı. participatory_processes_group: create: error: Yeni bir katılımcı süreç grubu oluştururken bir hata oluştu. diff --git a/decidim-participatory_processes/config/locales/uk.yml b/decidim-participatory_processes/config/locales/uk.yml index 71ecaa0ec1f0b..fae861aa409ea 100644 --- a/decidim-participatory_processes/config/locales/uk.yml +++ b/decidim-participatory_processes/config/locales/uk.yml @@ -131,8 +131,6 @@ uk: participatory_process_copies: new: copy: Скопіювати - select: Оберіть відомості, копію яких ви хотіли б створити - title: Створити копію руху співучасті participatory_process_groups: destroy: success: Сукупність рухів співучасті успішно видалено. @@ -212,10 +210,6 @@ uk: update: error: При спробі оновити цей рух співучасті сталася помилка. success: Рух співучасті успішно оновлено. - participatory_processes_copies: - create: - error: При спробі створити копію цього руху співучасті сталася помилка. - success: Успішно створено копію руху співучасті. participatory_processes_group: create: error: При спробі створити нову сукупність рухів співучасті сталася помилка. diff --git a/decidim-participatory_processes/config/locales/zh-CN.yml b/decidim-participatory_processes/config/locales/zh-CN.yml index b6306f2448995..a5fdc7e3d64f0 100644 --- a/decidim-participatory_processes/config/locales/zh-CN.yml +++ b/decidim-participatory_processes/config/locales/zh-CN.yml @@ -129,8 +129,6 @@ zh-CN: participatory_process_copies: new: copy: 复制 - select: 选择要重复的数据 - title: 重复的参与进程 participatory_process_groups: destroy: error: 销毁参与性进程小组时出错。 @@ -221,10 +219,6 @@ zh-CN: update: error: 在更新这一参与进程方面存在问题。 success: 参与进程已成功更新。 - participatory_processes_copies: - create: - error: 出现了重复这一参与进程的问题。 - success: 参与进程成功地重复。 participatory_processes_group: create: error: 建立一个新的参与性进程小组存在问题。 diff --git a/decidim-participatory_processes/config/locales/zh-TW.yml b/decidim-participatory_processes/config/locales/zh-TW.yml index bedb0ed9ae9cc..e1a3dcab9348b 100644 --- a/decidim-participatory_processes/config/locales/zh-TW.yml +++ b/decidim-participatory_processes/config/locales/zh-TW.yml @@ -163,8 +163,6 @@ zh-TW: participatory_process_copies: new: copy: 複製 - select: 請選擇您想要複製的資料 - title: 重複的參與性流程 participatory_process_groups: destroy: error: 刪除參與性流程群組時發生錯誤。 @@ -268,10 +266,6 @@ zh-TW: update: error: 更新此參與程序時出現問題。 success: 參與程序已成功更新。 - participatory_processes_copies: - create: - error: 複製參與流程時發生問題。 - success: 成功複製參與過程。 participatory_processes_group: create: error: 創建新參與程序群組時出現問題。 From 5c58e8daa9dfc66f006342aaca4f62737289961b Mon Sep 17 00:00:00 2001 From: decidim-bot Date: Mon, 25 Aug 2025 15:33:10 +0200 Subject: [PATCH 18/61] Change minimun_characters to minimum_characters (#15078) Co-authored-by: Alexander Rusa --- decidim-core/config/locales/bg-BG.yml | 4 ++-- decidim-core/config/locales/fr-LU.yml | 6 +++--- decidim-core/config/locales/lb-LU.yml | 4 ++-- decidim-core/config/locales/sk-SK.yml | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/decidim-core/config/locales/bg-BG.yml b/decidim-core/config/locales/bg-BG.yml index 574408508a9dd..8c056f8027858 100644 --- a/decidim-core/config/locales/bg-BG.yml +++ b/decidim-core/config/locales/bg-BG.yml @@ -405,7 +405,7 @@ bg: newsletter: Получавайте извънреден бюлетин с подходяща информация newsletter_title: Разрешение за контакт с Вас nickname_help: Вашя псевдоним в %{organization} - password_help: "Минимум %{minimun_characters} символа, не трябва да са разпространени (например, 123456) и трябва да са различни от вашите име и ел. поща." + password_help: "Минимум %{minimum_characters} символа, не трябва да са разпространени (например, 123456) и трябва да са различни от вашите име и ел. поща." sign_in: Вход sign_up: Регистрация sign_up_as: @@ -1315,7 +1315,7 @@ bg: change_your_password: Промени паролата си confirm_new_password: Потвърди нова парола new_password: Нова парола - password_help: "Минимум %{minimun_characters} символа, не трябва да са разпространени (например, 123456) и трябва да са различни от вашите име и ел. поща." + password_help: "Минимум %{minimum_characters} символа, не трябва да са разпространени (например, 123456) и трябва да са различни от вашите име и ел. поща." new: forgot_your_password: Забравили сте паролата си? send_me_reset_password_instructions: Изпрати ми инструкции за възстановяване на паролата diff --git a/decidim-core/config/locales/fr-LU.yml b/decidim-core/config/locales/fr-LU.yml index 23d4ff326022e..82652ce427b9c 100644 --- a/decidim-core/config/locales/fr-LU.yml +++ b/decidim-core/config/locales/fr-LU.yml @@ -452,7 +452,7 @@ fr-LU: newsletter: Recevoir une newsletter sur l'actualité de la plateforme newsletter_title: Autorisation de contact nickname_help: Cet identifiant est public et unique sur %{organization} ; il permet aux autres utilisateurs de vous suivre, de vous contacter ou de vous mentionner dans leurs commentaires. Il s’affichera précédé d’un « @ » ; il ne doit pas contenir d’espace ni de caractère spécial. - password_help: "%{minimun_characters} caractères minimum, ne doit pas être trop commun (par exemple 123456) et doit être différent de votre pseudo et de votre email." + password_help: "%{minimum_characters} caractères minimum, ne doit pas être trop commun (par exemple 123456) et doit être différent de votre pseudo et de votre email." sign_in: Se connecter sign_up: S'inscrire sign_up_as: @@ -1455,7 +1455,7 @@ fr-LU: change_your_password: changez votre mot de passe confirm_new_password: Confirmer le nouveau mot de passe new_password: Nouveau mot de passe - password_help: "%{minimun_characters} caractères minimum, ne doit pas être trop commun (par exemple 123456) et doit être différent de votre pseudo et de votre email." + password_help: "%{minimum_characters} caractères minimum, ne doit pas être trop commun (par exemple 123456) et doit être différent de votre pseudo et de votre email." new: forgot_your_password: Mot de passe oublié? send_me_reset_password_instructions: Envoyez-moi les instructions de réinitialisation du mot de passe @@ -1472,7 +1472,7 @@ fr-LU: currently_waiting_confirmation_for_email: 'En attente de confirmation pour: %{email}' leave_blank_if_you_don_t_want_to_change_it: laisser vide si vous ne voulez pas le changer title: Modifier %{resource} - unhappy: Pas satisfait ? + unhappy: Pas satisfait ? update: Mettre à jour we_need_your_current_password_to_confirm_your_changes: nous avons besoin de votre mot de passe actuel pour confirmer vos modifications new: diff --git a/decidim-core/config/locales/lb-LU.yml b/decidim-core/config/locales/lb-LU.yml index 0995d6ed83599..20cd09e05ce7b 100644 --- a/decidim-core/config/locales/lb-LU.yml +++ b/decidim-core/config/locales/lb-LU.yml @@ -427,7 +427,7 @@ lb: newsletter: Gelegentlich einen Newsletter mit relevanten Informationen erhalten newsletter_title: Kontakterlaubnis nickname_help: Ihr Pseudonym in %{organization}. Kann nur Buchstaben, Zahlen, '-' und '_' enthalten. - password_help: "Mindestens %{minimun_characters} Zeichen, nicht zu gewöhnlich (z.B. 123456) und darf nicht Ihr Benutzername oder Ihre E-Mail-Adresse sein." + password_help: "Mindestens %{minimum_characters} Zeichen, nicht zu gewöhnlich (z.B. 123456) und darf nicht Ihr Benutzername oder Ihre E-Mail-Adresse sein." sign_in: Anmelden sign_up: Registrieren sign_up_as: @@ -1245,7 +1245,7 @@ lb: change_your_password: Ändern Sie Ihr Passwort confirm_new_password: Bestätige neues Passwort new_password: Neues Passwort - password_help: "Mindestens %{minimun_characters} Zeichen, nicht zu gewöhnlich (z. B. 123456) und darf nicht Ihr Benutzername oder Ihre E-Mail-Adresse sein." + password_help: "Mindestens %{minimum_characters} Zeichen, nicht zu gewöhnlich (z. B. 123456) und darf nicht Ihr Benutzername oder Ihre E-Mail-Adresse sein." new: forgot_your_password: Haben Sie Ihr Passwort vergessen? send_me_reset_password_instructions: Senden Sie mir das Passwort zurück diff --git a/decidim-core/config/locales/sk-SK.yml b/decidim-core/config/locales/sk-SK.yml index 80be60edd4e43..97ef3c95be801 100644 --- a/decidim-core/config/locales/sk-SK.yml +++ b/decidim-core/config/locales/sk-SK.yml @@ -409,7 +409,7 @@ sk: newsletter: Dostávajte príležitostný spravodajca s relevantnými informáciami newsletter_title: Povolenie kontaktu nickname_help: Vaša prezývka v %{organization} - password_help: "%{minimun_characters} minimum znakov, nesmie byť príliš bežné (napr. 123456) a musí byť iné ako vaše prezývka a váš e-mail." + password_help: "%{minimum_characters} minimum znakov, nesmie byť príliš bežné (napr. 123456) a musí byť iné ako vaše prezývka a váš e-mail." sign_in: Prihlásiť sa sign_up: Zaregistrovať sa sign_up_as: @@ -1225,7 +1225,7 @@ sk: change_your_password: Zmeňte si svoje heslo confirm_new_password: Potvrďte nové heslo new_password: Nové heslo - password_help: "%{minimun_characters} je minimum znakov, nesmie byť príliš bežné (napr. 123456) a musí byť iné ako vaše prezývka a váš e-mail." + password_help: "%{minimum_characters} je minimum znakov, nesmie byť príliš bežné (napr. 123456) a musí byť iné ako vaše prezývka a váš e-mail." new: forgot_your_password: Zabudli ste heslo? send_me_reset_password_instructions: Pošlite mi pokyny na resetovanie hesla @@ -1408,7 +1408,7 @@ sk: dropdown: choose_diff_view_html: 'Mód zobrazenia HTML:' choose_diff_view_mode: 'Mód srovnávacího zobrazení:' - option_escaped: Nepoužívané + option_escaped: Nepoužívané option_split: Vedle sebe (Sbs) option_unescaped: Používané option_unified: Sjednocené From 45407fb6e239f3102f96057e0d05f88a37829013 Mon Sep 17 00:00:00 2001 From: decidim-bot Date: Thu, 28 Aug 2025 19:12:37 +0200 Subject: [PATCH 19/61] Fix aria-current on active filter in search page (#15110) Co-authored-by: stephanierousset <61418966+Stef-Rousset@users.noreply.github.com> --- decidim-core/app/views/decidim/searches/_filters.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/decidim-core/app/views/decidim/searches/_filters.html.erb b/decidim-core/app/views/decidim/searches/_filters.html.erb index 9ca5543f680d6..83e62b8c93621 100644 --- a/decidim-core/app/views/decidim/searches/_filters.html.erb +++ b/decidim-core/app/views/decidim/searches/_filters.html.erb @@ -14,7 +14,7 @@