decidim-core 0.31.4 → 0.31.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/content_blocks/html_cell.rb +1 -1
  3. data/app/cells/decidim/content_blocks/static_page/section_cell.rb +1 -1
  4. data/app/cells/decidim/content_blocks/static_page/summary_cell.rb +1 -1
  5. data/app/cells/decidim/content_blocks/static_page/two_pane_section_cell.rb +2 -2
  6. data/app/cells/decidim/data_consent/category.erb +5 -5
  7. data/app/cells/decidim/upload_modal_cell.rb +5 -0
  8. data/app/controllers/decidim/download_your_data_controller.rb +1 -1
  9. data/app/controllers/decidim/notifications_subscriptions_controller.rb +8 -0
  10. data/app/controllers/decidim/private_downloads_controller.rb +29 -0
  11. data/app/models/decidim/attachment.rb +20 -2
  12. data/app/models/decidim/authorization.rb +7 -0
  13. data/app/models/decidim/private_download.rb +61 -0
  14. data/app/models/decidim/private_export.rb +6 -0
  15. data/app/packs/src/decidim/sw/push-permissions.js +48 -13
  16. data/app/services/decidim/notifications_subscriptions_persistor.rb +6 -0
  17. data/app/services/decidim/push_subscription_endpoint_validator.rb +34 -0
  18. data/app/services/decidim/send_push_notification.rb +5 -1
  19. data/app/views/decidim/notifications_settings/show.html.erb +5 -5
  20. data/config/locales/ca-IT.yml +1 -0
  21. data/config/locales/ca.yml +1 -0
  22. data/config/locales/cs.yml +2 -0
  23. data/config/locales/en.yml +1 -0
  24. data/config/locales/es-MX.yml +1 -0
  25. data/config/locales/es-PY.yml +1 -0
  26. data/config/locales/es.yml +1 -0
  27. data/config/locales/eu.yml +1 -0
  28. data/config/locales/fi.yml +2 -2
  29. data/config/locales/fr-CA.yml +1 -0
  30. data/config/locales/fr.yml +1 -0
  31. data/config/locales/it.yml +10 -0
  32. data/config/locales/pt-BR.yml +1 -1
  33. data/config/locales/sk.yml +1417 -0
  34. data/config/locales/sv.yml +1 -0
  35. data/config/routes.rb +1 -0
  36. data/lib/decidim/core/version.rb +1 -1
  37. metadata +9 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 513dc9560cc17e12439bcef89efbb034edc6300b791810f2037bbb64191805a6
4
- data.tar.gz: 99d7f076a772765592d34f5164659c5f4d0826edb4d146d5917fff28e02dc922
3
+ metadata.gz: 62bd053e6fd5eba91bd7b08d3c502accbe3a13d9045cab68091035b0c95b2964
4
+ data.tar.gz: 1376e77830f2ec0e1f96c1503caa458f4c834d956f3e189e5d1176ad3403f430
5
5
  SHA512:
6
- metadata.gz: d8928571dc994667af951c63981ce2472ce1be5db0c477beb27cadfc2ac43b6cfa87f1a8ab6854483cd00ce46fb1856adacf593ad4e159e7e1a46dc3ae099b87
7
- data.tar.gz: e03706f2aadaac2165c6a513a5b1284011bda181ab5a29683fe5983e616c880fd9c7a6ff15cbf1619fb6d6f658eb30df40e4d151cb81f12b0bdbd16c7848b224
6
+ metadata.gz: 4beb29f27737221e77294185ea9b1d1fde88cb3296cd21691cc91ad2477117669b20b3f8369a80d7e9443594d516701462134ef77936433b4d372571ddaf414a
7
+ data.tar.gz: 51aafb46408f1a5cb3067e73b9559661b10c8573a46d5fb3036b576e94a998d9f98952da18cb0330d67d1379c7d7e568b4e9f9c9c22a2c9e30a4a998e5484991
@@ -8,7 +8,7 @@ module Decidim
8
8
  end
9
9
 
10
10
  def html_content
11
- translated_attribute(model.settings.html_content).html_safe
11
+ decidim_sanitize_editor_admin(translated_attribute(model.settings.html_content))
12
12
  end
