locomotive_cms 2.0.0.rc11 → 2.0.0.rc12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/Gemfile +1 -0
  2. data/app/assets/javascripts/locomotive/utils/tinymce_settings.js.coffee +3 -3
  3. data/app/assets/javascripts/locomotive/views/content_entries/_popup_form_view.js.coffee +1 -1
  4. data/app/assets/javascripts/locomotive/views/editable_elements/long_text_view.js.coffee +2 -2
  5. data/app/controllers/locomotive/api/base_controller.rb +4 -4
  6. data/app/controllers/locomotive/api/memberships_controller.rb +2 -2
  7. data/app/controllers/locomotive/api/my_account_controller.rb +18 -0
  8. data/app/controllers/locomotive/api/sites_controller.rb +1 -1
  9. data/app/controllers/locomotive/current_site_controller.rb +2 -0
  10. data/app/controllers/locomotive/passwords_controller.rb +6 -0
  11. data/app/mailers/locomotive/notifications.rb +2 -2
  12. data/app/models/locomotive/content_entry.rb +13 -1
  13. data/app/models/locomotive/editable_element.rb +4 -0
  14. data/app/models/locomotive/extensions/page/editable_elements.rb +7 -4
  15. data/app/models/locomotive/extensions/page/templatized.rb +14 -0
  16. data/app/models/locomotive/extensions/page/tree.rb +9 -0
  17. data/app/models/locomotive/extensions/site/locales.rb +7 -1
  18. data/app/models/locomotive/page.rb +8 -4
  19. data/app/presenters/locomotive/content_entry_presenter.rb +4 -4
  20. data/app/presenters/locomotive/page_presenter.rb +2 -2
  21. data/app/views/locomotive/current_site/edit.html.haml +3 -0
  22. data/app/views/locomotive/devise_mailer/reset_password_instructions.html.haml +2 -2
  23. data/app/views/locomotive/notifications/new_content_entry.html.haml +2 -4
  24. data/app/views/locomotive/pages/_form.html.haml +4 -0
  25. data/app/views/locomotive/passwords/edit.html.haml +1 -1
  26. data/config/locales/admin_ui.de.yml +1 -1
  27. data/config/locales/admin_ui.en.yml +2 -2
  28. data/config/locales/admin_ui.es.yml +1 -1
  29. data/config/locales/admin_ui.et.yml +1 -1
  30. data/config/locales/admin_ui.fr.yml +3 -3
  31. data/config/locales/admin_ui.it.yml +1 -1
  32. data/config/locales/admin_ui.nb.yml +1 -1
  33. data/config/locales/admin_ui.nl.yml +1 -1
  34. data/config/locales/admin_ui.pt-BR.yml +1 -1
  35. data/config/locales/admin_ui.ru.yml +1 -1
  36. data/config/locales/default.de.yml +2 -1
  37. data/config/locales/default.fr.yml +6 -3
  38. data/config/locales/devise.de.yml +11 -11
  39. data/config/locales/devise.en.yml +1 -1
  40. data/config/locales/devise.es.yml +9 -9
  41. data/config/locales/devise.et.yml +1 -1
  42. data/config/locales/devise.fr.yml +12 -12
  43. data/config/locales/devise.it.yml +11 -11
  44. data/config/locales/devise.nb.yml +3 -3
  45. data/config/locales/devise.nl.yml +10 -10
  46. data/config/locales/devise.pt-BR.yml +11 -11
  47. data/config/locales/devise.ru.yml +1 -1
  48. data/config/locales/flash.fr.yml +1 -1
  49. data/config/routes.rb +4 -2
  50. data/lib/generators/locomotive/install/templates/carrierwave.rb +1 -1
  51. data/lib/locomotive/liquid/tags/editable/base.rb +17 -9
  52. data/lib/locomotive/middlewares/inline_editor.rb +2 -2
  53. data/lib/locomotive/routing/site_dispatcher.rb +1 -1
  54. data/lib/locomotive/version.rb +1 -1
  55. data/vendor/assets/javascripts/locomotive/datepicker_i18n.js.erb +28 -0
  56. metadata +9 -11
  57. data/app/views/locomotive/mailer/confirmation_instructions.html.haml +0 -8
  58. data/app/views/locomotive/mailer/reset_password_instructions.html.haml +0 -12
  59. data/app/views/locomotive/mailer/unlock_instructions.html.haml +0 -10
  60. data/vendor/assets/javascripts/locomotive/datepicker_fr.js +0 -16
