decidim-core 0.31.3 → 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 (71) 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/nav_links/show.erb +3 -3
  8. data/app/cells/decidim/participatory_space_private_user/show.erb +6 -6
  9. data/app/cells/decidim/participatory_space_private_user_cell.rb +0 -4
  10. data/app/cells/decidim/upload_modal_cell.rb +5 -0
  11. data/app/controllers/decidim/download_your_data_controller.rb +1 -1
  12. data/app/controllers/decidim/notifications_subscriptions_controller.rb +8 -0
  13. data/app/controllers/decidim/private_downloads_controller.rb +29 -0
  14. data/app/helpers/decidim/mailer_helper.rb +36 -0
  15. data/app/helpers/decidim/menu_helper.rb +2 -1
  16. data/app/helpers/decidim/newsletters_helper.rb +4 -22
  17. data/app/mailers/decidim/application_mailer.rb +4 -0
  18. data/app/models/decidim/attachment.rb +20 -2
  19. data/app/models/decidim/authorization.rb +7 -0
  20. data/app/models/decidim/private_download.rb +61 -0
  21. data/app/models/decidim/private_export.rb +6 -0
  22. data/app/packs/src/decidim/controllers/accordion/accordion.test.js +118 -0
  23. data/app/packs/src/decidim/controllers/accordion/controller.js +24 -0
  24. data/app/packs/src/decidim/controllers/dropdown/controller.js +26 -0
  25. data/app/packs/src/decidim/controllers/dropdown/dropdown.test.js +187 -0
  26. data/app/packs/src/decidim/controllers/form_validator/form_validator.js +3 -2
  27. data/app/packs/src/decidim/controllers/form_validator/form_validator.test.js +5 -0
  28. data/app/packs/src/decidim/editor/extensions/image/index.js +49 -11
  29. data/app/packs/src/decidim/editor/extensions/image/node_view.js +9 -1
  30. data/app/packs/src/decidim/editor/extensions/link/bubble_menu.js +34 -6
  31. data/app/packs/src/decidim/editor/extensions/link/index.js +45 -12
  32. data/app/packs/src/decidim/editor/test/extensions/image_links.test.js +161 -0
  33. data/app/packs/src/decidim/sw/push-permissions.js +48 -13
  34. data/app/packs/stylesheets/decidim/_rich_text.scss +17 -0
  35. data/app/packs/stylesheets/decidim/editor.scss +10 -0
  36. data/app/presenters/decidim/menu_item_presenter.rb +7 -1
  37. data/app/services/decidim/notifications_subscriptions_persistor.rb +6 -0
  38. data/app/services/decidim/push_subscription_endpoint_validator.rb +34 -0
  39. data/app/services/decidim/send_push_notification.rb +5 -1
  40. data/app/views/decidim/devise/registrations/new.html.erb +1 -0
  41. data/app/views/decidim/devise/shared/_tos_fields.html.erb +3 -3
  42. data/app/views/decidim/notification_mailer/event_received.html.erb +3 -3
  43. data/app/views/decidim/notifications_settings/show.html.erb +5 -5
  44. data/app/views/decidim/pages/_tabbed.html.erb +3 -3
  45. data/app/views/decidim/shared/_filters.html.erb +5 -5
  46. data/app/views/decidim/shared/filters/_check_boxes_tree.html.erb +1 -1
  47. data/app/views/decidim/shared/filters/_collection.html.erb +1 -1
  48. data/config/locales/ca-IT.yml +1 -0
  49. data/config/locales/ca.yml +1 -0
  50. data/config/locales/cs.yml +2 -0
  51. data/config/locales/de.yml +27 -0
  52. data/config/locales/en.yml +1 -0
  53. data/config/locales/es-MX.yml +1 -0
  54. data/config/locales/es-PY.yml +1 -0
  55. data/config/locales/es.yml +1 -0
  56. data/config/locales/eu.yml +4 -0
  57. data/config/locales/fi-plain.yml +5 -0
  58. data/config/locales/fi.yml +7 -2
  59. data/config/locales/fr-CA.yml +1 -0
  60. data/config/locales/fr.yml +1 -0
  61. data/config/locales/it.yml +10 -0
  62. data/config/locales/pt-BR.yml +1 -1
  63. data/config/locales/sk.yml +1417 -0
  64. data/config/locales/sv.yml +1 -0
  65. data/config/routes.rb +1 -0
  66. data/lib/decidim/content_parsers/blob_parser.rb +3 -3
  67. data/lib/decidim/content_renderers/blob_renderer.rb +2 -2
  68. data/lib/decidim/core/test/shared_examples/participatory_space_members_shared_examples.rb +121 -0
  69. data/lib/decidim/core/version.rb +1 -1
  70. data/lib/decidim/participatory_space_user.rb +1 -1
  71. metadata +14 -6