13
13
  end
14
14
  end
@@ -5,7 +5,7 @@ module Decidim
5
5
  module StaticPage
6
6
  class SectionCell < Decidim::ViewModel
7
7
  def content
8
- translated_attribute(model.settings.content).html_safe
8
+ decidim_sanitize_editor_admin(translated_attribute(model.settings.content))
9
9
  end
10
10
  end
11
11
  end
@@ -5,7 +5,7 @@ module Decidim
5
5
  module StaticPage
6
6
  class SummaryCell < Decidim::ViewModel
7
7
  def content
8
- translated_attribute(model.settings.summary).html_safe
8
+ decidim_sanitize_editor_admin(translated_attribute(model.settings.summary))
9
9
  end
10
10
  end
11
11
  end
@@ -5,11 +5,11 @@ module Decidim
5
5
  module StaticPage
6
6
  class TwoPaneSectionCell < Decidim::ViewModel
7
7
  def left_column
8
- translated_attribute(model.settings.left_column).html_safe
8
+ decidim_sanitize_editor_admin(translated_attribute(model.settings.left_column))
9
9
  end
10
10
 
11
11
  def right_column
12
- translated_attribute(model.settings.right_column).html_safe
12
+ decidim_sanitize_editor_admin(translated_attribute(model.settings.right_column))
13
13
  end
14
14
  end
15
15
  end
@@ -13,12 +13,12 @@
13
13
  <%= icon "close-line", class: "cookies__category-toggle-icon" %>
14
14
  </label>
15
15
 
16
- <div id="accordion-trigger-<%= category[:slug] %>" data-controls="accordion-panel-<%= category[:slug] %>" aria-labelledby="accordion-title-<%= category[:slug] %>">
17
- <h3 id="accordion-title-<%= category[:slug] %>" class="cookies__category-trigger-title">
18
- <%= category[:title] %>
19
- </h3>
16
+ <h3 id="accordion-title-<%= category[:slug] %>" class="cookies__category-trigger-title">
17
+ <%= category[:title] %>
18
+ </h3>
20
19
 
21
- <span>
20
+ <div id="accordion-trigger-<%= category[:slug] %>" role="group" data-controls="accordion-panel-<%= category[:slug] %>" aria-labelledby="accordion-title-<%= category[:slug] %>">
21
+ <span aria-hidden="true">
22
22
  <%= icon "arrow-down-s-line", class: "cookies__category-trigger-arrow" %>
23
23
  <%= icon "arrow-up-s-line", class: "cookies__category-trigger-arrow" %>
24
24
  </span>
@@ -177,6 +177,11 @@ module Decidim
177
177
 
178
178
  def file_attachment_path(attachment)
179
179
  return unless attachment
180
+
181
+ if attachment.respond_to?(:record) && attachment.record.is_a?(Decidim::Authorization) && attachment.name.to_s == "verification_attachment"
182
+ return decidim.private_download_path(Decidim::PrivateDownload.for(attachment.record, attachment_name: attachment.name).token)
183
+ end
184
+
180
185
  return Rails.application.routes.url_helpers.rails_blob_url(attachment, only_path: true) if attachment.is_a? ActiveStorage::Blob
181
186
 
182
187
  if attachment.try(:attached?)
@@ -50,7 +50,7 @@ module Decidim
50
50
  flash[:error] = t("decidim.account.download_your_data_export.export_expired")
51
51
  redirect_to download_your_data_path
52
52
  elsif private_export.file.attached?
53
- redirect_to Rails.application.routes.url_helpers.rails_blob_url(private_export.file.blob, only_path: true)
53
+ redirect_to private_download_path(Decidim::PrivateDownload.for(private_export, attachment_name: :file).token)
54
54
  else
55
55
  flash[:error] = t("decidim.account.download_your_data_export.file_no_exists")
56
56
  redirect_to download_your_data_path
@@ -3,6 +3,8 @@
3
3
  module Decidim
4
4
  # The controller to handle the subscriptions to push notifications
5
5
  class NotificationsSubscriptionsController < Decidim::ApplicationController
6
+ rescue_from Decidim::NotificationsSubscriptionsPersistor::UnsupportedPushSubscriptionEndpointError, with: :unsupported_browser
7
+
6
8
  def create
