decidim-assemblies 0.22.0 → 0.23.0

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/app/commands/decidim/assemblies/admin/import_assembly.rb +72 -0
  3. data/app/commands/decidim/assemblies/admin/update_assembly.rb +10 -5
  4. data/app/controllers/decidim/assemblies/admin/assembly_exports_controller.rb +24 -0
  5. data/app/controllers/decidim/assemblies/admin/assembly_imports_controller.rb +31 -0
  6. data/app/controllers/decidim/assemblies/admin/assembly_user_roles_controller.rb +2 -1
  7. data/app/controllers/decidim/assemblies/{assembly_widgets_controller.rb → widgets_controller.rb} +2 -2
  8. data/app/forms/decidim/assemblies/admin/assembly_form.rb +5 -6
  9. data/app/forms/decidim/assemblies/admin/assembly_import_form.rb +83 -0
  10. data/app/forms/decidim/assemblies/admin/assembly_user_role_form.rb +8 -2
  11. data/app/models/decidim/assemblies_type.rb +3 -0
  12. data/app/models/decidim/assembly.rb +14 -1
  13. data/app/permissions/decidim/assemblies/permissions.rb +5 -1
  14. data/app/presenters/decidim/assemblies/assembly_presenter.rb +29 -0
  15. data/app/serializers/decidim/assemblies/assembly_importer.rb +172 -0
  16. data/app/serializers/decidim/assemblies/assembly_serializer.rb +147 -0
  17. data/app/views/decidim/assemblies/admin/assemblies/index.html.erb +10 -0
  18. data/app/views/decidim/assemblies/admin/assembly_imports/_form.html.erb +46 -0
  19. data/app/views/decidim/assemblies/admin/assembly_imports/new.html.erb +7 -0
  20. data/app/views/decidim/assemblies/assemblies/show.html.erb +1 -1
  21. data/app/views/layouts/decidim/assembly.html.erb +1 -0
  22. data/config/locales/am-ET.yml +1 -0
  23. data/config/locales/bg.yml +7 -0
  24. data/config/locales/ca.yml +24 -2
  25. data/config/locales/cs.yml +29 -7
  26. data/config/locales/da.yml +1 -0
  27. data/config/locales/de.yml +19 -0
  28. data/config/locales/el.yml +19 -0
  29. data/config/locales/en.yml +22 -0
  30. data/config/locales/eo.yml +1 -0
  31. data/config/locales/es-MX.yml +22 -0
  32. data/config/locales/es-PY.yml +22 -0
  33. data/config/locales/es.yml +24 -2
  34. data/config/locales/et.yml +1 -0
  35. data/config/locales/fi-plain.yml +19 -0
  36. data/config/locales/fi.yml +22 -3
  37. data/config/locales/fr-CA.yml +22 -0
  38. data/config/locales/fr.yml +22 -0
  39. data/config/locales/hr.yml +1 -0
  40. data/config/locales/hu.yml +18 -0
  41. data/config/locales/is.yml +263 -0
  42. data/config/locales/it.yml +19 -0
  43. data/config/locales/ja-JP.yml +145 -126
  44. data/config/locales/ja.yml +471 -0
  45. data/config/locales/ko-KR.yml +1 -0
  46. data/config/locales/ko.yml +1 -0
  47. data/config/locales/lt.yml +1 -0
  48. data/config/locales/{lv-LV.yml → lv.yml} +0 -0
  49. data/config/locales/mt.yml +1 -0
  50. data/config/locales/nl.yml +19 -0
  51. data/config/locales/no.yml +19 -0
  52. data/config/locales/om-ET.yml +1 -0
  53. data/config/locales/pl.yml +37 -18
  54. data/config/locales/pt.yml +19 -0
  55. data/config/locales/ro-RO.yml +19 -0
  56. data/config/locales/sl.yml +151 -0
  57. data/config/locales/so-SO.yml +1 -0
  58. data/config/locales/sv.yml +22 -0
  59. data/config/locales/ti-ER.yml +1 -0
  60. data/config/locales/tr-TR.yml +19 -0
  61. data/config/locales/vi-VN.yml +1 -0
  62. data/config/locales/vi.yml +1 -0
  63. data/config/locales/zh-CN.yml +471 -0
  64. data/config/locales/zh-TW.yml +1 -0
  65. data/lib/decidim/assemblies/admin_engine.rb +7 -0
  66. data/lib/decidim/assemblies/engine.rb +1 -1
  67. data/lib/decidim/assemblies/participatory_space.rb +20 -12
  68. data/lib/decidim/assemblies/test/factories.rb +3 -3
  69. data/lib/decidim/assemblies/version.rb +1 -1
  70. metadata +40 -12
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Assemblies
5
+ # A factory class to ensure we always create Assemblies the same way since it involves some logic.
6
+ class AssemblyImporter < Decidim::Importers::Importer
7
+ def initialize(organization, user)
8
+ @organization = organization
9
+ @user = user
10
+ end
11
+
12
+ # Public: Creates a new Assembly.
13
+ #
14
+ # attributes - The Hash of attributes to create the Assembly with.
15
+ # user - The user that performs the action.
16
+ # opts - The options MUST contain:
17
+ # - title: The +title+ for the new Assembly
18
+ # - slug: The +slug+ for the new Assembly
19
+ #
20
+ # Returns a Assembly.
21
+ def import(attributes, _user, opts)
22
+ title = opts[:title]
23
+ slug = opts[:slug]
24
+ Decidim.traceability.perform_action!(:create, Assembly, @user, visibility: "all") do
25
+ @imported_assembly = Assembly.new(
26
+ organization: @organization,
27
+ title: title,
28
+ slug: slug,
29
+ hashtag: attributes["hashtag"],
30
+ subtitle: attributes["subtitle"],
31
+ short_description: attributes["short_description"],
32
+ description: attributes["description"],
33
+ promoted: attributes["promoted"],
34
+ developer_group: attributes["developer_group"],
35
+ local_area: attributes["local_area"],
36
+ target: attributes["target"],
37
+ participatory_scope: attributes["participatory_scope"],
38
+ participatory_structure: attributes["participatory_structure"],
39
+ show_statistics: attributes["show_statistics"],
40
+ scopes_enabled: attributes["scopes_enabled"],
41
+ private_space: attributes["private_space"],
42
+ reference: attributes["reference"],
43
+ purpose_of_action: attributes["purpose_of_action"],
44
+ composition: attributes["composition"],
45
+ duration: attributes["duration"],
46
+ creation_date: attributes["creation_date"],
47
+ decidim_scope_id: attributes["decidim_scope_id"],
48
+ closing_date_reason: attributes["closing_date_reason"],
49
+ included_at: attributes["included_at"],
50
+ closing_date: attributes["closing_date"],
51
+ created_by_other: attributes["created_by_other"],
52
+ internal_organisation: attributes["internal_organisation"],
53
+ is_transparent: attributes["is_transparent"],
54
+ special_features: attributes["special_features"],
55
+ twitter_handler: attributes["twitter_handler"],
56
+ instagram_handler: attributes["instagram_handler"],
57
+ facebook_handler: attributes["facebook_handler"],
58
+ youtube_handler: attributes["youtube_handler"],
59
+ github_handler: attributes["github_handler"],
60
+ created_by: attributes["created_by"],
61
+ meta_scope: attributes["meta_scope"]
62
+ )
63
+ @imported_assembly.remote_hero_image_url = attributes["remote_hero_image_url"] if remote_file_exists?(attributes["remote_hero_image_url"])
64
+ @imported_assembly.remote_banner_image_url = attributes["remote_banner_image_url"] if remote_file_exists?(attributes["remote_banner_image_url"])
65
+ @imported_assembly.save!
66
+ @imported_assembly
67
+ end
68
+ end
69
+
70
+ def import_assemblies_type(type_id)
71
+ return if Decidim::AssembliesType.find_by(id: type_id).nil?
72
+
73
+ @imported_assembly.decidim_assemblies_type_id = type_id
74
+ end
75
+
76
+ def import_categories(categories)
77
+ return if categories.nil?
78
+
79
+ categories.map do |category_attributes|
80
+ category = Decidim.traceability.create!(
81
+ Category,
82
+ @user,
83
+ name: category_attributes["name"],
84
+ description: category_attributes["description"],
85
+ parent_id: category_attributes["parent_id"],
86
+ participatory_space: @imported_assembly
87
+ )
88
+ next if category_attributes["subcategories"].nil?
89
+
90
+ category_attributes["subcategories"].map do |subcategory_attributes|
91
+ Decidim.traceability.create!(
92
+ Category,
93
+ @user,
94
+ name: subcategory_attributes["name"],
95
+ description: subcategory_attributes["description"],
96
+ parent_id: category.id,
97
+ participatory_space: @imported_assembly
98
+ )
99
+ end
100
+ end
101
+ end
102
+
103
+ def import_folders_and_attachments(attachments)
104
+ return if attachments["files"].nil?
105
+
106
+ attachments["files"].map do |file|
107
+ next unless remote_file_exists?(file["remote_file_url"])
108
+
109
+ file_tmp = URI.open(file["remote_file_url"])
110
+
111
+ Decidim.traceability.perform_action!("create", Attachment, @user) do
112
+ attachment = Attachment.new(
113
+ title: file["title"],
114
+ description: file["description"],
115
+ content_type: file_tmp.content_type,
116
+ attached_to: @imported_assembly,
117
+ weight: file["weight"],
118
+ file: form.file, # Define attached_to before this
119
+ file_size: file_tmp.size
120
+ )
121
+ attachment.create_attachment_collection(file["attachment_collection"])
122
+ attachment.save!
123
+ attachment
124
+ end
125
+ end
126
+
127
+ attachments["attachment_collections"].map do |collection|
128
+ Decidim.traceability.perform_action!("create", AttachmentCollection, @user) do
129
+ create_attachment_collection(collection)
130
+ end
131
+ end
132
+ end
133
+
134
+ # +components+: An Array of Hashes, each corresponding with the settings of a Decidim::Component.
135
+ def import_components(components)
136
+ return if components.nil?
137
+
138
+ importer = Decidim::Importers::ParticipatorySpaceComponentsImporter.new(@imported_assembly)
139
+ importer.import(components, @user)
140
+ end
141
+
142
+ private
143
+
144
+ def create_attachment_collection(attributes)
145
+ return unless attributes.compact.any?
146
+
147
+ attachment_collection = AttachmentCollection.find_or_initialize_by(
148
+ name: attributes["name"],
149
+ weight: attributes["weight"],
150
+ description: attributes["description"],
151
+ collection_for: @imported_assembly
152
+ )
153
+ attachment_collection.save!
154
+ attachment_collection
155
+ end
156
+
157
+ def remote_file_exists?(url)
158
+ return if url.nil?
159
+
160
+ accepted = ["image", "application/pdf"]
161
+ url = URI.parse(url)
162
+ http_connection = Net::HTTP.new(url.host, url.port)
163
+ http_connection.use_ssl = true if url.scheme == "https"
164
+ http_connection.start do |http|
165
+ return http.head(url.request_uri)["Content-Type"].start_with?(*accepted)
166
+ end
167
+ rescue StandardError
168
+ nil
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Assemblies
5
+ # This class serializes an Assembly so it can be exported to CSV, JSON or other formats.
6
+ class AssemblySerializer < Decidim::Exporters::Serializer
7
+ include Decidim::ApplicationHelper
8
+ include Decidim::ResourceHelper
9
+ include Decidim::TranslationsHelper
10
+
11
+ # Public: Initializes the serializer with a assembly.
12
+ def initialize(assembly)
13
+ @assembly = assembly
14
+ end
15
+
16
+ # Public: Exports a hash with the serialized data for this assembly.
17
+ def serialize
18
+ {
19
+ id: assembly.id,
20
+ slug: assembly.slug,
21
+ hashtag: assembly.hashtag,
22
+ decidim_organization_id: assembly.decidim_organization_id,
23
+ title: assembly.title,
24
+ subtitle: assembly.subtitle,
25
+ short_description: assembly.short_description,
26
+ description: assembly.description,
27
+ remote_hero_image_url: Decidim::Assemblies::AssemblyPresenter.new(assembly).hero_image_url,
28
+ remote_banner_image_url: Decidim::Assemblies::AssemblyPresenter.new(assembly).banner_image_url,
29
+ promoted: assembly.promoted,
30
+ developer_group: assembly.developer_group,
31
+ meta_scope: assembly.meta_scope,
32
+ local_area: assembly.local_area,
33
+ target: assembly.target,
34
+ decidim_scope_id: assembly.decidim_scope_id,
35
+ paticipatory_scope: assembly.participatory_scope,
36
+ participatory_structure: assembly.participatory_structure,
37
+ show_statistics: assembly.show_statistics,
38
+ scopes_enabled: assembly.scopes_enabled,
39
+ private_space: assembly.private_space,
40
+ reference: assembly.reference,
41
+ purpose_of_action: assembly.purpose_of_action,
42
+ composition: assembly.composition,
43
+ duration: assembly.duration,
44
+ participatory_scope: assembly.participatory_scope,
45
+ included_at: assembly.included_at,
46
+ closing_date: assembly.closing_date,
47
+ created_by: assembly.created_by,
48
+ creation_date: assembly.creation_date,
49
+ closing_date_reason: assembly.closing_date_reason,
50
+ internal_organisation: assembly.internal_organisation,
51
+ is_transparent: assembly.is_transparent,
52
+ special_features: assembly.special_features,
53
+ twitter_handler: assembly.twitter_handler,
54
+ instagram_handler: assembly.instagram_handler,
55
+ facebook_handler: assembly.facebook_handler,
56
+ youtube_handler: assembly.youtube_handler,
57
+ github_handler: assembly.github_handler,
58
+ created_by_other: assembly.created_by_other,
59
+ decidim_assemblies_type_id: assembly.decidim_assemblies_type_id,
60
+ area: {
61
+ id: assembly.area.try(:id),
62
+ name: assembly.area.try(:name) || empty_translatable
63
+ },
64
+ scope: {
65
+ id: assembly.scope.try(:id),
66
+ name: assembly.scope.try(:name) || empty_translatable
67
+ },
68
+
69
+ assembly_categories: serialize_categories,
70
+ attachments: {
71
+ attachment_collections: serialize_attachment_collections,
72
+ files: serialize_attachments
73
+ },
74
+ components: serialize_components
75
+ }
76
+ end
77
+
78
+ private
79
+
80
+ attr_reader :assembly
81
+
82
+ def serialize_categories
83
+ return unless assembly.categories.first_class.any?
84
+
85
+ assembly.categories.first_class.map do |category|
86
+ {
87
+ id: category.try(:id),
88
+ name: category.try(:name),
89
+ description: category.try(:description),
90
+ parent_id: category.try(:parent_id),
91
+ subcategories: serialize_subcategories(category.subcategories)
92
+ }
93
+ end
94
+ end
95
+
96
+ def serialize_subcategories(subcategories)
97
+ return unless subcategories.any?
98
+
99
+ subcategories.map do |subcategory|
100
+ {
101
+ id: subcategory.try(:id),
102
+ name: subcategory.try(:name),
103
+ description: subcategory.try(:description),
104
+ parent_id: subcategory.try(:parent_id)
105
+ }
106
+ end
107
+ end
108
+
109
+ def serialize_attachment_collections
110
+ return unless assembly.attachment_collections.any?
111
+
112
+ assembly.attachment_collections.map do |collection|
113
+ {
114
+ id: collection.try(:id),
115
+ name: collection.try(:name),
116
+ weight: collection.try(:weight),
117
+ description: collection.try(:description)
118
+ }
119
+ end
120
+ end
121
+
122
+ def serialize_attachments
123
+ return unless assembly.attachments.any?
124
+
125
+ assembly.attachments.map do |attachment|
126
+ {
127
+ id: attachment.try(:id),
128
+ title: attachment.try(:title),
129
+ weight: attachment.try(:weight),
130
+ description: attachment.try(:description),
131
+ attachment_collection: {
132
+ name: attachment.attachment_collection.try(:name),
133
+ weight: attachment.attachment_collection.try(:weight),
134
+ description: attachment.attachment_collection.try(:description)
135
+ },
136
+ remote_file_url: Decidim::AttachmentPresenter.new(attachment).attachment_file_url
137
+ }
138
+ end
139
+ end
140
+
141
+ def serialize_components
142
+ serializer = Decidim::Exporters::ParticipatorySpaceComponentsSerializer.new(@assembly)
143
+ serializer.serialize
144
+ end
145
+ end
146
+ end
147
+ end
@@ -8,6 +8,12 @@
8
8
 
