decidim-comments 0.19.1 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/decidim/comments/bundle.js +66 -66
  3. data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -1
  4. data/app/cells/decidim/comments/comment_activity_cell.rb +2 -22
  5. data/app/cells/decidim/comments/comment_cell.rb +22 -0
  6. data/app/cells/decidim/comments/comment_m/footer.erb +5 -0
  7. data/app/cells/decidim/comments/comment_m/top.erb +7 -0
  8. data/app/cells/decidim/comments/comment_m_cell.rb +29 -0
  9. data/app/commands/decidim/comments/create_comment.rb +8 -8
  10. data/app/events/decidim/comments/comment_by_followed_user_group_event.rb +9 -0
  11. data/app/events/decidim/comments/comment_event.rb +15 -2
  12. data/app/events/decidim/comments/user_group_mentioned_event.rb +10 -0
  13. data/app/forms/decidim/comments/comment_form.rb +17 -1
  14. data/app/frontend/application/icon.component.tsx +16 -4
  15. data/app/frontend/comments/add_comment_form.component.test.tsx +34 -29
  16. data/app/frontend/comments/add_comment_form.component.tsx +48 -19
  17. data/app/frontend/comments/comment.component.test.tsx +36 -5
  18. data/app/frontend/comments/comment.component.tsx +311 -89
  19. data/app/frontend/comments/comment_order_selector.component.tsx +26 -7
  20. data/app/frontend/comments/comment_thread.component.test.tsx +9 -8
  21. data/app/frontend/comments/comment_thread.component.tsx +3 -1
  22. data/app/frontend/comments/comments.component.test.tsx +17 -14
  23. data/app/frontend/comments/comments.component.tsx +90 -9
  24. data/app/frontend/comments/down_vote_button.component.tsx +27 -9
  25. data/app/frontend/comments/up_vote_button.component.tsx +27 -9
  26. data/app/frontend/comments/vote_button.component.tsx +4 -0
  27. data/app/frontend/comments/vote_button_component.test.tsx +14 -8
  28. data/app/frontend/entry.ts +19 -0
  29. data/app/frontend/entry_test.ts +2 -0
  30. data/app/frontend/mutations/add_comment.mutation.graphql +2 -2
  31. data/app/frontend/mutations/down_vote.mutation.graphql +2 -2
  32. data/app/frontend/mutations/up_vote.mutation.graphql +2 -2
  33. data/app/frontend/queries/comments.query.graphql +3 -3
  34. data/app/frontend/support/schema.ts +326 -0
  35. data/app/helpers/decidim/comments/comment_cells_helper.rb +33 -0
  36. data/app/models/decidim/comments/comment.rb +96 -18
  37. data/app/models/decidim/comments/seed.rb +1 -1
  38. data/app/queries/decidim/comments/metrics/comments_metric_manage.rb +1 -6
  39. data/app/queries/decidim/comments/sorted_comments.rb +8 -2
  40. data/app/scrubbers/decidim/comments/user_input_scrubber.rb +20 -0
  41. data/app/services/decidim/comments/new_comment_notification_creator.rb +28 -3
  42. data/app/types/decidim/comments/commentable_interface.rb +3 -2
  43. data/app/types/decidim/comments/commentable_mutation_type.rb +6 -3
  44. data/config/locales/am-ET.yml +1 -0
  45. data/config/locales/ar.yml +10 -1
  46. data/config/locales/bg-BG.yml +6 -0
  47. data/config/locales/bg.yml +6 -0
  48. data/config/locales/ca.yml +24 -1
  49. data/config/locales/cs.yml +36 -13
  50. data/config/locales/da-DK.yml +1 -0
  51. data/config/locales/da.yml +1 -0
  52. data/config/locales/de.yml +23 -1
  53. data/config/locales/el-GR.yml +1 -0
  54. data/config/locales/el.yml +122 -0
  55. data/config/locales/en.yml +24 -1
  56. data/config/locales/eo.yml +1 -0
  57. data/config/locales/es-MX.yml +24 -1
  58. data/config/locales/es-PY.yml +24 -1
  59. data/config/locales/es.yml +24 -1
  60. data/config/locales/et-EE.yml +1 -0
  61. data/config/locales/et.yml +1 -0
  62. data/config/locales/eu.yml +4 -1
  63. data/config/locales/fi-plain.yml +24 -1
  64. data/config/locales/fi.yml +31 -8
  65. data/config/locales/fr-CA.yml +123 -0
  66. data/config/locales/fr.yml +25 -2
  67. data/config/locales/ga-IE.yml +1 -0
  68. data/config/locales/gl.yml +4 -1
  69. data/config/locales/hr-HR.yml +1 -0
  70. data/config/locales/hr.yml +1 -0
  71. data/config/locales/hu.yml +18 -2
  72. data/config/locales/id-ID.yml +4 -1
  73. data/config/locales/is-IS.yml +74 -0
  74. data/config/locales/is.yml +76 -0
  75. data/config/locales/it.yml +23 -1
  76. data/config/locales/ja-JP.yml +120 -0
  77. data/config/locales/ja.yml +121 -0
  78. data/config/locales/ko-KR.yml +1 -0
  79. data/config/locales/ko.yml +1 -0
  80. data/config/locales/lt-LT.yml +1 -0
  81. data/config/locales/lt.yml +1 -0
  82. data/config/locales/lv.yml +118 -0
  83. data/config/locales/mt-MT.yml +1 -0
  84. data/config/locales/mt.yml +1 -0
  85. data/config/locales/nl.yml +26 -3
  86. data/config/locales/no.yml +88 -1
  87. data/config/locales/om-ET.yml +1 -0
  88. data/config/locales/pl.yml +62 -40
  89. data/config/locales/pt-BR.yml +5 -2
  90. data/config/locales/pt.yml +47 -25
  91. data/config/locales/ro-RO.yml +124 -0
  92. data/config/locales/ru.yml +4 -1
  93. data/config/locales/sk-SK.yml +116 -0
  94. data/config/locales/sk.yml +120 -0
  95. data/config/locales/sl.yml +4 -0
  96. data/config/locales/so-SO.yml +1 -0
  97. data/config/locales/sr-CS.yml +20 -0
  98. data/config/locales/sv.yml +26 -3
  99. data/config/locales/ti-ER.yml +1 -0
  100. data/config/locales/tr-TR.yml +4 -1
  101. data/config/locales/uk.yml +4 -2
  102. data/config/locales/vi-VN.yml +1 -0
  103. data/config/locales/vi.yml +1 -0
  104. data/config/locales/zh-CN.yml +121 -0
  105. data/config/locales/zh-TW.yml +1 -0
  106. data/db/migrate/20200320105911_index_foreign_keys_in_decidim_comments_comments.rb +7 -0
  107. data/db/migrate/20200706123136_make_comments_handle_i18n.rb +41 -0
  108. data/db/migrate/20200828101910_add_commentable_counter_cache_to_comments.rb +9 -0
  109. data/lib/decidim/comments.rb +1 -0
  110. data/lib/decidim/comments/api/comment_type.rb +5 -1
  111. data/lib/decidim/comments/comment_serializer.rb +7 -2
  112. data/lib/decidim/comments/comment_vote_serializer.rb +5 -1
  113. data/lib/decidim/comments/commentable.rb +11 -0
  114. data/lib/decidim/comments/comments_helper.rb +28 -4
  115. data/lib/decidim/comments/engine.rb +13 -0
  116. data/lib/decidim/comments/markdown.rb +55 -0
  117. data/lib/decidim/comments/mutation_extensions.rb +8 -0
  118. data/lib/decidim/comments/query_extensions.rb +4 -0
  119. data/lib/decidim/comments/test/factories.rb +10 -1
  120. data/lib/decidim/comments/test/shared_examples/comment_event.rb +12 -2
  121. data/lib/decidim/comments/test/shared_examples/create_comment_context.rb +3 -2
  122. data/lib/decidim/comments/version.rb +1 -1
  123. metadata +72 -9
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Comments
5
+ # Custom helpers for comments cells.
6
+ #
7
+ module CommentCellsHelper
8
+ def renderable?
9
+ comment.present? && root_commentable.present?
10
+ end
11
+
12
+ def resource_link_text
13
+ comment.formatted_body
14
+ end
15
+
16
+ def resource_link_path
17
+ return root_commentable.polymorphic_resource_path(url_params) if root_commentable&.respond_to?(:polymorphic_resource_path)
18
+
19
+ resource_locator(root_commentable).path(url_params)
20
+ end
21
+
22
+ delegate :root_commentable, to: :comment
23
+
24
+ def root_commentable_title
25
+ decidim_html_escape(translated_attribute(root_commentable.title))
26
+ end
27
+
28
+ def url_params
29
+ { commentId: comment.id }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -13,6 +13,9 @@ module Decidim
13
13
  include Decidim::DataPortability
