decidim-core 0.30.8 → 0.30.9

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 +47 -12
  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: b31c19b0f1263634559541d974f68dba477328f79a493ab4188eb3d8cb42a0eb
4
- data.tar.gz: a9d06e9ea81dfe61b62c9f99961a4284af380a5343a12ce5c7ca7b055f4041b8
3
+ metadata.gz: a5817376e17111569ffa304fd748355de958fdb6020c5fec8f49d913707b43e9
4
+ data.tar.gz: '0785c351a8efe4e5740a7551bf6994a98db61257e29b8df325823a790061385b'
5
5
  SHA512:
6
- metadata.gz: 4c821103cefa1cfc8c3688fe69dce51bd2e6c94a448bee4fdc05ef5abd08e522cc4785e0827217b464b9b4a601cb52daa8539b21b0bf98f39e69fdff1b5824a5
7
- data.tar.gz: 9c27f4b8ce372ac3295e719a3985b7276ec91d90303b4e627545d4571012936d3d4f1694c6e81e4df95fac154bbb2aaa3889130b9ffcfc609a1b2a40a699a99f
6
+ metadata.gz: cf18599b257a5ffea8442bcba6ea817eddecfa48e7a2150fb0ccf00ee024c267da283c333ee5c2eedb89441c7deea081bc89fe3f5af3ce55d5e27b13ab3a3484
7
+ data.tar.gz: f56a6474372376c00de0e4589d9dcc733a311a5302aedc4384064cdb4ec7048b0761360f616d5bd0d515068757776691c0bb951aec53df944742015678287042
@@ -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
@@ -2,15 +2,37 @@ window.addEventListener("DOMContentLoaded", 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("DOMContentLoaded", 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("DOMContentLoaded", 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("DOMContentLoaded", 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("DOMContentLoaded", 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("DOMContentLoaded", 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
@@ -23,9 +24,12 @@ module Decidim
23
24
  raise ArgumentError, "Need to provide a title if the notification is a PushNotificationMessage" if notification.is_a?(Decidim::PushNotificationMessage) && title.nil?
24
25
 
25
26
  user = notification.user
27
+ subscriptions = user.notifications_subscriptions.values.select do |subscription|
28
+ supported_push_subscription_endpoint?(subscription["endpoint"])
29
+ end
26
30
 
27
31
  I18n.with_locale(user.locale || user.organization.default_locale) do
28
- user.notifications_subscriptions.values.map do |subscription|
32
+ subscriptions.map do |subscription|
29
33
  payload = build_payload(message_params(notification, title), subscription)
30
34
  # Capture webpush exceptions in order to avoid this call to be repeated by the background job runner
31
35
  # 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(Rails.application.secrets.vapid[:public_key]).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(Rails.application.secrets.vapid[:public_key]).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">
@@ -1525,6 +1525,7 @@ ca-IT:
1525
1525
  own_activity: La meva pròpia activitat, com quan algú fa comentaris a la meva proposta o em menciona
1526
1526
  push_notifications: Notificacions emergents
1527
1527
  push_notifications_reminder: Per rebre notificacions de la plataforma, primer les has de permetre a la configuració del teu navegador.
1528
+ push_notifications_unsupported_browser: El navegador no és compatible.
1528
1529
  receive_notifications_about: Vull rebre notificacions
1529
1530
  update_notifications_settings: Guardar canvis
1530
1531
  valuators: Avaluadores
@@ -1525,6 +1525,7 @@ ca:
1525
1525
  own_activity: La meva pròpia activitat, com quan algú fa comentaris a la meva proposta o em menciona
1526
1526
  push_notifications: Notificacions emergents
1527
1527
  push_notifications_reminder: Per rebre notificacions de la plataforma, primer les has de permetre a la configuració del teu navegador.
1528
+ push_notifications_unsupported_browser: El navegador no és compatible.
1528
1529
  receive_notifications_about: Vull rebre notificacions
1529
1530
  update_notifications_settings: Guardar canvis
1530
1531
  valuators: Avaluadores
@@ -844,6 +844,7 @@ cs:
844
844
  delete_reason: Důvod pro odstranění tohoto uživatele
845
845
  deleted_at: Datum a čas, kdy byl tento uživatel odstraněn
846
846
  email: E-mailová adresa tohoto uživatele
847
+ followers_count: Počet účastníků, kteří sledují tohoto uživatele
847
848
  following_count: Počet účastníků, které tento uživatel sleduje
848
849
  id: Jedinečný identifikátor tohoto uživatele
849
850
  invitation_accepted_at: Datum a čas, kdy byla pozvánka přijata
@@ -1331,6 +1332,7 @@ cs:
1331
1332
  create_with_space: "%{user_name} vytvořil %{resource_name} v %{space_name}"
1332
1333
  delete: "%{user_name} odstraněno %{resource_name}"
1333
1334
  delete_with_space: "%{user_name} smazán %{resource_name} v %{space_name}"
1335
+ publish: "%{user_name} publikoval %{resource_name}"
1334
1336
  publish_with_space: "%{user_name} publikoval %{resource_name} v %{space_name}"
1335
1337
  unknown_action: "%{user_name} provedla nějakou akci na %{resource_name}"
1336
1338
  unknown_action_with_space: "%{user_name} provedlo nějakou akci na %{resource_name} v %{space_name}"
@@ -1532,6 +1532,7 @@ en:
1532
1532
  own_activity: My own activity, like when someone comments in my proposal or mentions me
1533
1533
  push_notifications: Push notifications
1534
1534
  push_notifications_reminder: To get notifications from the platform, you will need to allow them in your browser settings first.
1535
+ push_notifications_unsupported_browser: Your browser is not supported.
1535
1536
  receive_notifications_about: I want to get notifications about
1536
1537
  update_notifications_settings: Save changes
1537
1538
  valuators: Evaluators
@@ -1528,6 +1528,7 @@ es-MX:
1528
1528
  own_activity: Mi propia actividad, como cuando alguien comenta en mi propuesta o me menciona.
1529
1529
  push_notifications: Notificaciones emergentes
1530
1530
  push_notifications_reminder: Para obtener notificaciones de la plataforma, primero tienes que permitirlas en la configuración de tu navegador.
1531
+ push_notifications_unsupported_browser: Tu navegador no es compatible.
1531
1532
  receive_notifications_about: Quiero recibir notificaciones sobre
1532
1533
  update_notifications_settings: Guardar cambios
1533
1534
  valuators: Evaluadoras
@@ -1528,6 +1528,7 @@ es-PY:
1528
1528
  own_activity: Mi propia actividad, como cuando alguien comenta en mi propuesta o me menciona.
1529
1529
  push_notifications: Notificaciones emergentes
1530
1530
  push_notifications_reminder: Para obtener notificaciones de la plataforma, primero tienes que permitirlas en la configuración de tu navegador.
1531
+ push_notifications_unsupported_browser: Tu navegador no es compatible.
1531
1532
  receive_notifications_about: Quiero recibir notificaciones sobre
1532
1533
  update_notifications_settings: Guardar cambios
1533
1534
  valuators: Evaluadoras
@@ -1525,6 +1525,7 @@ es:
1525
1525
  own_activity: Mi propia actividad, como cuando alguien comenta en mi propuesta o me menciona
1526
1526
  push_notifications: Notificaciones emergentes
1527
1527
  push_notifications_reminder: Para recibir notificaciones de la plataforma, primero tienes que permitirlas en la configuración de tu navegador.
1528
+ push_notifications_unsupported_browser: Tu navegador no es compatible.
1528
1529
  receive_notifications_about: Quiero recibir notificaciones
1529
1530
  update_notifications_settings: Guardar cambios
1530
1531
  valuators: Evaluadoras
@@ -1525,6 +1525,7 @@ eu:
1525
1525
  own_activity: Neure jarduera, norbaitek nire proposamenean iruzkina egiten duenean bezala, edo aipatzen nauenean
1526
1526
  push_notifications: Push jakinarazpenak
1527
1527
  push_notifications_reminder: Plataformaren jakinarazpenak jasotzeko, lehen zure nabigatzailearen konfigurazioan baimendu behar dituzu.
1528
+ push_notifications_unsupported_browser: Zure nabigatzaileak ez du euskarririk.
1528
1529
  receive_notifications_about: Jakinarazpenak jaso nahi ditut
1529
1530
  update_notifications_settings: Gorde aldaketak
1530
1531
  valuators: Ebaluatzaileak
@@ -2222,8 +2222,8 @@ fi:
2222
2222
  correct_errors: Lomakkeella on virheitä, korjaa ne jatkaaksesi.
2223
2223
  length_validator:
2224
2224
  minimum:
2225
- one: Vähintään %{count} kirjain
2226
- other: Vähintään %{count} kirjainta
2225
+ one: Vähintään %{count} merkki
2226
+ other: Vähintään %{count} merkkiä
2227
2227
  required: Pakollinen kenttä
2228
2228
  required_explanation: "* Vaaditut kentät on merkitty tähtimerkillä"
2229
2229
  invisible_captcha:
@@ -1416,6 +1416,7 @@ fr-CA:
1416
1416
  own_activity: Ma propre activité, comme quand quelqu'un commente dans ma proposition ou me mentionne
1417
1417
  push_notifications: Notifications push
1418
1418
  push_notifications_reminder: Pour recevoir des notifications de la plateforme, vous devez d'abord les autoriser dans les paramètres de votre navigateur.
1419
+ push_notifications_unsupported_browser: Votre navigateur n'est pas pris en charge.
1419
1420
  receive_notifications_about: Je veux recevoir des notifications sur
1420
1421
  update_notifications_settings: Enregistrer les modifications
1421
1422
  valuators: Évaluateurs
@@ -1416,6 +1416,7 @@ fr:
1416
1416
  own_activity: Ma propre activité, comme quand quelqu'un commente dans ma proposition ou me mentionne
1417
1417
  push_notifications: Notifications push
1418
1418
  push_notifications_reminder: Pour recevoir des notifications de la plateforme, vous devez d'abord les autoriser dans les paramètres de votre navigateur.
1419
+ push_notifications_unsupported_browser: Votre navigateur n'est pas pris en charge.
1419
1420
  receive_notifications_about: Je veux recevoir des notifications sur
1420
1421
  update_notifications_settings: Enregistrer les modifications
1421
1422
  valuators: Évaluateurs
@@ -517,6 +517,7 @@ it:
517
517
  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.
518
518
  sessions:
519
519
  new:
520
+ are_you_new?: Non sei ancora registrato?
520
521
  register: Crea un account
521
522
  shared:
522
523
  newsletter_modal:
@@ -541,6 +542,9 @@ it:
541
542
  download_your_data:
542
543
  export:
543
544
  ready: Pronto
545
+ help:
546
+ users:
547
+ notifications_sending_frequency: La frequenza delle notifiche che questo utente riceve
544
548
  editor_images:
545
549
  drag_and_drop_help: Aggiungi immagini trascinandole o incollandole.
546
550
  endorsements:
@@ -1001,6 +1005,12 @@ it:
1001
1005
  everything_followed: Tutto ciò che seguo
1002
1006
  newsletter_notifications: Voglio ricevere le newsletter
1003
1007
  newsletters: Le Newsletter
1008
+ notifications_sending_frequencies:
1009
+ daily: Quotidiana
1010
+ none: Non voglio ricevere mail
1011
+ real_time: Tempo reale
1012
+ weekly: Settimanale
1013
+ notifications_sending_frequency: Con che frequenza vuoi ricevere notifiche via mail?
1004
1014
  own_activity: La mia attività personale, come quando qualcuno commenta la mia proposta o mi menziona
1005
1015
  receive_notifications_about: Voglio ricevere le notifiche
1006
1016
  update_notifications_settings: Salva le modifiche