7
9
  Decidim::NotificationsSubscriptionsPersistor.new(current_user).add_subscription(params)
8
10
  head :ok
@@ -12,5 +14,11 @@ module Decidim
12
14
  Decidim::NotificationsSubscriptionsPersistor.new(current_user).delete_subscription(params[:auth])
13
15
  head :ok
14
16
  end
17
+
18
+ private
19
+
20
+ def unsupported_browser
21
+ render json: { error: I18n.t("notifications_settings.show.push_notifications_unsupported_browser", scope: "decidim") }, status: :unprocessable_entity
22
+ end
15
23
  end
16
24
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ class PrivateDownloadsController < Decidim::ApplicationController
5
+ before_action :authenticate_user!
6
+
7
+ def show
8
+ return head :not_found unless private_download.attached?
9
+ return head :not_found unless private_download.authorized_for?(current_user)
10
+
11
+ disposition = private_download.attachment.content_type.start_with?("image/") ? :inline : :attachment
12
+
13
+ send_data(
14
+ private_download.attachment.download,
15
+ filename: private_download.attachment.filename.to_s,
16
+ type: private_download.attachment.content_type,
17
+ disposition:
18
+ )
19
+ rescue Decidim::PrivateDownload::InvalidTokenError
20
+ head :not_found
21
+ end
22
+
23
+ private
24
+
25
+ def private_download
26
+ @private_download ||= Decidim::PrivateDownload.from_token(params[:id])
27
+ end
28
+ end
29
+ end
@@ -88,7 +88,7 @@ module Decidim
88
88
  # Returns String.
89
89
  def file_type
90
90
  if file?
91
- url&.split(".")&.last&.split("&")&.first&.downcase
91
+ file.filename.extension&.downcase
92
92
  elsif link?
93
93
  "link"
94
94
  end
@@ -100,7 +100,13 @@ module Decidim
100
100
  def url
101
101
  @url ||=
102
102
  if file?
103
- attached_uploader(:file).url
103
+ if private_download_required?
104
+ Decidim::Core::Engine.routes.url_helpers.private_download_path(
105
+ Decidim::PrivateDownload.for(self, attachment_name: :file).token
106
+ )
107
+ else
108
+ attached_uploader(:file).url
109
+ end
104
110
  elsif link?
105
111
  link
106
112
  end
@@ -144,5 +150,17 @@ module Decidim
144
150
 
145
151
  attached_to.can_participate?(user)
146
152
  end
153
+
154
+ def private_download_authorized?(user, requested_attachment_name)
155
+ return false unless requested_attachment_name.to_s == "file"
156
+
157
+ can_participate?(user)
158
+ end
159
+
160
+ def private_download_required?
161
+ return attached_to.private_space? if attached_to.respond_to?(:private_space?)
162
+
163
+ attached_to.respond_to?(:component) && attached_to.component&.private_non_transparent_space?
164
+ end
147
165
  end
148
166
  end
@@ -91,6 +91,13 @@ module Decidim
91
91
  Decidim::AuthorizationTransfer.perform!(self, handler)
92
92
  end
93
93
 
94
+ def private_download_authorized?(user, requested_attachment_name)
95
+ return false unless requested_attachment_name.to_s == "verification_attachment"
96
+ return true if user&.admin? && user.organization == organization
97
+
98
+ user == self.user
99
+ end
100
+
94
101
  private
95
102
 