data/Gemfile CHANGED
@@ -12,6 +12,7 @@ group :development do
12
12
  # gem 'custom_fields', :git => 'git://github.com/locomotivecms/custom_fields.git', :branch => '2.0.0.rc' # Branch on Github
13
13
 
14
14
  # gem 'locomotive-aloha-rails', :path => '../gems/aloha-rails' # for Developers
15
+ # gem 'locomotive-tinymce-rails', '~> 3.4.7.3', :path => '../gems/tinymce-rails' # for Developers
15
16
  # gem 'locomotive_liquid', :path => '../gems/liquid' # for Developers
16
17
 
17
18
  gem 'rspec-rails', '~> 2.8.0' # In order to have rspec tasks and generators
@@ -3,9 +3,9 @@ window.Locomotive.tinyMCE =
3
3
  defaultSettings:
4
4
  theme: 'advanced'
5
5
  skin: 'locomotive'
6
- plugins: 'safari,jqueryinlinepopups,locomotive_media,fullscreen'
6
+ plugins: 'safari,jqueryinlinepopups,table,locomotive_media,fullscreen'
7
7
  extended_valid_elements: 'iframe[width|height|frameborder|allowfullscreen|src|title]'
8
- theme_advanced_buttons1: 'fullscreen,code,|,bold,italic,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,bullist,numlist,|,outdent,indent,blockquote,|,link,unlink,|,locomotive_media'
8
+ theme_advanced_buttons1: 'fullscreen,code,|,bold,italic,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,bullist,numlist,|,outdent,indent,blockquote,|,link,unlink,|,table,|,locomotive_media'
9
9
  theme_advanced_buttons2: 'formatselect,fontselect,fontsizeselect'
10
10
  theme_advanced_buttons3: ''
11
11
  theme_advanced_toolbar_location: 'top'
@@ -33,7 +33,7 @@ window.Locomotive.tinyMCE =
33
33
  popupSettings:
34
34
  theme: 'advanced'
35
35
  skin: 'locomotive'
36
- plugins: 'safari,jqueryinlinepopups,locomotive_media,fullscreen'
36
+ plugins: 'safari,jqueryinlinepopups,locomotive_media,fullscreen'
37
37
  extended_valid_elements: 'iframe[width|height|frameborder|allowfullscreen|src|title]'
38
38
  theme_advanced_buttons1: 'fullscreen,code,|,bold,italic,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,bullist,numlist,|,outdent,indent,blockquote,|,link,unlink,|,locomotive_media'
39
39
  theme_advanced_buttons2: 'formatselect,fontselect,fontsizeselect'
@@ -87,4 +87,4 @@ class Locomotive.Views.ContentEntries.PopupFormView extends Locomotive.Views.Con
87
87
  # disabled in a popup form
88
88
 
89
89
  tinyMCE_settings: ->
90
- window.Locomotive.tinyMCE.popupSettings
90
+ _.extend { language: window.locale }, window.Locomotive.tinyMCE.popupSettings
@@ -14,7 +14,7 @@ class Locomotive.Views.EditableElements.LongTextView extends Backbone.View
14
14
  return @
15
15
 
16
16
  after_render: ->