14
14
  include Decidim::Traceable
15
15
  include Decidim::Loggable
16
+ include Decidim::Searchable
17
+ include Decidim::TranslatableResource
18
+ include Decidim::TranslatableAttributes
16
19
 
17
20
  # Limit the max depth of a comment tree. If C is a comment and R is a reply:
18
21
  # C (depth 0)
@@ -22,22 +25,53 @@ module Decidim
22
25
  # |--R (depth 3)
23
26
  MAX_DEPTH = 3
24
27
 
28
+ translatable_fields :body
29
+
25
30
  belongs_to :commentable, foreign_key: "decidim_commentable_id", foreign_type: "decidim_commentable_type", polymorphic: true
26
- belongs_to :root_commentable, foreign_key: "decidim_root_commentable_id", foreign_type: "decidim_root_commentable_type", polymorphic: true
31
+ belongs_to :root_commentable, foreign_key: "decidim_root_commentable_id", foreign_type: "decidim_root_commentable_type", polymorphic: true, touch: true
27
32
  has_many :up_votes, -> { where(weight: 1) }, foreign_key: "decidim_comment_id", class_name: "CommentVote", dependent: :destroy
28
33
  has_many :down_votes, -> { where(weight: -1) }, foreign_key: "decidim_comment_id", class_name: "CommentVote", dependent: :destroy