@@ -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
  }
@@ -106,4 +106,21 @@
106
106
  .editor-indent-10 {
107
107
  @apply ml-40;
108
108
  }
109
+
110
+ // Style for external link icon inside linked images
111
+ a:has(.editor-content-image) [data-external-link="true"] {
112
+ @apply block relative;
113
+
114
+ margin: -1.5rem 0 1em auto;
115
+ width: fit-content;
116
+
117
+ svg {
118
+ @apply w-4 h-4;
119
+ }
120
+ }
121
+
122
+ // Make sure the anchor with image is positioned for proper icon placement
123
+ a:has(.editor-content-image) {
124
+ @apply block relative;
125
+ }
109
126
  }
@@ -130,6 +130,16 @@
130
130
  [data-image-resizer-wrapper] {
131
131
  @apply relative inline-block;
132
132
 
133
+ [data-image-resizer-control],
134
+ [data-image-resizer-dimensions] {
135
+ z-index: 1;
136
+ }
137
+
138
+ > a {
139
+ position: relative;
140
+ z-index: 0;
141
+ }
142
+
133
143
  [data-image-resizer-control] {
134
144
  @apply absolute hidden w-2.5 h-2.5 rounded-none border border-solid border-[#000] bg-[#fff] opacity-80;
135
145
  }
@@ -29,7 +29,7 @@ module Decidim
29
29
  delegate :content_tag, :safe_join, :link_to, :active_link_to_class, :is_active_link?, :icon, to: :@view
30
30
 
31
31
  def render
32
- content_tag :li, role: :menuitem, class: link_wrapper_classes do
32
+ content_tag :li, role: menuitem_role, class: link_wrapper_classes do
33
33
  output = if url == "#"
34
34
  [content_tag(:span, composed_label, class: "sidebar-menu__item-disabled")]
35
35
  else
@@ -69,6 +69,12 @@ module Decidim
69
69
  [@options.element_wrapper_class, active_class].compact.join(" ")
70
70
  end
71
71
 
72
+ def menuitem_role
73
+ return if @options.role == false
74
+
75
+ @options.role || :menuitem
76
+ end
77
+
72
78
  def active_class
73
79
  active_link_to_class(
74
80
  url,
@@ -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
@@ -34,6 +34,7 @@
34
34
  <%= f.text_field :name, help_text: t("decidim.devise.registrations.new.username_help"), autocomplete: "name", placeholder: "John Doe" %>
35
35
 
36
36
  <%= f.email_field :email, autocomplete: "email", placeholder: t("placeholder_email", scope: "decidim.devise.shared") %>
37
+ <span class="sr-only"><%= t("placeholder_email", scope: "decidim.devise.shared") %></span>
37
38
 
38
39
  <%= render partial: "decidim/account/password_fields", locals: { form: f, user: :user } %>
39
40
  </div>
@@ -1,13 +1,13 @@
1
1
  <div id="card__tos" class="form__wrapper-block border-y-2">
2
2
  <h2 class="h4"><%= t("decidim.devise.registrations.new.tos_title") %></h2>
3
+ <span class="sr-only"><%= t("forms.required") %></span>
3
4
 
4
- <div>
5
+ <div id="terms_of_service_summary">
5
6
  <% terms_of_service_summary_content_blocks.each do |content_block| %>
6
7
  <%= cell content_block.manifest.cell, content_block %>
7
8
  <% end %>
8
9
  </div>
9
-
10
- <%= form.check_box :tos_agreement, label: t("decidim.devise.registrations.new.tos_agreement", link: link_to(t("decidim.devise.registrations.new.terms"), decidim.page_path("terms-of-service"))), label_options: { class: "form__wrapper-checkbox-label" } %>
10
+ <%= form.check_box :tos_agreement, label: t("decidim.devise.registrations.new.tos_agreement", link: link_to(t("decidim.devise.registrations.new.terms"), decidim.page_path("terms-of-service"))), label_options: { class: "form__wrapper-checkbox-label" }, "aria-describedby": "terms_of_service_summary" %>
11
11
  </div>
12
12
 
13
13
  <div id="card__newsletter" class="form__wrapper-block">
@@ -15,7 +15,7 @@
15
15
 
16
16
  <blockquote>
17
17
  <p>
18
- <%= @event_instance.safe_resource_text %>
18
+ <%= decidim_transform_image_urls(@event_instance.safe_resource_text, @organization.host).html_safe %>
19
19
  </p>
20
20
  </blockquote>
21
21
  <% end %>
@@ -28,7 +28,7 @@
28
28
  <p style="font-weight: bold"><%= t(".translated_text") %></p>
29
29
  <blockquote>
30
30
  <p>
31
- <%= @event_instance.safe_resource_translated_text %>
31
+ <%= decidim_transform_image_urls(@event_instance.safe_resource_translated_text, @organization.host).html_safe %>
32
32
  </p>
33
33
  </blockquote>
34
34
  <% end %>
@@ -40,7 +40,7 @@
40
40
  <table>
41
41
  <tr>
42
42
  <td>
43
- <%= link_to @event_instance.button_text, @event_instance.button_url, target: :blank %>
43
+ <%= link_to decidim_sanitize(@event_instance.button_text, strip_tags: true), @event_instance.button_url, target: :blank %>
44
44
  </td>
45
45
  </tr>
46
46
  </table>
@@ -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">
@@ -11,16 +11,16 @@
11
11
 
12
12
  <div class="vertical-tabs">
13
13
  <nav role="navigation" aria-label="<%= I18n.t("layouts.decidim.navigation.aria_label", title: translated_attribute(page.title)) %>">
14
- <button id="dropdown-trigger-pages" data-controller="dropdown" data-target="dropdown-menu-pages" data-open-md="true" data-auto-close="true">
14
+ <button id="dropdown-trigger-pages" data-controller="dropdown" data-target="dropdown-menu-pages" data-open-md="true" data-auto-close="true" data-add-aria-roles="false">
15
15
  <span>
16
16
  <%= translated_attribute(page.title) %>
17
17
  </span>
18
18
  <%= icon "arrow-down-s-line" %>
19
19
  <%= icon "arrow-up-s-line" %>
20
20
  </button>
21
- <ul id="dropdown-menu-pages" class="vertical-tabs__list" role="menu">
21
+ <ul id="dropdown-menu-pages" class="vertical-tabs__list">
22
22
  <% pages.each do |sibling| %>
23
- <li class="<%= "is-active" if page == sibling %>" role="menuitem">
23
+ <li class="<%= "is-active" if page == sibling %>">
24
24
  <%= link_to translated_attribute(sibling.title), page_path(sibling.slug), "aria-current": ("page" if page == sibling) %>
25
25
  </li>
26
26
  <% end %>
@@ -2,9 +2,9 @@
2
2
  <% search_label = t("decidim.searches.filters.search") unless local_assigns.has_key?(:search_label) %>
3
3
 
4
4
  <% if filter_sections.present? || local_assigns.has_key?(:search_variable) %>
5
- <%= filter_form_for filter, url_for, class: "new_filter self-stretch", data: { filters: "", controller: "accordion form-filter" } do |form| %>
5
+ <%= filter_form_for filter, url_for, class: "new_filter self-stretch", data: { filters: "", controller: "accordion form-filter", panel_role: "group" } do |form| %>
6
6
 
7
- <button id="dropdown-trigger-filters" data-controller="dropdown" data-target="dropdown-menu-filters" data-open-md="true">
7
+ <button id="dropdown-trigger-filters" data-controller="dropdown" data-target="dropdown-menu-filters" data-open-md="true" data-add-aria-roles="false">
8
8
  <%= icon "arrow-down-s-line" %>
9
9
  <%= icon "arrow-up-s-line" %>
10
10
  <span>
@@ -14,13 +14,13 @@
14
14
 
15
15
  <div id="dropdown-menu-filters">
16
16
  <% if local_assigns.has_key?(:skip_to_id) %>
17
- <%= link_to t("skip", scope: "decidim.shared.filter_form_help"), "##{skip_to_id}", class: "filter-skip", role: "menuitem", "data-skip-to-content": true %>
17
+ <%= link_to t("skip", scope: "decidim.shared.filter_form_help"), "##{skip_to_id}", class: "filter-skip", "data-skip-to-content": true %>
18
18
  <% end %>
19
19
 
20
- <p id="filter-help-text" class="filter-help" role="menuitem" aria-disabled="true"><%= t("help", scope: "decidim.shared.filter_form_help") %></p>
20
+ <p id="filter-help-text" class="filter-help" aria-disabled="true"><%= t("help", scope: "decidim.shared.filter_form_help") %></p>
21
21
 
22
22
  <% if local_assigns.has_key?(:search_variable) %>
23
- <div class="filter-search filter-container" role="menuitem">
23
+ <div class="filter-search filter-container">
24
24
  <%= form.search_field search_variable,
25
25
  label: false,
26
26
  placeholder: search_label,
@@ -1,4 +1,4 @@
1
- <div class="filter-container" role="menuitem">
1
+ <div class="filter-container">
2
2
  <button id="trigger-menu-<%= id %>" data-controls="panel-dropdown-menu-<%= id %>" data-open="false" data-open-md="true">
3
3
  <%= icon "arrow-down-s-line" %>
4
4
  <%= icon "arrow-up-s-line" %>
@@ -1,4 +1,4 @@
1
- <div class="filter-container" role="menuitem">
1
+ <div class="filter-container">
2
2
  <button id="trigger-menu-<%= id %>" data-controls="panel-dropdown-menu-<%= id %>" data-open="false" data-open-md="true">
3
3
  <%= icon "arrow-down-s-line" %>
4
4
  <%= icon "arrow-up-s-line" %>
@@ -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}"
@@ -1055,6 +1055,8 @@ de:
1055
1055
  explanation: 'Anleitung für Bild:'
1056
1056
  message_1: Vorzugsweise ein Bild im Querformat, das keinen Text enthält.
1057
1057
  message_2: Der Dienst schneidet die Datei zu.
1058
+ import_file:
1059
+ message_1: Muss ein JSON-Dokument sein, das über die Exportfunktion heruntergeladen wurde.
1058
1060
  file_validation:
1059
1061
  allowed_file_extensions: 'Erlaubte Dateiformate: %{extensions}'
1060
1062
  max_file_dimension: 'Maximale Dateigröße: %{resolution} Pixel'
@@ -1290,9 +1292,13 @@ de:
1290
1292
  same_language: Der Inhalt wurde in Ihrer bevorzugten Sprache (%{language}) veröffentlicht, daher wird in dieser E-Mail keine automatisierte Übersetzung angezeigt.
1291
1293
  translated_text: 'Automatisch übersetzter Text:'
1292
1294
  notifications:
1295
+ action_error: Beim Aktualisieren der Benachrichtigungseinstellungen ist ein Fehler aufgetreten.
1293
1296
  no_notifications: Noch keine Benachrichtigungen
1294
1297
  show:
1298
+ deleted: Inhalt wurde vom Autor gelöscht.
1295
1299
  missing_event: Hoppla, diese Benachrichtigung gehört zu einem Artikel, der nicht mehr verfügbar ist. Du kannst sie verwerfen.
1300
+ moderated: Inhalt wurde durch Moderation versteckt.
1301
+ not_available: Hoppla, diese Benachrichtigung gehört zu einem Artikel, der nicht mehr verfügbar ist. Es ist keine weitere Aktion erforderlich.
1296
1302
  notifications_digest_mailer:
1297
1303
  header:
1298
1304
  daily: Tägliche Zusammenfassung
@@ -1301,6 +1307,7 @@ de:
1301
1307
  hello: Hallo %{name}
1302
1308
  intro:
1303
1309
  daily: 'Dies sind die Benachrichtigungen vom letzten Tag basierend auf den Aktivitäten, denen Sie folgen:'
1310
+ real_time: 'Es gibt eine Benachrichtigung über die Aktivität, die Sie folgen:'
1304
1311
  weekly: 'Dies sind die Benachrichtigungen der letzten Woche, basierend auf den Aktivitäten, die Sie folgen:'
1305
1312
  outro: Sie haben diese Benachrichtigung erhalten, weil Sie diesen Inhalt oder seine Verfassenden folgen. Sie können dem Inhalt direkt auf seiner Seite entfolgen.
1306
1313
  see_more: Weitere Benachrichtigungen ansehen
@@ -1443,7 +1450,9 @@ de:
1443
1450
  title: Wie man diese Dateien öffnet und mit ihnen arbeitet
1444
1451
  license:
1445
1452
  body_1_html: Diese Datenbank von %{organization_name} wird unter %{link_database} zur Verfügung gestellt. Alle Rechte an einzelnen Inhalten der Datenbank sind unter %{link_contents} lizenziert.
1453
+ license_contents_link: https://opendatacommons.org/licenses/dbcl/1.0/
1446
1454
  license_contents_name: Lizenz für Datenbankinhalte
1455
+ license_database_link: https://opendatacommons.org/licenses/odbl/1.0/
1447
1456
  license_database_name: Offene Datenbanklizenz
1448
1457
  title: Lizenz
1449
1458
  title: Offene Daten
@@ -1726,9 +1735,23 @@ de:
1726
1735
  notify_deprecation_to_owner:
1727
1736
  body_1: 'Wir möchten Sie gerne über ein wichtiges Update ihres Gruppenprofil informieren: %{organization_name}.'
1728
1737
  body_2: Wir möchten die Erfahrungen von Organisationen vereinfachen und verbessern und werden deshalb die Funktion "Benutzergruppen" abschalten. Ihre Gruppe, <strong>%{name}</strong>, wurde in ein reguläres Konto umgewandelt.
1738
+ body_3: Um weiterhin auf Ihr Konto zuzugreifen und den Zugriff freizugeben, müssen Sie ein Passwort festlegen. Sobald gesetzt, können Sie die Anmeldedaten (E-Mail und Passwort) an jeden weitergeben.
1729
1739
  greeting: Hallo %{name},
1740
+ instructions_1: 'Klicken Sie auf den Link unten, um Ihr Passwort zu setzen:'
1741
+ instructions_2: 'Teilen Sie die Anmeldedaten (E-Mail: %{email} und das neue Passwort) mit Ihren Kollegen.'
1742
+ instructions_title: "<strong>Was sie tun müssen</strong>"
1730
1743
  set_password: Passwort festlegen
1731
1744
  subject: Wichtige Aktualisierung für Ihr Gruppenprofil
1745
+ notify_user_group_patched:
1746
+ body_1_html: 'Wir möchten Sie gerne über ein wichtiges Update ihres Gruppenprofil informieren: %{organization_name}.'
1747
+ body_2_html: Wir möchten die Erfahrungen von Organisationen vereinfachen und verbessern und werden deshalb die Funktion "Benutzergruppen" abschalten. Ihre Gruppe, <strong>%{name}</strong>, wurde in ein reguläres Konto umgewandelt.
1748
+ body_3_html: Um weiterhin auf Ihr Konto zuzugreifen und den Zugriff freizugeben, müssen Sie eine neue E-Mail und ein neues Passwort setzen. Bitte beachten Sie, dass jedes Mitglied deiner Gruppe diese E-Mail erhält. Um den weiteren Zugriff sicherzustellen, müssen Sie sich intern auf die gemeinsamen Zugangsdaten (E-Mail und Passwort) einigen, die jeder verwenden wird.
1749
+ greeting: Hallo %{name},
1750
+ instructions_1_html: 'Verwenden Sie die folgenden temporären Anmeldeinformationen, um sich einzuloggen: <br><br> Benutzername: <strong>%{email}</strong> <br> Passwort: <strong>%{password}</strong><br><br>'
1751
+ instructions_2_html: Geben Sie eine neue E-Mail-Adresse und ein Passwort ein.
1752
+ instructions_3_html: Teilen Sie die ausgewählten Anmeldedaten mit Ihren Kollegen, damit die gesamte Gruppe weiterhin auf das Konto zugreifen kann.
1753
+ instructions_title_html: "<strong>Was Sie tun müssen</strong>"
1754
+ subject: Wichtige Aktualisierung für Ihr Gruppenprofil
1732
1755
  user_report_mailer:
1733
1756
  notify:
1734
1757
  body_1: Benutzer %{user} wurde von %{token} gemeldet
@@ -1778,6 +1801,7 @@ de:
1778
1801
  send_paranoid_instructions: Wenn Ihre E-Mail-Adresse in unserer Datenbank vorhanden ist, erhalten Sie innerhalb weniger Minuten eine E-Mail mit Anweisungen zur Bestätigung Ihrer E-Mail-Adresse.
1779
1802
  failure:
1780
1803
  already_authenticated: Sie sind bereits angemeldet.
1804
+ csrf_token: Ihre Anfrage konnte nicht verifiziert werden. Bitte versuchen Sie es erneut.
1781
1805
  inactive: Dein Benutzerkonto ist noch nicht aktiviert.
1782
1806
  invalid: Ungültige %{authentication_keys} oder Passwort
1783
1807
  invited: Sie haben eine ausstehende Einladung, akzeptieren Sie sie, um die Erstellung Ihres Kontos abzuschließen.
@@ -1792,6 +1816,7 @@ de:
1792
1816
  nickname_help: Ihr Pseudonym auf %{organization}. Kann nur Buchstaben, Zahlen, '-' und '_' enthalten.
1793
1817
  submit_button: Speichern
1794
1818
  subtitle: Wenn Sie die Einladung annehmen, geben Sie bitte Ihren Kontonamen und Ihr Passwort ein.
1819
+ subtitle_no_password: Wenn Sie die Einladung annehmen, geben Sie bitte Ihren Kontonamen ein.
1795
1820
  invitation_removed: Ihre Einladung wurde entfernt.
1796
1821
  invitation_token_invalid: Das angegebene Einladungstoken ist nicht gültig!
1797
1822
  new:
@@ -1799,6 +1824,8 @@ de:
1799
1824
  submit_button: Eine Einladung schicken
1800
1825
  no_invitations_remaining: Keine Einladungen übrig
1801
1826
  send_instructions: Eine Einladungs-E-Mail wurde an %{email}gesendet.
1827
+ updated: Einladung erfolgreich angenommen. Sie sind jetzt angemeldet.
1828
+ updated_not_active: Einladung erfolgreich angenommen.
1802
1829
  mailer:
1803
1830
  confirmation_instructions:
1804
1831
  action: Konto bestätigen
@@ -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:
@@ -1819,6 +1820,7 @@ eu:
1819
1820
  nickname_help: Zure ezizena %{organization}-an. Letrak, zenbakiak, '-' eta '_' soilik eduki ditzake.
1820
1821
  submit_button: Gorde
1821
1822
  subtitle: Gonbidapena onartzen baduzu, mesedez, ezarri zure ezizena eta pasahitza.
1823
+ subtitle_no_password: Gonbidapena onartzen baduzu, jarri zure ezizena.
1822
1824
  invitation_removed: Zure gonbidapena kendu egin da.
1823
1825
  invitation_token_invalid: Gonbidapen token hori ez da baliozkoa!
1824
1826
  new:
@@ -1826,6 +1828,8 @@ eu:
1826
1828
  submit_button: Bidali gonbidapena
1827
1829
  no_invitations_remaining: Ez da gonbidapenik geratzen
1828
1830
  send_instructions: Gonbidapen-mezu elektroniko bat %{email} helbidera bidali da.
1831
+ updated: Gonbidapena behar bezala onartu da. Orain erregistratuta zaude.
1832
+ updated_not_active: Gonbidapena behar bezala onartu da.
1829
1833
  mailer:
1830
1834
  confirmation_instructions:
1831
1835
  action: Berretsi nire kontua
@@ -1821,6 +1821,7 @@ fi-pl:
1821
1821
  nickname_help: Nimimerkkisi palvelussa %{organization}. Voi sisältää ainoastaan kirjaimia, numeroita sekä yhdysmerkkejä "-" ja alaviivoja "_".
1822
1822
  submit_button: Tallenna
1823
1823
  subtitle: Jos hyväksyt kutsun, aseta käyttäjänimesi ja salasana.
1824
+ subtitle_no_password: Jos hyväksyt kutsun, aseta tilillesi nimi.
1824
1825
  invitation_removed: Kutsusi peruutettiin.
1825
1826
  invitation_token_invalid: Käyttämäsi kutsuavain ei ole voimassa!
1826
1827
  new:
@@ -1828,6 +1829,8 @@ fi-pl:
1828
1829
  submit_button: Lähetä kutsu
1829
1830
  no_invitations_remaining: Ei kutsuja jäljellä
1830
1831
  send_instructions: Kutsuviesti on lähetetty %{email}.
1832
+ updated: Kutsun hyväksyminen onnistui. Olet nyt kirjautunut sisään.
1833
+ updated_not_active: Kutsun hyväksyminen onnistui.
1831
1834
  mailer:
1832
1835
  confirmation_instructions:
1833
1836
  action: Vahvista käyttäjätilini
@@ -1887,6 +1890,8 @@ fi-pl:
1887
1890
  confirm_new_password: Vahvista uusi salasana
1888
1891
  new_password: Uusi salasana
1889
1892
  old_password_help: Vahvistaaksesi muutokset käyttäjätiliisi, anna nykyinen salasanasi.
1893
+ password_help: "Vähintään %{minimum_characters} merkkiä, vähintään 5 eri merkkiä, ei voi olla yleisesti käytetty salasana (esim. 123456), eikä voi vastata nimeäsi, nimimerkkiäsi, sähköpostiosoitettasi tai tämän palvelun verkko-osoitetta."
1894
+ password_help_admin: "Vähintään %{minimum_characters} merkkiä, vähintään 5 eri merkkiä, ei voi olla yleisesti käytetty salasana (esim. 123456), eikä voi vastata nimeäsi, nimimerkkiäsi, sähköpostiosoitettasi tai tämän palvelun verkko-osoitetta. Et myöskään voi käyttää aikaisempia salasanojasi."
1890
1895
  title: Salasanan vaihto
1891
1896
  new:
1892
1897
  forgot_your_password: Unohditko salasanasi?