17
- settings = _.extend {}, @tinymce_settings(),
17
+ settings = _.extend {}, @tinymce_settings(),
18
18
  oninit: ((editor) =>
19
19
  $.cmd 'S', (() =>
20
20
  @model.set(content: editor.getBody().innerHTML)
@@ -26,7 +26,7 @@ class Locomotive.Views.EditableElements.LongTextView extends Backbone.View
26
26
  @$('textarea').tinymce(settings)
27
27
 
28
28
  tinymce_settings: ->
29
- window.Locomotive.tinyMCE.defaultSettings
29
+ _.extend { language: window.locale }, window.Locomotive.tinyMCE.defaultSettings
30
30
 
31
31
  refresh: ->
32
32
  # do nothing
@@ -21,10 +21,10 @@ module Locomotive
21
21
 
22
22
  protected
23
23
 
24
- def set_current_thread_variables
25
- Thread.current[:account] = current_locomotive_account
26
- Thread.current[:site] = current_site
27
- end
24
+ def set_current_thread_variables
25
+ Thread.current[:account] = current_locomotive_account
26
+ Thread.current[:site] = current_site
27
+ end
28
28
 
29
29
  def current_ability
30
30
  @current_ability ||= Ability.new(current_locomotive_account, current_site)
@@ -3,8 +3,8 @@ module Locomotive
3
3
  class MembershipsController < BaseController
4
4
 
5
5
  # It's an embedded document, so we'll just load manually
6
- before_filter :load_membership, :only => [ :show, :update, :destroy ]
7
- before_filter :load_memberships, :only => [ :index ]
6
+ before_filter :load_membership, :only => [:show, :update, :destroy]
7
+ before_filter :load_memberships, :only => [:index]
8
8
 
9
9
  authorize_resource :class => Locomotive::Membership
10
10
 
@@ -0,0 +1,18 @@
1
+ module Locomotive
2
+ module Api
3
+ class MyAccountController < BaseController
4
+
5
+ skip_load_and_authorize_resource
6
+
7
+ # FIXME: the auto-loaded site won't pass authorization for show, update, or destroy
8
+ # skip_load_and_authorize_resource :only => [ :show, :update, :destroy ]
9
+
10
+ def show
11
+ respond_with(current_locomotive_account)
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
18
+
@@ -5,7 +5,7 @@ module Locomotive
5
5
  load_and_authorize_resource :class => Locomotive::Site
6
6
 
7
7
  # FIXME: the auto-loaded site won't pass authorization for show, update, or destroy
8
- skip_load_and_authorize_resource :only => [ :show, :update, :destroy ]
8
+ skip_load_and_authorize_resource :only => [:show, :update, :destroy]
9
9
 
10
10
  def index
11
11
  @sites = Locomotive::Site.all
@@ -3,6 +3,8 @@ module Locomotive
3
3
 
4
4
  sections 'settings', 'site'
5
5
 
6
+ localized
7
+
6
8
  skip_load_and_authorize_resource
7
9
 
8
10
  load_and_authorize_resource :class => 'Site'
@@ -9,5 +9,11 @@ module Locomotive
9
9
 
10
10
  helper 'locomotive/base'
11
11
 
12
+ protected
13
+
14
+ def after_sending_reset_password_instructions_path_for(resource_name)
15
+ new_locomotive_account_session_path
16
+ end
17
+
12
18
  end
13
19
  end
@@ -4,9 +4,9 @@ module Locomotive
4
4
  default :from => Locomotive.config.mailer_sender
5
5
 
6
6
  def new_content_entry(account, entry)
7
- @account, @entry, @type = account, entry, entry.content_type
7
+ @account, @entry, @type, @domain = account, entry, entry.content_type, entry.site.domains.first
8
8
 
9
- subject = t('locomotive.notifications.new_content_entry.subject', :type => @type.name, :locale => account.locale)
9
+ subject = t('locomotive.notifications.new_content_entry.subject', :domain => @domain, :type => @type.name, :locale => account.locale)
10
10
 
11
11
  mail :subject => subject, :to => account.email
12
12
  end
@@ -57,7 +57,7 @@ module Locomotive
57
57
 
58
58
  alias :to_label :_label
59
59
 
60
- # Tells if the content entry has been translated or not.
60
+ # Tell if the content entry has been translated or not.
61
61
  # It just checks if the field used for the label has been translated.
62
62
  #
63
63
  # @return [ Boolean ] True if translated, false otherwise
@@ -70,6 +70,18 @@ module Locomotive
70
70
  end
71
71
  end
72
72
 
73
+ # Return the locales the content entry has been translated to.
74
+ #
75
+ # @return [ Array ] The list of locales. Nil if not localized
76
+ #
77
+ def translated_in
78
+ if self.respond_to?(:"#{self._label_field_name}_translations")
79
+ self.send(:"#{self._label_field_name}_translations").keys
80
+ else
81
+ nil
82
+ end
83
+ end
84
+
73
85
  # Return the next content entry based on the order defined in the parent content type.
74
86
  #
75
87
  # @param [ Object ] The next content entry or nil if not found
@@ -31,6 +31,10 @@ module Locomotive
31
31
  !!self.disabled # the original method does not work quite well with the localization
32
32
  end
33
33
 
34
+ def disabled_in_all_translations?
35
+ self.disabled_translations.all? { |_, v| v == true }
36
+ end
37
+
34
38
  # Determines if the current element can be edited in the back-office
35
39
  #
36
40
  def editable?
@@ -72,8 +72,8 @@ module Locomotive
72
72
  else
73
73
  existing_el.disabled = false
74
74
 
75
- # only the type and hint properties can be modified from the parent element
76
- %w(_type hint).each do |attr|
75
+ # only the type, hint and fixed properties can be modified from the parent element
76
+ %w(_type hint fixed).each do |attr|
77
77
  existing_el.send(:"#{attr}=", el.send(attr.to_sym))
78
78
  end
79
79
  end
@@ -81,10 +81,13 @@ module Locomotive
81
81
  end
82
82
 
83
83
  def remove_disabled_editable_elements
84
- return unless self.editable_elements.any? { |el| el.disabled? }
84
+ # get only those which are fully disabled, meaning in ALL the locales
85
+ ids = self.editable_elements.find_all { |el| el.disabled_in_all_translations? }.map(&:_id)
86
+
87
+ return if ids.empty?
85
88
 
86
89
  # super fast way to remove useless elements all in once
87
- self.collection.update(self.atomic_selector, '$pull' => { 'editable_elements' => { "disabled.#{::Mongoid::Fields::I18n.locale}" => true } })
90
+ self.collection.update(self.atomic_selector, '$pull' => { 'editable_elements' => { '_id' => { '$in' => ids } } })
88
91
  end
89
92
 
90
93
  end
@@ -42,6 +42,20 @@ module Locomotive
42
42
  target_klass_name.constantize
43
43
  end
44
44
 
45
+ # Returns the slug related to the target_klass.
46
+ # In other words, it returns the slug of the target content type.
47
+ #
48
+ # @return [ String ] The slug of the target class / content type. Nil if no target klass.
49
+ #
50
+ def target_klass_slug
51
+ if self.target_klass_name =~ /^Locomotive::Entry([a-z0-9]+)$/
52
+ @content_type ||= self.site.content_types.find($1)
53
+ @content_type.slug
54
+ else
55
+ nil
56
+ end
57
+ end
58
+
45
59
  # Gives the name which can be used in a liquid template in order
46
60
  # to reference an entry. It uses the slug property if the target klass
47
61
  # is a Locomotive content type or the class name itself for the other classes.
@@ -22,6 +22,7 @@ module Locomotive
22
22
 
23
23
  alias_method_chain :rearrange, :identity_map
24
24
  alias_method_chain :rearrange_children, :identity_map
25
+ alias_method_chain :siblings_and_self, :scoping
25
26
  end
26
27
 
27
28
  module ClassMethods
@@ -98,6 +99,14 @@ module Locomotive
98
99
  end
99
100
  end
100
101
 
102
+ ##
103
+ # Returns this document's siblings and itself, all scoped by the site
104
+ #
105
+ # @return [Mongoid::Criteria] Mongoid criteria to retrieve the document's siblings and itself
106
+ def siblings_and_self_with_scoping
107
+ base_class.where(:parent_id => self.parent_id, :site_id => self.site_id)
108
+ end
109
+
101
110
  def depth
102
111
  self.parent_ids.count
103
112
  end
@@ -41,7 +41,13 @@ module Locomotive
41
41
  locale = (locale || I18n.locale).to_s
42
42
  fullpath = page.fullpath_translations[locale] || page.fullpath_translations[self.default_locale]
43
43
 
44
- locale == self.default_locale ? fullpath : File.join(locale, fullpath)
44
+ if locale == self.default_locale.to_s # no need to specify the locale
45
+ page.index? ? '' : fullpath
46
+ elsif page.index? # avoid /en/index or /fr/index, prefer /en or /fr instead
47
+ locale
48
+ else
49
+ File.join(locale, fullpath)
50
+ end
45
51
  end
46
52
 
47
53
  def locales=(array)
@@ -40,10 +40,10 @@ module Locomotive
40
40
  before_destroy :do_not_remove_index_and_404_pages
41
41
 
42
42
  ## validations ##
43
- validates_presence_of :site, :title, :slug
44
- validates_uniqueness_of :slug, :scope => [:site_id, :parent_id]
45
- validates_uniqueness_of :handle, :allow_blank => true
46
- validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth <= 1 }
43
+ validates_presence_of :site, :title, :slug
44
+ validates_uniqueness_of :slug, :scope => [:site_id, :parent_id]
45
+ validates_uniqueness_of :handle, :scope => :site_id, :allow_blank => true
46
+ validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth <= 1 }
47
47
 
