noticed 1.6.3 → 2.0.1

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +269 -237
  3. data/app/jobs/noticed/application_job.rb +9 -0
  4. data/app/jobs/noticed/event_job.rb +19 -0
  5. data/app/models/concerns/noticed/deliverable.rb +115 -0
  6. data/app/models/concerns/noticed/notification_methods.rb +17 -0
  7. data/app/models/concerns/noticed/readable.rb +78 -0
  8. data/app/models/noticed/application_record.rb +6 -0
  9. data/app/models/noticed/deliverable/deliver_by.rb +43 -0
  10. data/app/models/noticed/event.rb +15 -0
  11. data/app/models/noticed/notification.rb +14 -0
  12. data/db/migrate/20231215190233_create_noticed_tables.rb +25 -0
  13. data/lib/generators/noticed/delivery_method_generator.rb +1 -1
  14. data/lib/generators/noticed/install_generator.rb +19 -0
  15. data/lib/generators/noticed/{notification_generator.rb → notifier_generator.rb} +2 -2
  16. data/lib/generators/noticed/templates/README +5 -4
  17. data/lib/generators/noticed/templates/delivery_method.rb.tt +4 -8
  18. data/lib/generators/noticed/templates/notifier.rb.tt +24 -0
  19. data/lib/noticed/api_client.rb +44 -0
  20. data/lib/noticed/bulk_delivery_method.rb +46 -0
  21. data/lib/noticed/bulk_delivery_methods/discord.rb +11 -0
  22. data/lib/noticed/bulk_delivery_methods/slack.rb +17 -0
  23. data/lib/noticed/bulk_delivery_methods/webhook.rb +18 -0
  24. data/lib/noticed/coder.rb +2 -0
  25. data/lib/noticed/delivery_method.rb +51 -0
  26. data/lib/noticed/delivery_methods/action_cable.rb +7 -39
  27. data/lib/noticed/delivery_methods/discord.rb +11 -0
  28. data/lib/noticed/delivery_methods/email.rb +13 -45
  29. data/lib/noticed/delivery_methods/fcm.rb +23 -64
  30. data/lib/noticed/delivery_methods/ios.rb +25 -112
  31. data/lib/noticed/delivery_methods/microsoft_teams.rb +5 -22
  32. data/lib/noticed/delivery_methods/slack.rb +6 -16
  33. data/lib/noticed/delivery_methods/test.rb +2 -12
  34. data/lib/noticed/delivery_methods/twilio_messaging.rb +37 -0
  35. data/lib/noticed/delivery_methods/vonage_sms.rb +20 -0
  36. data/lib/noticed/delivery_methods/webhook.rb +17 -0
  37. data/lib/noticed/engine.rb +1 -9
  38. data/lib/noticed/required_options.rb +21 -0
  39. data/lib/noticed/translation.rb +7 -3
  40. data/lib/noticed/version.rb +1 -1
  41. data/lib/noticed.rb +30 -15
  42. metadata +29 -40
  43. data/lib/generators/noticed/model/base_generator.rb +0 -47
  44. data/lib/generators/noticed/model/mysql_generator.rb +0 -18
  45. data/lib/generators/noticed/model/postgresql_generator.rb +0 -18
  46. data/lib/generators/noticed/model/sqlite3_generator.rb +0 -18
  47. data/lib/generators/noticed/model_generator.rb +0 -63
  48. data/lib/generators/noticed/templates/notification.rb.tt +0 -27
  49. data/lib/noticed/base.rb +0 -160
  50. data/lib/noticed/delivery_methods/base.rb +0 -95
  51. data/lib/noticed/delivery_methods/database.rb +0 -34
  52. data/lib/noticed/delivery_methods/twilio.rb +0 -51
  53. data/lib/noticed/delivery_methods/vonage.rb +0 -40
  54. data/lib/noticed/has_notifications.rb +0 -49
  55. data/lib/noticed/model.rb +0 -85
  56. data/lib/noticed/notification_channel.rb +0 -15
  57. data/lib/noticed/text_coder.rb +0 -16
  58. data/lib/rails_6_polyfills/actioncable/test_adapter.rb +0 -70
  59. data/lib/rails_6_polyfills/actioncable/test_helper.rb +0 -143
  60. data/lib/rails_6_polyfills/activejob/serializers.rb +0 -240
  61. data/lib/rails_6_polyfills/base.rb +0 -18
  62. data/lib/tasks/noticed_tasks.rake +0 -4
