noticed 1.6.3 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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