9
9
  <%= t("assemblies", scope: "decidim.admin.titles") %>
10
10
 
11
+ <% if allowed_to? :import, :assembly %>
12
+ <%= link_to t("actions.import_assembly", scope: "decidim.admin"),
13
+ new_import_path,
14
+ class: "button tiny button--title" %>
15
+ <% end %>
16
+
11
17
  <% if allowed_to? :create, :assembly %>
12
18
  <%= link_to t("actions.new_assembly", scope: "decidim.admin"),
13
19
  new_assembly_path(parent_id: parent_assembly&.id),
@@ -76,6 +82,10 @@
76
82
  <% end %>
77
83
  </td>
78
84
  <td class="table-list__actions">
85
+ <% if allowed_to? :create, :assembly, assembly: assembly %>
86
+ <%= icon_link_to "data-transfer-download", assembly_export_path(assembly), t("actions.export", scope: "decidim.admin"), method: :post, class: "action-icon--export" %>
87
+ <% end %>
88
+
79
89
  <% if allowed_to? :create, :assembly, assembly: assembly %>
80
90
  <%= icon_link_to "clipboard", new_assembly_copy_path(assembly), t("actions.duplicate", scope: "decidim.admin"), class: "action-icon--copy" %>
81
91
  <% end %>
@@ -0,0 +1,46 @@
1
+ <%= javascript_include_tag "decidim/slug_form" %>
2
+
3
+ <div class="card" id="assemblies">
4
+ <div class="card-divider">
5
+ <h2 class="card-title"><%= title %></h2>
6
+ </div>
7
+ <div class="card-section">
8
+ <div class="row column">
9
+ <div class="row">
10
+ <div class="column xlarge-8">
11
+ <%= form.translated :text_field, :title, autofocus: true %>
12
+ </div>
13
+ <div class="column xlarge-4 slug">
14
+ <%= form.text_field :slug %>
15
+ <p class="help-text"><%== t(".slug_help", url: decidim_form_slug_url(:assemblies, form.object.slug)) %></p>
16
+ </div>
17
+ <div class="column xlarge-8">
18
+ <fieldset>
19
+ <legend><%= t(".document_legend") %> </legend>
20
+ <div class="row column">
21
+ <%= form.upload :document, optional: false %>
22
+ </div>
23
+ </fieldset>
24
+ </div>
25
+ </div>
26
+ <div class="card">
27
+ <div class="card-divider">
28
+ <legend><%= select %></legend>
29
+ </div>
30
+ <div class="card-section">
31
+ <div class="row">
32
+ <div class="columns xlarge-3">
33
+ <%= form.check_box :import_categories %>
34
+ </div>
35
+ <div class="columns xlarge-3">
36
+ <%= form.check_box :import_attachments %>
37
+ </div>
38
+ <div class="columns xlarge-6">
39
+ <%= form.check_box :import_components %>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
@@ -0,0 +1,7 @@
1
+ <%= decidim_form_for(@form, url: imports_path, method: :post, html: { class: "form import_assembly" }) do |f| %>
2
+ <%= render partial: "form", object: f, locals: { title: t("assembly_imports.new.title", scope: "decidim.admin"), select: t("assembly_imports.new.select", scope: "decidim.admin") } %>
3
+
4
+ <div class="button--double form-general-submit">
5
+ <%= f.submit t("assembly_imports.new.import", scope: "decidim.admin") %>
6
+ </div>
7
+ <% end %>
@@ -209,7 +209,7 @@ edit_link(
209
209
  </div>
210
210
  <%= resource_reference(current_participatory_space) %>
211
211
  <%= render partial: "decidim/shared/share_modal" %>
212
- <%= embed_modal_for assembly_assembly_widget_url(current_participatory_space, format: :js) %>
212
+ <%= embed_modal_for assembly_widget_url(current_participatory_space, format: :js) %>
213
213
  </div>
214
214
  </div>
215
215
 
@@ -7,6 +7,7 @@
7
7
 
8
8
  <%= render "layouts/decidim/application" do %>
9
9
  <%= render partial: "layouts/decidim/assembly_header" %>
10
+ <%= cell "decidim/translation_bar", current_organization %>
10
11
  <div class="wrapper">
11
12
  <%= yield %>
12
13
  </div>
@@ -0,0 +1 @@
1
+ am:
@@ -0,0 +1,7 @@
1
+ bg:
2
+ activemodel:
3
+ attributes:
4
+ assembly:
5
+ area_id: Район
6
+ assembly_type: Тип събрание
7
+ assembly_type_other: Друг тип събрание
@@ -64,6 +64,12 @@ ca:
64
64
  email: Correu electrònic
65
65
  name: Nom
66
66
  role: Rol de participant
67
+ errors:
68
+ models:
69
+ assembly:
70
+ attributes:
71
+ document:
72
+ invalid_document_type: 'Tipus de document invàlid. Els formats que s''accepten són: %{valid_mime_types}'
67
73
  activerecord:
68
74
  models:
69
75
  decidim/assembly:
@@ -78,6 +84,7 @@ ca:
78
84
  decidim:
79
85
  admin:
80
86
  actions:
87
+ import_assembly: Importar
81
88
  new_assembly: Nova assemblea
82
89
  new_assembly_type: Nou tipus d'assemblea
83
90
  assemblies:
@@ -122,6 +129,14 @@ ca:
122
129
  copy: Còpia
123
130
  select: Selecciona quines dades vols duplicar
124
131
  title: Assemblea duplicada
132
+ assembly_imports:
133
+ create:
134
+ error: Hi ha hagut un problema important aquesta assemblea.
135
+ success: Assemblea importada amb èxit.
136
+ new:
137
+ import: Importar
138
+ select: Selecciona quines dades vols importar
139
+ title: Importar assemblea
125
140
  assembly_members:
126
141
  create:
127
142
  error: S'ha produït un error en afegir un membre per a aquesta assemblea.
@@ -269,6 +284,10 @@ ca:
269
284
  assembly_copies:
270
285
  form:
271
286
  slug_help: 'Els noms curts d''URL s''utilitzen per generar els URL que apunten a aquesta assemblea. Només accepta lletres, números i guions, i ha de començar amb una lletra. Exemple: %{url}'
287
+ assembly_imports:
288
+ form:
289
+ document_legend: Afegir un document
290
+ slug_help: 'Els noms curts d''URL s''utilitzen per generar les URL que apunten a aquesta assemblea. Només accepta lletres, números i guions, i ha de començar amb una lletra. Exemple: %{url}'
272
291
  assembly_members:
273
292
  form:
274
293
  existing_user: Participant existent
@@ -286,6 +305,9 @@ ca:
286
305
  content_blocks:
287
306
  highlighted_assemblies:
288
307
  max_results: Quantitat màxima d'elements que es mostraran
308
+ new_import:
309
+ accepted_types:
310
+ json: JSON
289
311
  assembly_members:
290
312
  index:
291
313
  members: Membres
@@ -385,10 +407,10 @@ ca:
385
407
  events:
386
408
  assemblies:
387
409
  create_assembly_member:
388
- email_intro: Una administradora de la assemblea <a href="%{resource_url}">%{resource_name}</a> t'ha afegit com a un dels seus membres.
410
+ email_intro: Una administradora de l'assemblea <a href="%{resource_url}">%{resource_name}</a> t'ha afegit com a un dels seus membres.
389
411
  email_outro: Reps aquesta notificació perquè t'han convidat a una assemblea. Entra en <a href="%{resource_path}">assembly page</a> per a contribuir-hi!
390
412
  email_subject: T'han convidat a ser membre de l'assemblea %{resource_name}!
391
- notification_title: T'han registrat com a membre de l'assemblea <a href="%{resource_path}">%{resource_name}</a>. Entra a <a href="%{resource_path}">assembly page</a> per a contribuir-hi!
413
+ notification_title: T'han registrat com a membre de l'assemblea <a href="%{resource_path}">%{resource_name}</a>. Entra a <a href="%{resource_path}">la pàgina de l'assemblea</a> per a contribuir-hi!
392
414
  assembly:
393
415
  role_assigned:
394
416
  email_intro: T'han assignat el rol de %{role} a l'assemblea "%{resource_title}".