@@ -1,46 +1,14 @@
1
1
  module Noticed
2
2
  module DeliveryMethods
3
- class ActionCable < Base
4
- def deliver
5
- channel.broadcast_to stream, format
6
- end
3
+ class ActionCable < DeliveryMethod
4
+ required_options :channel, :stream, :message
7
5
 
8
- private
9
-
10
- def format
11
- if (method = options[:format])
12
- notification.send(method)
13
- else
14
- notification.params
15
- end
16
- end
17
-
18
- def channel
19
- @channel ||= begin
20
- value = options[:channel]
21
- case value
22
- when String
23
- value.constantize
24
- when Symbol
25
- notification.send(value)
26
- when Class
27
- value
28
- else
29
- Noticed::NotificationChannel
30
- end
31
- end
32
- end
6
+ def deliver
7
+ channel = fetch_constant(:channel)
8
+ stream = evaluate_option(:stream)
9
+ message = evaluate_option(:message)
33
10
 
34
- def stream
35
- value = options[:stream]
36
- case value
37
- when String
38
- value
39
- when Symbol
40
- notification.send(value)
41
- else
42
- recipient
43
- end
11
+ channel.broadcast_to stream, message
44
12
  end
45
13
  end
46
14
  end
@@ -0,0 +1,11 @@
1
+ module Noticed
2
+ module DeliveryMethods
3
+ class Discord < BulkDeliveryMethod
4
+ required_options :json, :url
5
+
6
+ def deliver
7
+ post_request evaluate_option(:url), headers: evaluate_option(:headers), json: evaluate_option(:json)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,54 +1,22 @@
1
1
  module Noticed
2
2
  module DeliveryMethods
3
- class Email < Base
4
- option :mailer
3
+ class Email < DeliveryMethod
4
+ required_options :mailer, :method
5
5
 
6
6
  def deliver
7
- if options[:enqueue]
8
- mailer.with(format).send(mailer_method.to_sym).deliver_later
9
- else
10
- mailer.with(format).send(mailer_method.to_sym).deliver_now
11
- end
7
+ mailer = fetch_constant(:mailer)
8
+ email = evaluate_option(:method)
9
+ args = evaluate_option(:args) || []
10
+ mail = mailer.with(params).send(email, *args)
11
+ (!!evaluate_option(:enqueue)) ? mail.deliver_later : mail.deliver_now
12
12
  end
13
13
 
14
- private
15
-
16
- # mailer: "UserMailer"
17
- # mailer: UserMailer
18
- # mailer: :my_method - `my_method` should return Class
19
- def mailer
20
- option = options.fetch(:mailer)
21
- case option
22
- when String
23
- option.constantize
24
- when Symbol
25
- notification.send(option)
26
- else
27
- option
28
- end
29
- end
30
-
31
- # Method should be a symbol
32
- #
33
- # If notification responds to symbol, call that method and use return value
34
- # If notification does not respond to symbol, use the symbol for the mailer method
35
- # Otherwise, use the underscored notification class name as the mailer method
36
- def mailer_method
37
- method_name = options[:method]&.to_sym
38
- if method_name.present?
39
- notification.respond_to?(method_name) ? notification.send(method_name) : method_name
40
- else
41
- notification.class.name.underscore
42
- end
43
- end
44
-
45
- def format
46
- params = if (method = options[:format])
47
- notification.send(method)
48
- else
49
- notification.params
50
- end
51
- params.merge(recipient: recipient, record: record)
14
+ def params
15
+ (evaluate_option(:params) || notification&.params || {}).merge(
16
+ notification: notification,
17
+ record: notification&.record,
18
+ recipient: notification&.recipient
19
+ )
52
20
  end
53
21
  end
54
22
  end
@@ -1,95 +1,54 @@
1
1
  require "googleauth"
2
2
 
3
- # class CommentNotifier
4
- # deliver_by :fcm, credentials: Rails.root.join("config/certs/fcm.json"), format: :format_notification
5
- #
6
- # deliver_by :fcm, credentials: :fcm_credentials
7
- # def fcm_credentials
8
- # { project_id: "api-12345" }
9
- # end
10
- # end
11
-
12
3
  module Noticed
13
4
  module DeliveryMethods