29
34
 
35
+ # Updates the counter caches for the root_commentable when a comment is
36
+ # created or updated.
37
+ after_save :update_counter
38
+
39
+ # Updates the counter caches for the root_commentable when a comment is
40
+ # deleted.
41
+ after_destroy :update_counter
42
+
43
+ # Updates the counter caches for the root_commentable when a comment is
44
+ # touched, which happens when a comment was reported and its moderation
45
+ # is accepted and sets the comment as hidden.
46
+ after_touch :update_counter
47
+
48
+ before_validation :compute_depth
30
49
  validates :body, presence: true
31
- validates :depth, numericality: { greater_than_or_equal_to: 0 }
50
+ validates :depth, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: MAX_DEPTH }
32
51
  validates :alignment, inclusion: { in: [0, 1, -1] }
52
+ validate :body_length
53
+ validate :commentable_can_have_comments
33
54
 
34
- validates :body, length: { maximum: 1000 }
55
+ delegate :organization, to: :commentable
35
56
 
36
- validate :commentable_can_have_comments
57
+ translatable_fields :body
58
+ searchable_fields(
59
+ participatory_space: :itself,
60
+ A: :body,
61
+ datetime: :created_at
62
+ )
37
63
 
38
- before_save :compute_depth
64
+ def self.positive
65
+ where(alignment: 1)
66
+ end
39
67
 
40
- delegate :organization, to: :commentable
68
+ def self.neutral
69
+ where(alignment: 0)
70
+ end
71
+
72
+ def self.negative
73
+ where(alignment: -1)
74
+ end
41
75
 
42
76
  def participatory_space