48
48
  ## named scopes ##
49
49
  scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.ui[:latest_entries_nb]
@@ -81,6 +81,10 @@ module Locomotive
81
81
  self.title_translations.key?(::Mongoid::Fields::I18n.locale.to_s) rescue false
82
82
  end
83
83
 
84
+ def translated_in
85
+ self.title_translations.keys
86
+ end
87
+
84
88
  def to_liquid
85
89
  Locomotive::Liquid::Drops::Page.new(self)
86
90
  end
@@ -1,9 +1,9 @@
1
1
  module Locomotive
2
2
  class ContentEntryPresenter < BasePresenter
3
3
 
4
- delegate :_label, :_slug, :_position, :seo_title,
5
- :meta_keywords, :meta_description, :select_custom_fields,
6
- :file_custom_fields, :has_many_custom_fields, :many_to_many_custom_fields, :to => :source
4
+ delegate :_label, :_slug, :_position, :translated_in, :seo_title,
5
+ :meta_keywords, :meta_description, :select_custom_fields,
6
+ :file_custom_fields, :has_many_custom_fields, :many_to_many_custom_fields, :to => :source
7
7
 
8
8
  # Lists of all the attributes editable thru the html form for instance
9
9
  #
@@ -33,7 +33,7 @@ module Locomotive
33
33
  end