14
- class Fcm < Base
15
- BASE_URI = "https://fcm.googleapis.com/v1/projects/"
16
-
17
- option :format
5
+ class Fcm < DeliveryMethod
6
+ required_option :credentials, :device_tokens, :json
18
7
 
19
8
  def deliver
20
- device_tokens.each do |device_token|
21
- post("#{BASE_URI}#{project_id}/messages:send", headers: {authorization: "Bearer #{access_token}"}, json: {message: format(device_token)})
22
- rescue ResponseUnsuccessful => exception
23
- if exception.response.code == 404
24
- cleanup_invalid_token(device_token)
25
- else
26
- raise
27
- end
9
+ evaluate_option(:device_tokens).each do |device_token|
10
+ send_notification device_token
28
11
  end
29
12
  end
30
13
 
31
- def cleanup_invalid_token(device_token)
32
- return unless notification.respond_to?(:cleanup_device_token)
33
- notification.send(:cleanup_device_token, token: device_token, platform: "fcm")
14
+ def send_notification(device_token)
15
+ post_request("https://fcm.googleapis.com/v1/projects/#{credentials[:project_id]}/messages:send",
16
+ headers: {authorization: "Bearer #{access_token}"},
17
+ json: notification.instance_exec(device_token, &config[:json]))
18
+ rescue Noticed::ResponseUnsuccessful => exception
19
+ if exception.response.code == "404" && config[:invalid_token]
20
+ notification.instance_exec(device_token, &config[:invalid_token])
21
+ else
22
+ raise
23
+ end
34
24
  end
35
25
 
36
26
  def credentials
37
27
  @credentials ||= begin
38
- option = options[:credentials]
39
- credentials_hash = case option
28
+ value = evaluate_option(:credentials)
29
+ case value
40
30
  when Hash
41
- option
31
+ value
42
32
  when Pathname
43
- load_json(option)
33
+ load_json(value)
44
34
  when String
45
- load_json(Rails.root.join(option))
46
- when Symbol
47
- notification.send(option)
35
+ load_json(Rails.root.join(value))
48
36
  else
49
- Rails.application.credentials.fcm
37
+ raise ArgumentError, "FCM credentials must be a Hash, String, Pathname, or Symbol"
50
38
  end
51
-
52
- credentials_hash.symbolize_keys
53
39
  end
54
40
  end
55
41
 
56
42
  def load_json(path)
57
- JSON.parse(File.read(path))
58
- end
59
-
60
- def project_id
61
- credentials[:project_id]
43
+ JSON.parse(File.read(path), symbolize_names: true)
62
44
  end
63
45
 
64
46
  def access_token