43
77
  return root_commentable if root_commentable.is_a?(Decidim::Participable)
@@ -87,12 +121,13 @@ module Decidim
87
121
 
88
122
  # Public: Overrides the `reported_content_url` Reportable concern method.
89
123
  def reported_content_url
90
- ResourceLocatorPresenter.new(root_commentable).url(anchor: "comment_#{id}")
91
- end
124
+ url_params = { anchor: "comment_#{id}" }
92
125
 
93
- # Public: Returns the comment message ready to display (it is expected to include HTML)
94
- def formatted_body
95
- @formatted_body ||= Decidim::ContentProcessor.render(sanitized_body)
126
+ if root_commentable&.respond_to?(:polymorphic_resource_url)
127
+ root_commentable.polymorphic_resource_url(url_params)
128
+ else
129
+ ResourceLocatorPresenter.new(root_commentable).url(url_params)
130
+ end
96
131
  end
97
132
 
98
133
  def self.export_serializer
@@ -100,10 +135,10 @@ module Decidim
100
135
  end
101
136
 
102
137
  def self.newsletter_participant_ids(space)
103
- Decidim::Comments::Comment.includes(:root_commentable).not_hidden
104
- .where("decidim_comments_comments.decidim_author_id IN (?)", Decidim::User.where(organization: space.organization).pluck(:id))
105
- .where("decidim_comments_comments.decidim_author_type IN (?)", "Decidim::UserBaseEntity")
106
- .map(&:author).pluck(:id).flatten.compact.uniq
138
+ authors_sql = Decidim::Comments::Comment.select("DISTINCT decidim_comments_comments.decidim_author_id").not_hidden
139
+ .where("decidim_comments_comments.decidim_author_type" => "Decidim::UserBaseEntity").to_sql
140
+
141
+ Decidim::User.where(organization: space.organization).where("id IN (#{authors_sql})").pluck(:id)
107
142
  end
108
143
 
109
144
  def can_participate?(user)
@@ -112,8 +147,35 @@ module Decidim
112
147
  root_commentable.can_participate?(user)
113
148
  end
114
149
 
150
+ def formatted_body
151
+ Decidim::ContentProcessor.render(sanitize_content(render_markdown(translated_body)), "div")
152
+ end
153
+
154
+ def translated_body
155
+ @translated_body ||= translated_attribute(body, organization)
156
+ end
157
+
115
158
  private
116
159
 
160
+ def body_length
161
+ language = (body.keys - ["machine_translations"]).first
162
+ errors.add(:body, :too_long, count: comment_maximum_length) unless body[language].length <= comment_maximum_length
163
+ end
164
+
165
+ def comment_maximum_length
166
+ return unless commentable.commentable?
167
+ return component.settings.comments_max_length if component_settings_comments_max_length?
168
+ return organization.comments_max_length if organization.comments_max_length.positive?
169
+
170
+ 1000
171
+ end
172
+
173
+ def component_settings_comments_max_length?
174
+ return unless component&.settings.respond_to?(:comments_max_length)
175
+
176
+ component.settings.comments_max_length.positive?
177
+ end
178
+
117
179
  # Private: Check if commentable can have comments and if not adds
118
180
  # a validation error to the model
119
181
  def commentable_can_have_comments
@@ -125,9 +187,25 @@ module Decidim
125
187
  self.depth = commentable.depth + 1 if commentable.respond_to?(:depth)
126
188
  end
127
189
 
128
- # Private: Returns the comment body sanitized, stripping HTML tags
129
- def sanitized_body
130
- Rails::Html::Sanitizer.full_sanitizer.new.sanitize(body)
190
+ # Private: Returns the comment body sanitized, sanitizing HTML tags
191
+ def sanitize_content(content)
192
+ Decidim::ContentProcessor.sanitize(content)
193
+ end
194
+
195
+ # Private: Initializes the Markdown parser
196
+ def markdown
197
+ @markdown ||= Decidim::Comments::Markdown.new
198
+ end
199
+
200
+ # Private: converts the string from markdown to html
201
+ def render_markdown(string)
202
+ markdown.render(string)
203
+ end
204
+
205
+ def update_counter
206
+ return unless root_commentable
207
+
208
+ root_commentable.update_comments_count
131
209
  end