34
34
 
35
35
  def included_methods
36
- default_list = %w(_label _slug _position content_type_slug select_custom_fields file_custom_fields has_many_custom_fields many_to_many_custom_fields safe_attributes)
36
+ default_list = %w(_label _slug _position content_type_slug select_custom_fields file_custom_fields has_many_custom_fields many_to_many_custom_fields translated_in safe_attributes)
37
37
  default_list << 'errors' if !!self.options[:include_errors]
38
38
  super + self.filtered_custom_fields_methods + default_list
39
39
  end
@@ -1,7 +1,7 @@
1
1
  module Locomotive
2
2
  class PagePresenter < BasePresenter
3
3
 
4
- delegate :title, :slug, :fullpath, :handle, :raw_template, :published, :listed, :templatized, :templatized_from_parent, :redirect, :redirect_url, :template_changed, :cache_strategy, :response_type, :to => :source
4
+ delegate :title, :slug, :fullpath, :handle, :position, :raw_template, :published, :listed, :templatized, :templatized_from_parent, :target_klass_slug, :redirect, :redirect_url, :template_changed, :cache_strategy, :response_type, :translated_in, :to => :source
5
5
 
6
6
  def escaped_raw_template
7
7
  h(self.source.raw_template)
@@ -12,7 +12,7 @@ module Locomotive
12
12
  end
13
13
 
14
14
  def included_methods
15
- super + %w(title slug fullpath handle raw_template published listed templatized templatized_from_parent redirect redirect_url cache_strategy response_type template_changed editable_elements localized_fullpaths)
15
+ super + %w(title slug fullpath handle position raw_template published listed templatized templatized_from_parent target_klass_slug redirect redirect_url cache_strategy response_type template_changed editable_elements localized_fullpaths translated_in)
16
16
  end
17
17
 
18
18
  def localized_fullpaths
@@ -3,6 +3,9 @@
3
3
  - content_for :submenu do
4
4
  = render_cell 'locomotive/settings_menu', :show
5
5
 
6
+ - content_for :actions do
7
+ = locale_picker_link
8
+
6
9
  - content_for :buttons do