65
- token = authorizer.fetch_access_token!
66
- token["access_token"]
67
- end
68
-
69
- def authorizer
70
- @authorizer ||= options.fetch(:authorizer, Google::Auth::ServiceAccountCredentials).make_creds(
47
+ @authorizer ||= (evaluate_option(:authorizer) || Google::Auth::ServiceAccountCredentials).make_creds(
71
48
  json_key_io: StringIO.new(credentials.to_json),
72
49
  scope: "https://www.googleapis.com/auth/firebase.messaging"
73
50
  )
74
- end
75
-
76
- def format(device_token)
77
- notification.send(options[:format], device_token)
78
- end
79
-
80
- def device_tokens
81
- if notification.respond_to?(:fcm_device_tokens)
82
- Array.wrap(notification.fcm_device_tokens(recipient))
83
- else
84
- raise NoMethodError, <<~MESSAGE
85
- You must implement `fcm_device_tokens` to send Firebase Cloud Messaging notifications
86
-
87
- # This must return an Array of FCM device tokens
88
- def fcm_device_tokens(recipient)
89
- recipient.fcm_device_tokens.pluck(:token)
90
- end
91
- MESSAGE
92
- end
51
+ @authorizer.fetch_access_token!["access_token"]
93
52
  end
94
53
  end
95
54
  end
@@ -2,26 +2,24 @@ require "apnotic"
2
2
 
3
3
  module Noticed
4
4
  module DeliveryMethods
5
- class Ios < Base
6
- cattr_accessor :connection_pool
5
+ class Ios < DeliveryMethod
6
+ cattr_accessor :development_connection_pool, :production_connection_pool
7
+
8
+ required_options :bundle_identifier, :key_id, :team_id, :apns_key, :device_tokens
7
9
 
8
10
  def deliver
9
- raise ArgumentError, "bundle_identifier is missing" if bundle_identifier.blank?
10
- raise ArgumentError, "key_id is missing" if key_id.blank?
11
- raise ArgumentError, "team_id is missing" if team_id.blank?
12
- raise ArgumentError, "Could not find APN cert at '#{cert_path}'" unless valid_cert_path?
11
+ evaluate_option(:device_tokens).each do |device_token|
12
+ apn = Apnotic::Notification.new(device_token)
13
+ format_notification(apn)
13
14
 
14
- device_tokens.each do |device_token|
15
+ connection_pool = (!!evaluate_option(:development)) ? development_pool : production_pool
15
16
  connection_pool.with do |connection|
16
- apn = Apnotic::Notification.new(device_token)
17
- format_notification(apn)
18
-
19
17
  response = connection.push(apn)
20
18
  raise "Timeout sending iOS push notification" unless response
21
19
 
22
- if bad_token?(response)
20
+ if bad_token?(response) && config[:invalid_token]
23
21
  # Allow notification to cleanup invalid iOS device tokens
24
- cleanup_invalid_token(device_token)
22
+ notification.instance_exec(device_token, &config[:invalid_token])
25
23
  elsif !response.ok?
26
24
  raise "Request failed #{response.body}"
27
25
  end
@@ -32,53 +30,37 @@ module Noticed
32
30
  private
33
31
 
34
32
  def format_notification(apn)
35
- apn.topic = bundle_identifier
33
+ apn.topic = evaluate_option(:bundle_identifier)
36
34
 
37
- if (method = options[:format])
38
- notification.send(method, apn)
39
- elsif params[:message].present?
40
- apn.alert = params[:message]
35
+ if (method = config[:format])
36
+ notification.instance_exec(apn, &method)
37
+ elsif notification.params.try(:has_key?, :message)
38
+ apn.alert = notification.params[:message]
41
39
  else
42
40
  raise ArgumentError, "No message for iOS delivery. Either include message in params or add the 'format' option in 'deliver_by :ios'."
43
41
  end
44
42
  end
45
43
 
46
- def device_tokens
47
- if notification.respond_to?(:ios_device_tokens)
48
- Array.wrap(notification.ios_device_tokens(recipient))
49
- else
50
- raise NoMethodError, <<~MESSAGE
51
- You must implement `ios_device_tokens` to send iOS notifications
52
-
53
- # This must return an Array of iOS device tokens
54
- def ios_device_tokens(recipient)
55
- recipient.ios_device_tokens.pluck(:token)
56
- end
57
- MESSAGE
58
- end
59
- end
60
-
61
44
  def bad_token?(response)
62
45
  response.status == "410" || (response.status == "400" && response.body["reason"] == "BadDeviceToken")
63
46
  end
64
47
 
65
- def cleanup_invalid_token(token)
66
- return unless notification.respond_to?(:cleanup_device_token)
67
- notification.send(:cleanup_device_token, token: token, platform: "iOS")
48
+ def development_pool
49
+ self.class.development_connection_pool ||= new_connection_pool(development: true)
68
50
  end
69
51
 
70
- def connection_pool
71
- self.class.connection_pool ||= new_connection_pool
52
+ def production_pool
53
+ self.class.production_connection_pool ||= new_connection_pool(development: false)
72
54
  end
73
55
 
74
- def new_connection_pool
56
+ def new_connection_pool(development:)
75
57
  handler = proc do |connection|
76
58
  connection.on(:error) do |exception|
77
59
  Rails.logger.info "Apnotic exception raised: #{exception}"
78
60
  end
79
61
  end
80
62
 
81
- if development?
63
+ if development
82
64
  Apnotic::ConnectionPool.development(connection_pool_options, pool_options, &handler)
83
65
  else
84
66
  Apnotic::ConnectionPool.new(connection_pool_options, pool_options, &handler)
@@ -88,83 +70,14 @@ module Noticed
88
70
  def connection_pool_options
89
71
  {
90
72
  auth_method: :token,
91
- cert_path: cert_path,
92
- key_id: key_id,
93
- team_id: team_id
73
+ cert_path: StringIO.new(config.fetch(:apns_key)),
74
+ key_id: config.fetch(:key_id),
75
+ team_id: config.fetch(:team_id)
94
76
  }
95
77
  end
96
78
 
97
- def bundle_identifier
98
- option = options[:bundle_identifier]
99
- case option
100
- when String
101
- option
102
- when Symbol
103
- notification.send(option)
104
- else
105
- Rails.application.credentials.dig(:ios, :bundle_identifier)
106
- end
107
- end
108
-
109
- def cert_path
110
- option = options[:cert_path]
111
- case option
112
- when String
113
- option
114
- when Symbol
115
- notification.send(option)
116
- else
117
- Rails.root.join("config/certs/ios/apns.p8")
118
- end
119
- end
120
-
121
- def key_id
122
- option = options[:key_id]
123
- case option
124
- when String
125
- option
126
- when Symbol
127
- notification.send(option)
128
- else
129
- Rails.application.credentials.dig(:ios, :key_id)
130
- end
131
- end
132
-
133
- def team_id
134
- option = options[:team_id]
135
- case option
136
- when String
137
- option
138
- when Symbol
139
- notification.send(option)
140
- else
141
- Rails.application.credentials.dig(:ios, :team_id)
142
- end
143
- end
144
-
145
- def development?
146
- option = options[:development]
147
- case option
148
- when Symbol
149
- !!notification.send(option)
150
- else
151
- !!option
152
- end
153
- end
154
-
155
- def valid_cert_path?
156
- case cert_path
157
- when File, StringIO
158
- cert_path.size > 0
159
- else
160
- File.exist?(cert_path)
161
- end
162
- end
163
-
164
79
  def pool_options
165
- {
166
- size: options.fetch(:pool_size, 5)
167
- }
80
+ {size: evaluate_option(:pool_size) || 5}
168
81
  end
169
82
  end
170
83
  end
@@ -1,31 +1,14 @@
1
1
  module Noticed
2
2
  module DeliveryMethods
3
- class MicrosoftTeams < Base
4
- def deliver
5
- post(url, json: format)
6
- end
3
+ class MicrosoftTeams < DeliveryMethod
4
+ required_options :json
7
5
 
8
- private
9
-
10
- def format
11
- if (method = options[:format])
12
- notification.send(method)
13
- else
14
- {
15
- title: notification.params[:title],
16
- text: notification.params[:text],
17
- sections: notification.params[:sections],
18
- potentialAction: notification.params[:notification_action]
19
- }
20
- end
6
+ def deliver
7
+ post_request url, headers: evaluate_option(:headers), json: evaluate_option(:json)
21
8
  end
22
9
 
23
10
  def url
24
- if (method = options[:url])
25
- notification.send(method)
26
- else
27
- Rails.application.credentials.microsoft_teams[:notification_url]
28
- end
11
+ evaluate_option(:url) || Rails.application.credentials.dig(:microsoft_teams, :notification_url)
29
12
  end
30
13
  end
31
14
  end
@@ -1,26 +1,16 @@
1
1
  module Noticed
2
2
  module DeliveryMethods
3
- class Slack < Base
4
- def deliver
5
- post(url, json: format)
6
- end
3
+ class Slack < DeliveryMethod
4
+ DEFAULT_URL = "https://slack.com/api/chat.postMessage"
7
5
 
8
- private
6
+ required_options :json
9
7
 
10
- def format
11
- if (method = options[:format])
12
- notification.send(method)
13
- else
14
- notification.params
15
- end
8
+ def deliver
9
+ post_request url, headers: evaluate_option(:headers), json: evaluate_option(:json)
16
10
  end
17
11
 
18
12
  def url
19
- if (method = options[:url])
20
- notification.send(method)
21
- else
22
- Rails.application.credentials.slack[:notification_url]
23
- end
13
+ evaluate_option(:url) || DEFAULT_URL
24
14
  end
25
15
  end
26
16
  end
@@ -1,20 +1,10 @@
1
1
  module Noticed
2
2
  module DeliveryMethods
3
- class Test < Base
3
+ class Test < DeliveryMethod
4
4
  class_attribute :delivered, default: []
5
- class_attribute :callbacks, default: []
6
-
7
- after_deliver do
8
- self.class.callbacks << :after
9
- end
10
-
11
- def self.clear!
12
- delivered.clear
13
- callbacks.clear
14
- end
15
5
 
16
6
  def deliver
17
- self.class.delivered << notification
7
+ delivered << notification
18
8
  end
19
9
  end
20
10
  end
@@ -0,0 +1,37 @@
1
+ module Noticed
2
+ module DeliveryMethods
3
+ class TwilioMessaging < DeliveryMethod
4
+ def deliver
5
+ post_request url, basic_auth: {user: account_sid, pass: auth_token}, form: json
6
+ end
7
+
8
+ def json
9
+ evaluate_option(:json) || {
10
+ From: phone_number,
11
+ To: recipient.phone_number,
12
+ Body: params.fetch(:message)
13
+ }
14
+ end
15
+
16
+ def url
17
+ evaluate_option(:url) || "https://api.twilio.com/2010-04-01/Accounts/#{account_sid}/Messages.json"
18
+ end
19
+
20
+ def account_sid
21
+ evaluate_option(:account_sid) || credentials.fetch(:account_sid)
22
+ end
23
+
24
+ def auth_token
25
+ evaluate_option(:auth_token) || credentials.fetch(:auth_token)
26
+ end
27
+
28
+ def phone_number
29
+ evaluate_option(:phone_number) || credentials.fetch(:phone_number)
30
+ end
31
+
32
+ def credentials
33
+ evaluate_option(:credentials) || Rails.application.credentials.twilio
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ module Noticed
2
+ module DeliveryMethods
3
+ class VonageSms < DeliveryMethod
4
+ DEFAULT_URL = "https://rest.nexmo.com/sms/json"
5
+
6
+ required_options :json
7
+
8
+ def deliver
9
+ headers = evaluate_option(:headers)
10
+ json = evaluate_option(:json)
11
+ response = post_request url, headers: headers, json: json
12
+ raise ResponseUnsuccessful.new(response, url, headers: headers, json: json) if JSON.parse(response.body).dig("messages", 0, "status") != "0"
13
+ end
14
+
15
+ def url
16
+ evaluate_option(:url) || DEFAULT_URL
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ module Noticed
2
+ module DeliveryMethods
3
+ class Webhook < DeliveryMethod
4
+ required_options :url
5
+
6
+ def deliver
7
+ post_request(
8
+ evaluate_option(:url),
9
+ basic_auth: evaluate_option(:basic_auth),
10
+ headers: evaluate_option(:headers),
11
+ json: evaluate_option(:json),
12
+ form: evaluate_option(:form)
13
+ )
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,13 +1,5 @@
1
1
  module Noticed
2
2
  class Engine < ::Rails::Engine
3
- initializer "noticed.has_notifications" do
4
- ActiveSupport.on_load(:active_record) do
5
- include Noticed::HasNotifications
6
- end
7
- end
8
-
9
- initializer "noticed.rails_5_2_support" do
10
- require "rails_6_polyfills/base" if Rails::VERSION::MAJOR < 6
11
- end
3
+ isolate_namespace Noticed
12
4
  end
13
5
  end
@@ -0,0 +1,21 @@
1
+ module Noticed
2
+ module RequiredOptions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :required_option_names, instance_writer: false, default: []
7
+ end
8
+
9
+ class_methods do
10
+ def inherited(base)
11
+ base.required_option_names = required_option_names.dup
12
+ super
13
+ end
14
+
15
+ def required_options(*names)
16
+ required_option_names.concat names
17
+ end
18
+ alias_method :required_option, :required_options
19
+ end
20
+ end
21
+ end
@@ -4,7 +4,7 @@ module Noticed
4
4
 
5
5
  # Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup.
6
6
  def i18n_scope
7
- :notifications
7
+ :notifiers
8
8
  end
9
9
 
10
10
  def class_scope
@@ -12,13 +12,17 @@ module Noticed
12
12
  end
13
13
 
14
14
  def translate(key, **options)
15
- I18n.translate(scope_translation_key(key), **options)
15
+ if defined?(::ActiveSupport::HtmlSafeTranslation)
16
+ ActiveSupport::HtmlSafeTranslation.translate scope_translation_key(key), **options
17
+ else
18
+ I18n.translate scope_translation_key(key), **options
19
+ end
16
20
  end
17
21
  alias_method :t, :translate
18
22
 
19
23
  def scope_translation_key(key)
20
24
  if key.to_s.start_with?(".")
21
- "#{i18n_scope}.#{class_scope}#{key}"
25
+ [i18n_scope, class_scope].compact.join(".") + key
22
26
  else
23
27
  key
24
28
  end