132
210
  end
133
211
  end
@@ -25,7 +25,7 @@ module Decidim
25
25
  params = {
26
26
  commentable: resource,
27
27
  root_commentable: resource,
28
- body: ::Faker::Lorem.sentence(50),
28
+ body: { en: ::Faker::Lorem.sentence(50) },
29
29
  author: author,
30
30
  user_group: user_group
31
31
  }
@@ -9,9 +9,6 @@ module Decidim
9
9
  end
10
10
 
11
11
  def save
12
- return @registry if @registry
13
-
14
- @registry = []
15
12
  query.each do |key, results|
16
13
  cumulative_value = results[:cumulative]
17
14
  next if cumulative_value.zero?
@@ -23,10 +20,8 @@ module Decidim
23
20
  organization: @organization, decidim_category_id: category_id,
24
21
  related_object_type: related_object_type, related_object_id: related_object_id)
25
22
  record.assign_attributes(cumulative: cumulative_value, quantity: quantity_value)
26
- @registry << record
23
+ record.save!
27
24
  end
28
- @registry.each(&:save!)
29
- @registry
30
25
  end
31
26
 
32
27
  private
@@ -30,8 +30,7 @@ module Decidim
30
30
  # loads comments replies. It uses Comment's MAX_DEPTH to load a maximum
31
31
  # level of nested replies.
32
32
  def query
33
- scope = Comment
34
- .where(commentable: commentable)
33
+ scope = base_scope
35
34
  .not_hidden
36
35
  .includes(:author, :user_group, :up_votes, :down_votes)
37
36
 
@@ -53,6 +52,13 @@ module Decidim
53
52
 
54
53
  private
55
54
 
55
+ def base_scope
56
+ id = @options[:id]
57
+ return Comment.where(root_commentable: commentable, id: id) if id.present?
58
+
59
+ Comment.where(commentable: commentable)
60
+ end
61
+
56
62
  def order_by_older(scope)
57
63
  scope.order(created_at: :asc)
58
64
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Comments
5
+ # Use this class as a scrubber to sanitize user input.
6
+ # https://stackoverflow.com/a/35073814/2110884.
7
+ class UserInputScrubber < Rails::Html::PermitScrubber
8
+ def initialize
9
+ super
10
+ self.tags = custom_allowed_tags
11
+ end
12
+
13
+ private
14
+
15
+ def custom_allowed_tags
16
+ %w(p blockquote)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -17,9 +17,12 @@ module Decidim
17
17
  # comment - the Comment from which to generate notifications.
18
18
  # mentioned_users - An ActiveRecord::Relation of the users that have been
19
19
  # mentioned
20
- def initialize(comment, mentioned_users)
20
+ # mentioned_groups - And ActiveRecord::Relation of the user_groups that have
21
+ # been mentioned
22
+ def initialize(comment, mentioned_users, mentioned_groups = nil)
21
23
  @comment = comment
22
24
  @mentioned_users = mentioned_users
25
+ @mentioned_groups = mentioned_groups
23
26
  @already_notified_users = [comment.author]
24
27
  end
25
28
 
@@ -28,14 +31,16 @@ module Decidim
28
31
  # Returns nothing.
29
32
  def create
30
33
  notify_mentioned_users
34
+ notify_mentioned_groups
31
35
  notify_parent_comment_author
32
36
  notify_author_followers
37
+ notify_user_group_followers
33
38
  notify_commentable_recipients
34
39
  end
35
40
 
36
41
  private
37
42
 
38
- attr_reader :comment, :mentioned_users, :already_notified_users
43
+ attr_reader :comment, :mentioned_users, :mentioned_groups, :already_notified_users
39
44
 
40
45
  def notify_mentioned_users