7
10
  - if can?(:create, Locomotive::Account)
8
11
  = local_action_button t('.new_membership'), new_membership_url, :class => 'new'
@@ -1,11 +1,11 @@
1
1
  %p
2
- != t('locomotive.mailer.common.hello')
2
+ != t('locomotive.devise_mailer.common.hello')
3
3
  = @resource.email
4
4
  \!
5
5
  %p
6
6
  != t('.reset_password_instruction')
7
7
  %p
8
- = link_to t('.change_my_password'), edit_password_url(@resource, :reset_password_token => @resource.reset_password_token)
8
+ = link_to t('.change_my_password'), edit_locomotive_account_password_url(:reset_password_token => @resource.reset_password_token)
9
9
  %p
10
10
  != t('.wrong_request_instruction')
11
11
  %p
@@ -1,6 +1,4 @@
1
- %p= t('.title', :name => @account.name, :date => I18n.l(Time.now), :locale => @account.locale)
2
-
3
- %hr
1
+ %p= t('.title', :name => @account.name, :domain => @domain, :date => I18n.l(Time.now), :locale => @account.locale).html_safe
4
2
 
5
3
  %p
6
4
  %b= t('.type', :type => @type.name, :locale => @account.locale)
@@ -21,7 +19,7 @@
21
19
  = value
22
20
  - when 'file'
23
21
  - url = value.guess_url
24
- - url = url =~ /^http/ ? url : URI.join("http://#{@entry.site.domains.first}", url).to_s # Amazon S3 (http/https) or local files ?
22
+ - url = url =~ /^http/ ? url : URI.join("http://#{@domain}", url).to_s # Amazon S3 (http/https) or local files ?
25
23
  = link_to File.basename(url), url
26
24
  - when 'select'
27
25
  = value
@@ -18,6 +18,10 @@
18
18
 
19
19
  = f.input :slug, :required => false, :hint => @page.slug.blank? ? t('.empty_slug') : public_page_url(@page), :input_html => { :'data-url' => get_path_pages_url, :disabled => @page.index? || @page.not_found? }, :wrapper_html => { :style => "#{'display: none' if @page.templatized? && !@page.templatized_from_parent?};", :class => 'em-inline-hints' }
20
20
 
21
+ - else
22
+ = f.inputs :name => :information, :style => 'display: none' do
23
+ = f.text_field :title
24
+
21
25
  = f.inputs :name => :seo, :class => "inputs foldable #{'folded' if inputs_folded?(@page)}" do
22
26
 
23
27
  = f.input :seo_title
@@ -1,6 +1,6 @@
1
1
  - title t('.title')
2
2
 
3
- = semantic_form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f|
3
+ = semantic_form_for(resource, :as => resource_name, :url => locomotive_account_password_url, :html => { :method => :put }) do |f|
4
4
 
5
5
  = f.hidden_field :reset_password_token
6
6
 
@@ -68,7 +68,7 @@ de:
68
68
 
69
69
  notifications:
70
70
  new_content_entry:
71
- subject: "[%{type}] neu"
71
+ subject: "[%{domain}][%{type}] neu"
72
72
  title: "Hi %{name}, nur damit Sie Bescheid wissen, am %{date} wurde eine neue Instanz erstellt."
73
73
  type: "Model: %{type}"
74
74
 
@@ -68,8 +68,8 @@ en:
68
68
 
69
69
  notifications:
70
70
  new_content_entry:
71
- subject: "[%{type}] new"
72
- title: "Hi %{name}, just to let you know that a new instance has been created on %{date}"
71
+ subject: "[%{domain}][%{type}] new entry"
72
+ title: "Hi %{name}, just to let you know that a new instance has been created on %{date} for the site <b>%{domain}</b>"
73
73
  type: "Model: %{type}"
74
74
 
75
75
  sites_picker:
@@ -49,7 +49,7 @@ es:
49
49
 
50
50
  notifications:
51
51
  new_content_entry:
52
- subject: "[%{type}] nuevo"
52
+ subject: "[%{domain}][%{type}] nuevo"
53
53
  title: "Hola %{name}, queremos hacerte saber que una nueva instancia se creó el día %{date}"
54
54
  type: "Modelo: %{type}"
55
55