96
103
  def active_handler?
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ class PrivateDownload
5
+ class InvalidTokenError < StandardError; end
6
+
7
+ VERIFIER_PURPOSE = :private_download
8
+
9
+ def self.for(record, attachment_name:)
10
+ new(record:, attachment_name:)
11
+ end
12
+
13
+ def self.from_token(token)
14
+ payload = verifier.verify(token, purpose: VERIFIER_PURPOSE).with_indifferent_access
15
+ record = GlobalID::Locator.locate(payload[:gid])
16
+
17
+ raise InvalidTokenError if record.blank?
18
+
19
+ new(record:, attachment_name: payload[:attachment_name])
20
+ rescue ActiveSupport::MessageVerifier::InvalidSignature, TypeError
21
+ raise InvalidTokenError
22
+ end
23
+
24
+ def self.verifier
25
+ @verifier ||= ActiveSupport::MessageVerifier.new(Rails.application.secret_key_base, serializer: JSON)
26
+ end
27
+
28
+ def initialize(record:, attachment_name:)
29
+ @record = record
30
+ @attachment_name = attachment_name.to_s
31
+ end
32
+
33
+ def token
34
+ self.class.verifier.generate(
35
+ {
36
+ gid: record.to_global_id.to_s,
37
+ attachment_name:
38
+ },
39
+ purpose: VERIFIER_PURPOSE
40
+ )
41
+ end
42
+
43
+ def attachment
44
+ record.public_send(attachment_name)
45
+ end
46
+
47
+ def attached?
48
+ attachment.respond_to?(:attached?) && attachment.attached?
49
+ end
50
+
51
+ def authorized_for?(user)
52
+ return false unless record.respond_to?(:private_download_authorized?)
53
+
54
+ record.private_download_authorized?(user, attachment_name)
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :record, :attachment_name
60
+ end
61
+ end
@@ -24,5 +24,11 @@ module Decidim
24
24
  self.content_type = file.content_type
25
25
  self.file_size = file.byte_size
26
26
  end
27
+
28
+ def private_download_authorized?(user, requested_attachment_name)
29
+ return false unless requested_attachment_name.to_s == "file"
30
+
31
+ attached_to == user
32
+ end
27
33
  end
28
34
  end