41
46
  affected_users = mentioned_users - already_notified_users
@@ -44,6 +49,17 @@ module Decidim
44
49
  notify(:user_mentioned, affected_users: affected_users)
45
50
  end
46
51
 
52
+ def notify_mentioned_groups
53
+ return unless mentioned_groups
54
+
55
+ mentioned_groups.each do |group|
56
+ affected_users = group.accepted_users - already_notified_users
57
+ @already_notified_users += affected_users
58
+
59
+ notify(:user_group_mentioned, affected_users: affected_users, extra: { group: group })
60
+ end
61
+ end
62
+
47
63
  # Notifies the author of a comment that their comment has been replied.
48
64
  # Only applies if the comment is a reply.
49
65
  def notify_parent_comment_author
@@ -62,6 +78,15 @@ module Decidim
62
78
  notify(:comment_by_followed_user, followers: followers)
63
79
  end
64
80
 
81
+ def notify_user_group_followers
82
+ return if comment.user_group.blank?
83
+
84
+ followers = comment.user_group.followers - already_notified_users
85
+ @already_notified_users += followers
86
+
87
+ notify(:comment_by_followed_user_group, followers: followers)
88
+ end
89
+
65
90
  # Notifies the users the `comment.commentable` resource implements as necessary.
66
91
  def notify_commentable_recipients
67
92
  followers = comment.commentable.users_to_notify_on_comment_created - already_notified_users
@@ -90,7 +115,7 @@ module Decidim
90
115
  extra: {
91
116
  comment_id: comment.id
92
117
  }
93
- }.merge(users)
118
+ }.deep_merge(users)
94
119
 
95
120
  Decidim::EventsManager.publish(data)
96
121
  end
@@ -29,9 +29,10 @@ module Decidim
29
29
  type !types[!CommentType]
30
30
 
31
31
  argument :orderBy, types.String, "Order the comments"
32
+ argument :singleCommentId, types.String, "ID of the single comment to look at"
32
33
 
33
34
  resolve lambda { |obj, args, _ctx|
34
- SortedComments.for(obj, order_by: args[:orderBy])
35
+ SortedComments.for(obj, order_by: args[:orderBy], id: args[:singleCommentId])
35
36
  }
36
37
  end
37
38
 
@@ -40,7 +41,7 @@ module Decidim
40
41
  description "The number of comments in all levels this resource holds"
41
42
 
42
43
  resolve lambda { |obj, _args, _ctx|
43
- obj.comments.count
44
+ obj.comments_count
44
45
  }
45
46
  end
46
47
 
@@ -16,9 +16,12 @@ module Decidim
16
16
  argument :userGroupId, types.ID, "The comment's user group id. Replaces the author."
17
17
 