@@ -1,16 +1,38 @@
1
- window.addEventListener("turbo:load", async () => {
1
+ document.addEventListener("turbo:load", async () => {
2
2
  const GRANTED_PERMISSION = "granted"
3
3
 
4
4
  const hideReminder = function() {
5
- const reminder = document.querySelector("#push-notifications-reminder")
5
+ const reminder = document.querySelector("[data-push-notifications-reminder]")
6
+ if (!reminder) {
7
+ return;
8
+ }
9
+
6
10
  reminder.classList.add("hide")
7
11
  }
8
12
 
13
+ const showError = (message) => {
14
+ const container = document.querySelector("[data-push-notifications-container]")
15
+ if (!container) {
16
+ return;
17
+ }
18
+
19
+ const existingError = container.querySelector("[data-push-notifications-error]")
20
+ if (existingError) {
21
+ existingError.remove()
22
+ }
23
+
24
+ const errorElement = document.createElement("div")
25
+ errorElement.dataset.pushNotificationsError = "true"
26
+ errorElement.classList.add("flash", "alert", "push-notifications__error")
27
+ errorElement.innerText = message
28
+ container.prepend(errorElement)
29
+ }
30
+
9
31
  const subscribeToNotifications = async (registration) => {
10
32
  const permission = await window.Notification.requestPermission();
11
33
 
12
34
  if (registration && permission === GRANTED_PERMISSION) {
13
- const vapidElement = document.querySelector("#vapidPublicKey")
35
+ const vapidElement = document.querySelector("[data-push-vapid-public-key]")
14
36
  // element could not exist in DOM
15
37
  if (vapidElement) {
16
38
  const vapidPublicKeyElement = JSON.parse(vapidElement.value)
@@ -20,7 +42,7 @@ window.addEventListener("turbo:load", async () => {
20
42
  });
21
43
 
22
44
  if (subscription) {
23
- await fetch("/notifications_subscriptions", {
45
+ const response = await fetch("/notifications_subscriptions", {
24
46
  headers: {
25
47
  "Content-Type": "application/json",
26
48
  "X-CSRF-Token": document.querySelector("meta[name=csrf-token]")?.content
@@ -28,6 +50,11 @@ window.addEventListener("turbo:load", async () => {
28
50
  method: "POST",
29
51
  body: JSON.stringify(subscription)
30
52
  });
53
+
54
+ if (!response.ok) {
55
+ const body = await response.json()
56
+ throw new Error(body.error)
57
+ }
31
58
  }
32
59
  }
33
60
  hideReminder()
@@ -57,10 +84,13 @@ window.addEventListener("turbo:load", async () => {
57
84
  hideReminder()
58
85
  if (currentSubscription) {
59
86
  const auth = currentSubscription.toJSON().keys.auth
60
- const subKeys = JSON.parse(document.querySelector("#subKeys").value)
61
- // Subscribed && browser notifications enabled
62
- if (subKeys.includes(auth)) {
63
- toggleChecked = true
87
+ const subKeysElement = document.querySelector("[data-push-sub-keys]")
88
+ if (subKeysElement) {
89
+ const subKeys = JSON.parse(subKeysElement.value)
90
+ // Subscribed && browser notifications enabled
91
+ if (subKeys.includes(auth)) {
92
+ toggleChecked = true
93
+ }
64
94
  }
65
95
  }
66
96
  }
@@ -68,7 +98,7 @@ window.addEventListener("turbo:load", async () => {
68
98
  }
69
99
 
70
100
  if ("serviceWorker" in navigator) {
71
- const toggle = document.getElementById("allow_push_notifications")
101
+ const toggle = document.querySelector("[data-push-notifications-toggle]")
72
102
 
73
103
  if (toggle) {
74
104
  const registration = await navigator.serviceWorker.ready
@@ -76,10 +106,15 @@ window.addEventListener("turbo:load", async () => {
76
106
  setToggleState(registration, toggle)
77
107
 
78
108
  toggle.addEventListener("change", async ({ target }) => {
79
- if (target.checked) {
80
- await subscribeToNotifications(registration);
81
- } else {
82
- await unsubscribeFromNotifications(registration)
109
+ try {
110
+ if (target.checked) {
111
+ await subscribeToNotifications(registration)
112
+ } else {
113
+ await unsubscribeFromNotifications(registration)
114
+ }
115
+ } catch (error) {
116
+ target.checked = false
117
+ showError(error.message)
83
118
  }
84
119
  })
85
120
  }
@@ -4,6 +4,10 @@ module Decidim
4
4
  # This class manages the creation and deletion of user notifications
5
5
 
6
6
  class NotificationsSubscriptionsPersistor
7
+ include PushSubscriptionEndpointValidator
8
+
9
+ class UnsupportedPushSubscriptionEndpointError < StandardError; end
10
+
7
11
  attr_reader :user
8
12
 
9
13
  def initialize(user)
@@ -11,6 +15,8 @@ module Decidim
11
15
  end
12
16
 
13
17
  def add_subscription(params)
18
+ raise UnsupportedPushSubscriptionEndpointError unless supported_push_subscription_endpoint?(params[:endpoint])
19
+
14
20
  subscriptions = user.notification_settings["subscriptions"] || {}
15
21
  filtered_params = filter_params(params)
16
22
  new_subscription = { filtered_params[:auth] => filtered_params }
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # Shared validation for browser push subscription endpoints.
5
+ module PushSubscriptionEndpointValidator
6
+ private
7
+
8
+ def supported_push_subscription_endpoint?(endpoint)
9
+ return false if endpoint.blank?
10
+
11
+ uri = URI.parse(endpoint)
12
+ return false unless uri.is_a?(URI::HTTPS)
13
+
14
+ host = uri.host&.downcase
15
+ return false if host.blank?
16
+
17
+ allowed_push_subscription_endpoint_patterns.any? { |pattern| pattern.match?(host) }
18
+ rescue URI::InvalidURIError
19
+ false
20
+ end
21
+
22
+ # Override this method to customize the browser push endpoint allowlist.
23
+ def allowed_push_subscription_endpoint_patterns
24
+ [
25
+ /\A(?:.*\.)?push\.services\.mozilla\.com\z/,
26
+ /\A(?:.*\.)?fcm\.googleapis\.com\z/,
27
+ /\A(?:.*\.)?android\.googleapis\.com\z/,
28
+ /\A(?:.*\.)?push\.apple\.com\z/,
29
+ /\A(?:.*\.)?opera\.com\z/,
30
+ /\A(?:.*\.)?notify\.windows\.com\z/
31
+ ]
32
+ end
33
+ end
34
+ end
@@ -10,6 +10,7 @@ module Decidim
10
10
 
11
11
  class SendPushNotification
12
12
  include ActionView::Helpers::UrlHelper
13
+ include PushSubscriptionEndpointValidator
13
14
 
14
15
  # Send the push notification. Returns `nil` if the user did not allowed push notifications
15
16
  # or if the subscription to push notifications does not exist
@@ -24,9 +25,12 @@ module Decidim
24
25
  raise ArgumentError, "Need to provide a title if the notification is a PushNotificationMessage" if notification.is_a?(Decidim::PushNotificationMessage) && title.nil?
25
26
 
26
27
  user = notification.user
28
+ subscriptions = user.notifications_subscriptions.values.select do |subscription|
29
+ supported_push_subscription_endpoint?(subscription["endpoint"])
30
+ end
27
31
 
28
32
  I18n.with_locale(user.locale || user.organization.default_locale) do
29
- user.notifications_subscriptions.values.map do |subscription|
33
+ subscriptions.map do |subscription|
30
34
  payload = build_payload(message_params(notification, title), subscription)
31
35
  # Capture webpush exceptions in order to avoid this call to be repeated by the background job runner
32
36
  # Webpush::Error class is the parent class of all defined errors
@@ -167,20 +167,20 @@
167
167
  <% end %>
168
168
 
169
169
  <% if @notifications_settings.meet_push_notifications_requirements? %>
170
- <div class="push-notifications js-sw-mandatory">
170
+ <div class="push-notifications js-sw-mandatory" data-push-notifications-container>
171
171
  <label>
172
172
  <%= t("push_notifications", scope: "decidim.notifications_settings.show") %>
173
173
  </label>
174
- <p id="push-notifications-reminder" class="push-notifications__reminder block my-4">
174
+ <p id="push-notifications-reminder" class="push-notifications__reminder block my-4" data-push-notifications-reminder>
175
175
  <%= t("push_notifications_reminder", scope: "decidim.notifications_settings.show") %>
176
176
  </p>
177
177
  <div class="toggle__switch-trigger">
178
178
  <label class="toggle__switch-toggle" for="allow_push_notifications">
179
179
  <span>
180
180
  <input
181
- <%== %(checked="checked") if @notifications_settings.meet_push_notifications_requirements? %>
182
181
  id="allow_push_notifications"
183
182
  type="checkbox"
183
+ data-push-notifications-toggle
184
184
  name="allow_push_notifications">
185
185
  <span class="toggle__switch-toggle-content">
186
186
  </span>
@@ -194,8 +194,8 @@
194
194
  </div>
195
195
  </div>
196
196
 
197
- <input id="vapidPublicKey" name="vapid_public_key" type="hidden" value="<%= Base64.urlsafe_decode64(Decidim.vapid_public_key.to_s).bytes %>">
198
- <input id="subKeys" name="sub_key" type="hidden" value="<%= current_user.notifications_subscriptions.keys %>">
197
+ <input id="vapidPublicKey" name="vapid_public_key" data-push-vapid-public-key type="hidden" value="<%= Base64.urlsafe_decode64(Decidim.vapid_public_key.to_s).bytes.to_json %>">
198
+ <input id="subKeys" name="sub_key" data-push-sub-keys type="hidden" value="<%= current_user.notifications_subscriptions.keys.to_json %>">
199
199
  <% end %>
200
200
 
201
201
  <div class="form__wrapper-block">
@@ -1338,6 +1338,7 @@ ca-IT:
1338
1338
  own_activity: La meva pròpia activitat, com quan algú fa comentaris a la meva proposta o em menciona
1339
1339
  push_notifications: Notificacions emergents
1340
1340
  push_notifications_reminder: Per rebre notificacions de la plataforma, primer les has de permetre a la configuració del teu navegador.
1341
+ push_notifications_unsupported_browser: El navegador no és compatible.
1341
1342
  receive_notifications_about: Vull rebre notificacions
1342
1343
  update_notifications_settings: Guardar canvis
1343
1344
  update:
@@ -1338,6 +1338,7 @@ ca:
1338
1338
  own_activity: La meva pròpia activitat, com quan algú fa comentaris a la meva proposta o em menciona
1339
1339
  push_notifications: Notificacions emergents
1340
1340
  push_notifications_reminder: Per rebre notificacions de la plataforma, primer les has de permetre a la configuració del teu navegador.
1341
+ push_notifications_unsupported_browser: El navegador no és compatible.
1341
1342
  receive_notifications_about: Vull rebre notificacions
1342
1343
  update_notifications_settings: Guardar canvis
1343
1344
  update:
@@ -837,6 +837,7 @@ cs:
837
837
  delete_reason: Důvod pro odstranění tohoto uživatele
838
838
  deleted_at: Datum a čas, kdy byl tento uživatel odstraněn
839
839
  email: E-mailová adresa tohoto uživatele
840
+ followers_count: Počet účastníků, kteří sledují tohoto uživatele
840
841
  following_count: Počet účastníků, které tento uživatel sleduje
841
842
  id: Jedinečný identifikátor tohoto uživatele
842
843
  invitation_accepted_at: Datum a čas, kdy byla pozvánka přijata
@@ -1184,6 +1185,7 @@ cs:
1184
1185
  create_with_space: "%{user_name} vytvořil %{resource_name} v %{space_name}"
1185
1186
  delete: "%{user_name} odstraněno %{resource_name}"
1186
1187
  delete_with_space: "%{user_name} smazán %{resource_name} v %{space_name}"
1188
+ publish: "%{user_name} publikoval %{resource_name}"
1187
1189
  publish_with_space: "%{user_name} publikoval %{resource_name} v %{space_name}"
1188
1190
  unknown_action: "%{user_name} provedla nějakou akci na %{resource_name}"
1189
1191
  unknown_action_with_space: "%{user_name} provedlo nějakou akci na %{resource_name} v %{space_name}"
@@ -1345,6 +1345,7 @@ en:
1345
1345
  own_activity: My own activity, like when someone comments in my proposal or mentions me
1346
1346
  push_notifications: Push notifications
1347
1347
  push_notifications_reminder: To get notifications from the platform, you will need to allow them in your browser settings first.
1348
+ push_notifications_unsupported_browser: Your browser is not supported.
1348
1349
  receive_notifications_about: I want to get notifications about
1349
1350
  update_notifications_settings: Save changes
1350
1351
  update:
@@ -1341,6 +1341,7 @@ es-MX:
1341
1341
  own_activity: Mi propia actividad, como cuando alguien comenta en mi propuesta o me menciona.
1342
1342
  push_notifications: Notificaciones emergentes
1343
1343
  push_notifications_reminder: Para obtener notificaciones de la plataforma, primero tienes que permitirlas en la configuración de tu navegador.
1344
+ push_notifications_unsupported_browser: Tu navegador no es compatible.
1344
1345
  receive_notifications_about: Quiero recibir notificaciones sobre
1345
1346
  update_notifications_settings: Guardar cambios
1346
1347
  update:
@@ -1341,6 +1341,7 @@ es-PY:
1341
1341
  own_activity: Mi propia actividad, como cuando alguien comenta en mi propuesta o me menciona.
1342
1342
  push_notifications: Notificaciones emergentes
1343
1343
  push_notifications_reminder: Para obtener notificaciones de la plataforma, primero tienes que permitirlas en la configuración de tu navegador.
1344
+ push_notifications_unsupported_browser: Tu navegador no es compatible.
1344
1345
  receive_notifications_about: Quiero recibir notificaciones sobre
1345
1346
  update_notifications_settings: Guardar cambios
1346
1347
  update:
@@ -1338,6 +1338,7 @@ es:
1338
1338
  own_activity: Mi propia actividad, como cuando alguien comenta en mi propuesta o me menciona
1339
1339
  push_notifications: Notificaciones emergentes
1340
1340
  push_notifications_reminder: Para recibir notificaciones de la plataforma, primero tienes que permitirlas en la configuración de tu navegador.
1341
+ push_notifications_unsupported_browser: Tu navegador no es compatible.
1341
1342
  receive_notifications_about: Quiero recibir notificaciones
1342
1343
  update_notifications_settings: Guardar cambios
1343
1344
  update:
@@ -1338,6 +1338,7 @@ eu:
1338
1338
  own_activity: Neure jarduera, norbaitek nire proposamenean iruzkina egiten duenean bezala, edo aipatzen nauenean
1339
1339
  push_notifications: Push jakinarazpenak
1340
1340
  push_notifications_reminder: Plataformaren jakinarazpenak jasotzeko, lehen zure nabigatzailearen konfigurazioan baimendu behar dituzu.
1341
+ push_notifications_unsupported_browser: Zure nabigatzaileak ez du euskarririk.
1341
1342
  receive_notifications_about: Jakinarazpenak jaso nahi ditut
1342
1343
  update_notifications_settings: Gorde aldaketak
1343
1344
  update:
@@ -2052,8 +2052,8 @@ fi:
2052
2052
  correct_errors: Lomakkeella on virheitä, korjaa ne jatkaaksesi.
2053
2053
  length_validator:
2054
2054
  minimum:
2055
- one: Vähintään %{count} kirjain
2056
- other: Vähintään %{count} kirjainta
2055
+ one: Vähintään %{count} merkki
2056
+ other: Vähintään %{count} merkkiä
2057
2057
  required: Pakollinen kenttä
2058
2058
  required_explanation: "* Vaaditut kentät on merkitty tähtimerkillä"
2059
2059
  invisible_captcha:
@@ -1216,6 +1216,7 @@ fr-CA:
1216
1216
  own_activity: Ma propre activité, comme quand quelqu'un commente dans ma proposition ou me mentionne
1217
1217
  push_notifications: Notifications push
1218
1218
  push_notifications_reminder: Pour recevoir des notifications de la plateforme, vous devez d'abord les autoriser dans les paramètres de votre navigateur.
1219
+ push_notifications_unsupported_browser: Votre navigateur n'est pas pris en charge.
1219
1220
  receive_notifications_about: Je veux recevoir des notifications sur
1220
1221
  update_notifications_settings: Enregistrer les modifications
1221
1222
  update:
@@ -1216,6 +1216,7 @@ fr:
1216
1216
  own_activity: Ma propre activité, comme quand quelqu'un commente dans ma proposition ou me mentionne
1217
1217
  push_notifications: Notifications push
1218
1218
  push_notifications_reminder: Pour recevoir des notifications de la plateforme, vous devez d'abord les autoriser dans les paramètres de votre navigateur.
1219
+ push_notifications_unsupported_browser: Votre navigateur n'est pas pris en charge.
1219
1220
  receive_notifications_about: Je veux recevoir des notifications sur
1220
1221
  update_notifications_settings: Enregistrer les modifications
1221
1222
  update:
@@ -500,6 +500,7 @@ it:
500
500
  username_help: Nome "pubblico" che comparirà a firma di tutti i tuoi contributi e che sarà accessibile a tutti i navigatori, anche non iscritti alla piattaforma. Ti invitiamo a utilizzare un nome di fantasia per una maggiore tutela della riservatezza.
501
501
  sessions:
502
502
  new:
503
+ are_you_new?: Non sei ancora registrato?
503
504
  register: Crea un account
504
505
  shared:
505
506
  newsletter_modal:
@@ -524,6 +525,9 @@ it:
524
525
  download_your_data:
525
526
  export:
526
527
  ready: Pronto
528
+ help:
529
+ users:
530
+ notifications_sending_frequency: La frequenza delle notifiche che questo utente riceve
527
531
  editor_images:
528
532
  drag_and_drop_help: Aggiungi immagini trascinandole o incollandole.
529
533
  errors:
@@ -813,6 +817,12 @@ it:
813
817
  everything_followed: Tutto ciò che seguo
814
818
  newsletter_notifications: Voglio ricevere le newsletter
815
819
  newsletters: Le Newsletter
820
+ notifications_sending_frequencies:
821
+ daily: Quotidiana
822
+ none: Non voglio ricevere mail
823
+ real_time: Tempo reale
824
+ weekly: Settimanale
825
+ notifications_sending_frequency: Con che frequenza vuoi ricevere notifiche via mail?
816
826
  own_activity: La mia attività personale, come quando qualcuno commenta la mia proposta o mi menziona
817
827
  receive_notifications_about: Voglio ricevere le notifiche
818
828
  update_notifications_settings: Salva le modifiche