18
18
  resolve lambda { |obj, args, ctx|
19
- params = { "comment" => { "body" => args[:body], "alignment" => args[:alignment], "user_group_id" => args[:userGroupId] } }
20
- form = Decidim::Comments::CommentForm.from_params(params).with_context(current_organization: ctx[:current_organization])
21
- Decidim::Comments::CreateComment.call(form, ctx[:current_user], obj) do
19
+ params = { "comment" => { "body" => args[:body], "alignment" => args[:alignment], "user_group_id" => args[:userGroupId], "commentable" => obj } }
20
+ form = Decidim::Comments::CommentForm.from_params(params).with_context(
21
+ current_organization: ctx[:current_organization],
22
+ current_component: obj.component
23
+ )
24
+ Decidim::Comments::CreateComment.call(form, ctx[:current_user]) do
22
25
  on(:ok) do |comment|
23
26
  return comment
24
27
  end
@@ -0,0 +1 @@
1
+ am:
@@ -4,6 +4,7 @@ ar:
4
4
  decidim/comments/comment_by_followed_user_event: تعليق
5
5
  decidim/comments/comment_created_event: تعليق
6
6
  decidim/comments/reply_created_event: تعليق الرد
7
+ decidim/comments/user_group_mentioned_event: أشير
7
8
  decidim/comments/user_mentioned_event: أشير
8
9
  activerecord:
9
10
  models:
@@ -50,6 +51,7 @@ ar:
50
51
  against: ضد
51
52
  in_favor: لصالح
52
53
  deleted_user: مشارك محذوف
54
+ hide_replies: إخفاء الردود
53
55
  reply: الرد
54
56
  report:
55
57
  action: أبلغ عن
@@ -61,7 +63,8 @@ ar:
61
63
  does_not_belong: يحتوي على نشاط غير قانوني أو تهديدات انتحارية أو معلومات شخصية أو أي شيء آخر تعتقد أنه لا ينتمي إلى %{organization_name}.
62
64
  offensive: يحتوي على العنصرية والتمييز الجنسي والتشهير والهجمات الشخصية والتهديدات بالقتل أو طلبات الانتحار أو أي شكل من أشكال خطاب الكراهية.
63
65
  spam: يحتوي على clickbait أو الإعلان أو الخدع أو روبوت البرامج النصية.
64
- title: الإبلاغ عن مشكلة
66
+ show_replies: اظهر الردود الـ %{replies_count}
67
+ single_comment_link_title: الحصول على رابط نحو تعليق واحد
65
68
  comment_order_selector:
66
69
  order:
67
70
  best_rated: أفضل تصنيف
@@ -74,7 +77,10 @@ ar:
74
77
  comments:
75
78
  blocked_comments_for_user_warning: لا يمكنك التعليق في هذه اللحظة ، ولكن يمكنك قراءة التعليقات السابقة.
76
79
  blocked_comments_warning: التعليقات معطلة في هذا الوقت ، لكن يمكنك قراءة التعليقات السابقة.
80
+ comment_details_title: تفاصيل التعليق
77
81
  loading: جارٍ تحميل التعليقات ...
82
+ single_comment_warning: يمكنك الإطلاع على التعليقات المتبقية الأخرى <a href="%{url}">هنا</a>.
83
+ single_comment_warning_title: إنك ترى تعليقا واحدا
78
84
  title: "%{count} تعليقات"
79
85
  events:
80
86
  comments:
@@ -83,6 +89,9 @@ ar:
83
89
  email_outro: لقد تلقيت هذا الإشعار لأنك تتابع %{author_name}. يمكنك إلغاء تتبع هذا المستخدم من صفحة ملفه الشخصي.
84
90
  email_subject: هناك تعليق جديد مِن %{author_name} على %{resource_title}
85
91
  notification_title: هناك تعليق جديد مِن <a href="%{author_path}">%{author_name} %{author_nickname}</a> على <a href="%{resource_path}">%{resource_title}</a>.
92
+ comment_by_followed_user_group:
93
+ email_subject: هناك تعليق جديد مِن %{author_name} على %{resource_title}
94
+ notification_title: هناك تعليق جديد مِن <a href="%{author_path}">%{author_name} %{author_nickname}</a> على <a href="%{resource_path}">%{resource_title}</a>.
86
95
  comment_created:
87
96
  email_intro: "%{resource_title} تم التعليق. يمكنك قراءة التعليق في هذه الصفحة:"
88
97
  email_outro: لقد تلقيت هذا الإشعار لأنك تتابع "%{resource_title}" أو مؤلفه. يمكنك إلغاء تتبعه من الرابط السابق.
@@ -0,0 +1,6 @@
1
+ bg:
2
+ activemodel:
3
+ models:
4
+ decidim/comments/comment_by_followed_user_event: Коментар
5
+ decidim/comments/comment_created_event: Коментар
6
+ decidim/comments/reply_created_event: Отговор на коментар
@@ -0,0 +1,6 @@
1
+ bg:
2
+ activemodel:
3
+ models:
4
+ decidim/comments/comment_by_followed_user_event: Коментар
5
+ decidim/comments/comment_created_event: Коментар
6
+ decidim/comments/reply_created_event: Отговор на коментар