hanami 3.0.0.rc1 → 3.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0629b02399ba598bc3d4cbcd3c421b6312e06bee5f6e1f41a5608b2cac18ec0
4
- data.tar.gz: fde643ae7b04a53c3047ff88f27856177bc16905e1bbe65ee577142ae1cdb35f
3
+ metadata.gz: 53f2d51070211158a50d0adfd0e4326c2647cd8dfb81135d74b5c09449213b24
4
+ data.tar.gz: f8c19c0c73096809f15176ed90c358be89dfda92adb8c418e1863c902b7835dc
5
5
  SHA512:
6
- metadata.gz: 1a27833e74d730e9ba84222cf739a6e503d5ae733846ba0abf0ecc21d527fbb3e0f416902f37d82064d773b18456fe803f85fd7085a5367ba9a6744efaac49be
7
- data.tar.gz: ac8e58a76c42b02a967c2d2c86769b019dc5f4321408d877250c446ddb851c4199a781e7594e4c0bca54f3211bcdd86093af6a5393127e380cc59227aa7af883
6
+ metadata.gz: a0b02ef4b8832c4de4d7ba33077ccde81a5412a1483e942fcf3ac8d179c6b5a4dc1ad23c2c597ef50cfb77f84034c6045679051c7c70e7bcc0c9ccd43f7414f0
7
+ data.tar.gz: 82fe7c7f69fc77c9a4cce6017a3b3e3dea90e3eb55aabc58205532c77711caab93cf2bc1824177a1d1840d5d723da12c7337a7a158390043fad9d7e5f88c7ea3
data/CHANGELOG.md CHANGED
@@ -37,7 +37,55 @@ A complete Hanami app is composed of multiple gems. For a complete overview of c
37
37
 
38
38
  ### Security
39
39
 
40
- [unreleased]: https://github.com/hanami/hanami/compare/v3.0.0.rc1...HEAD
40
+ [unreleased]: https://github.com/hanami/hanami/compare/v3.0.0...HEAD
41
+
42
+ ## [3.0.0] - 2026-06-30
43
+
44
+ ### Added
45
+
46
+ - Integrate hanami-mailer gem when bundled. (@timriley in #1597, #1600, #1606, #1609)
47
+
48
+ Load templates from `templates/mailers/`. Register a `"mailers.delivery_method"`, which is either an SMTP mailer when `SMTP_ADDRESS`, `SMTP_PORT`, `SMTP_USERNAME`, `SMTP_PASSWORD`, `SMTP_AUTHENTICATION` env vars are present. These can also be prefixed with a slice name. Register a test delivery always in the test env, and also in other envs when the env vars are absent.
49
+ - Integrate i18n gem when bundled. (@timriley in #1562, #1589, #1590, #1591, #1592, #1593)
50
+
51
+ Register an `"i18n"` component in each slice, which may be configured via `config.i18n` or a dedicated `:i18n` provider. Load translations from `config/i18n/` within each slice. Load shared translations from `config/i18n/shared/` at the app-level only. Bundle default English translations for `#localize`. Make helpers available in views and actions, which also prefix relative keys (with a leading ".") with their own dot-delimited template or action names.
52
+ - Wrap the configured app logger with `Hanami::UniversalLogger`, to provide a consistent logging interface regardless of the underlying logger. The app can now be depended upon to support (1) structured logging via keyword args passed to log methods, and (2) tagged logging via `#tagged`. (@timriley in #1567, #1568, #1608)
53
+ - Allow the built-in logger to be further configured in a `config/providers/logger.rb` provider file, useful for calling arbitrary methods on the logger, like adding backends. (@timriley in #1608)
54
+
55
+ ```ruby
56
+ Hanami.app.configure_provider :logger do
57
+ before :start do
58
+ logger.add_backend(
59
+ stream: Hanami.app.root.join("log", "payments.log"),
60
+ log_if: -> entry { entry.tag?(:payments) }
61
+ )
62
+ end
63
+ end
64
+ ```
65
+ - Support `HANAMI_LOG_LEVEL` env var to set the log level (e.g. `HANAMI_LOG_LEVEL=warn bundle exec hanami server`). Takes precedence over `config.logger.level` set in `config/app.rb`. (@cllns in #1580)
66
+ - Add `config.db.log_level` setting, for changing the log level for SQL logs. (@katafrakt in #1587)
67
+ - New setting `:default_template_engine` that sets which template engine should be used by default when doing `hanami generate`. (@katafrakt in #1564)
68
+ - Add `Hanami::Settings::CompositeStore`, which can be used to chain setting lookups from multiple stores. (@aaronmallen in #1572)
69
+ - Add `Hanami::Slice.with_slices`, returning the slice and all its nested slices. (@timriley in #1604)
70
+
71
+ ### Changed
72
+
73
+ - Default to memoizing all components, except in test env. Opt out for some or all components via `config.no_memoize`. (@timriley in #1573, #1599)
74
+ - Colorize logs by default in development env. (@timriley in #1566)
75
+ - Emit structured log entries for SQL queries, formatted consistently with request logs. (@timriley in #1569)
76
+ - Syntax highlight SQL in logs when the rouge gem is bundled. (@timriley in #1570, #1607)
77
+ - Colorize web request logs. (@timriley in #1571)
78
+ - Change default log level for SQL (and other database) statements from `:info` to `:debug`. (@katafrakt in #1595)
79
+ - Raise a helpful error message when the `Slice.call` Rack entrypoint is called and no routes are available. (@sandbergja in #1586)
80
+ - Apply extensions to hanami-action gem rather than hanami-controller (which is now retired). (@cllns in #1582)
81
+ - Redesign the new app welcome screen to match our new Hanakai visuals. (@makenosound in #1598)
82
+ - Require Ruby 3.3 or newer.
83
+
84
+ ### Removed
85
+
86
+ - Remove default body parsing middleware. This functionality has moved into Hanami Action. (@timriley in #1575)
87
+
88
+ [3.0.0]: https://github.com/hanami/hanami/compare/v2.3.2...v3.0.0
41
89
 
42
90
  ## [3.0.0.rc1] - 2026-06-16
43
91
 
@@ -46,7 +94,7 @@ A complete Hanami app is composed of multiple gems. For a complete overview of c
46
94
  - Integrate hanami-mailer gem when bundled. (@timriley in #1597, #1600)
47
95
 
48
96
  Load templates from `templates/mailers/`. Register a `"mailers.delivery_method"`, which is either an SMTP mailer when `SMTP_ADDRESS`, `SMTP_PORT`, `SMTP_USERNAME`, `SMTP_PASSWORD`, `SMTP_AUTHENTICATION` env vars are present. These can also be prefixed with a slice name. Register a test delivery always in the test env, and also in other envs when the env vars are absent.
49
- - Integrate i18n gem when bundled. (@timriley in #1562, #1589, #1590, #1591, #1592)
97
+ - Integrate i18n gem when bundled. (@timriley in #1562, #1589, #1590, #1591, #1592, #1593)
50
98
 
51
99
  Register an `"i18n"` component in each slice, which may be configured via `config.i18n` or a dedicated `:i18n` provider. Load translations from `config/i18n/` within each slice. Load shared translations from `config/i18n/shared/` at the app-level only. Bundle default English translations for `#localize`. Make helpers available in views and actions, which also prefix relative keys (with a leading ".") with their own dot-delimited template or action names.
52
100
  - Wrap the configured app logger with `Hanami::UniversalLogger`, to provide a consistent logging interface regardless of the underlying logger. The app can now be depended upon to support (1) structured logging via keyword args passed to log methods, and (2) tagged logging via `#tagged`. (@timriley in #1567, #1568)
data/hanami.gemspec CHANGED
@@ -38,7 +38,7 @@ Gem::Specification.new do |spec|
38
38
  spec.add_runtime_dependency "dry-system", "~> 1.1"
39
39
  spec.add_runtime_dependency "dry-logger", "~> 1.2", "< 2"
40
40
  spec.add_runtime_dependency "hanami-cli", ">= 2.3.1"
41
- spec.add_runtime_dependency "hanami-utils", ">= 2.3.0"
41
+ spec.add_runtime_dependency "hanami-utils", "~> 3.0.0"
42
42
  spec.add_runtime_dependency "json", ">= 2.7.2"
43
43
  spec.add_runtime_dependency "zeitwerk", "~> 2.6"
44
44
  spec.add_runtime_dependency "rack-session"
data/lib/hanami/app.rb CHANGED
@@ -151,12 +151,22 @@ module Hanami
151
151
  require_relative "providers/inflector"
152
152
  register_provider(:inflector, source: Hanami::Providers::Inflector)
153
153
 
154
- # Allow logger to be replaced by users with a manual provider, for advanced cases
154
+ # Allow the logger to be replaced by users with a manual provider, for advanced cases.
155
+ #
156
+ # Require the logger provider source up front, to make `configure_provider(:logger)`
157
+ # possible.
158
+ require_relative "providers/logger"
155
159
  unless container.providers[:logger]
156
- require_relative "providers/logger"
157
160
  register_provider(:logger, source: Hanami::Providers::Logger)
158
161
  end
159
162
 
163
+ # Ensure the logger is wrapped by `Hanami::UniversalLogger`, even if manually registered in a
164
+ # user-defined provider, guaranteeing Hanami's structured and tagged logging interface across
165
+ # the framework.
166
+ container.providers[:logger].source.after(:start) do
167
+ container.decorate(:logger) { |logger| Hanami::UniversalLogger[logger] }
168
+ end
169
+
160
170
  if Hanami.bundled?("rack")
161
171
  require_relative "providers/rack"
162
172
  register_provider(:rack, source: Hanami::Providers::Rack, namespace: true)
@@ -8,7 +8,7 @@ module Hanami
8
8
  # Hanami I18n config
9
9
  #
10
10
  # @api public
11
- # @since x.x.x
11
+ # @since 3.0.0
12
12
  class I18n
13
13
  include Dry::Configurable
14
14
 
@@ -23,7 +23,7 @@ module Hanami
23
23
  # config.i18n.default_locale = :fr
24
24
  #
25
25
  # @api public
26
- # @since x.x.x
26
+ # @since 3.0.0
27
27
  setting :default_locale, default: Providers::I18n::DEFAULT_LOCALE
28
28
 
29
29
  # @!attribute [rw] available_locales
@@ -41,7 +41,7 @@ module Hanami
41
41
  # config.i18n.available_locales = [:en, :fr, :de]
42
42
  #
43
43
  # @api public
44
- # @since x.x.x
44
+ # @since 3.0.0
45
45
  setting :available_locales, default: Providers::I18n::DEFAULT_AVAILABLE_LOCALES
46
46
 
47
47
  # @!attribute [rw] load_path
@@ -65,7 +65,7 @@ module Hanami
65
65
  # config.i18n.load_path = ["translations/**/*.yml"]
66
66
  #
67
67
  # @api public
68
- # @since x.x.x
68
+ # @since 3.0.0
69
69
  setting :load_path, default: Providers::I18n::DEFAULT_LOAD_PATH
70
70
 
71
71
  # @!attribute [rw] shared_load_path
@@ -91,7 +91,7 @@ module Hanami
91
91
  # config.i18n.shared_load_path += ["vendor/translations/**/*.yml"]
92
92
  #
93
93
  # @api public
94
- # @since x.x.x
94
+ # @since 3.0.0
95
95
  setting :shared_load_path, default: Providers::I18n::DEFAULT_SHARED_LOAD_PATH
96
96
 
97
97
  # @!attribute [rw] fallbacks
@@ -117,7 +117,7 @@ module Hanami
117
117
  # config.i18n.fallbacks = [:en]
118
118
  #
119
119
  # @api public
120
- # @since x.x.x
120
+ # @since 3.0.0
121
121
  setting :fallbacks
122
122
 
123
123
  private
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Extensions
5
+ module Action
6
+ # Action translation and localization helpers.
7
+ #
8
+ # These helpers are automatically available on `Hanami::Action` when the `i18n` gem is
9
+ # bundled.
10
+ #
11
+ # When relative translation keys (with a leading dot) are given, they are expanded against a
12
+ # the action's name. For example, `t(".not_found")` within `Main::Actions::Posts::Show`
13
+ # becomes `"posts.show.not_found"`.
14
+ #
15
+ # @example Basic translation in an action
16
+ # module Main
17
+ # module Actions
18
+ # module Posts
19
+ # class Create < Main::Action
20
+ # def handle(req, res)
21
+ # res.flash[:notice] = t("messages.post_created")
22
+ # res.redirect_to routes.path(:posts)
23
+ # end
24
+ # end
25
+ # end
26
+ # end
27
+ # end
28
+ #
29
+ # @example Relative key lookup
30
+ # # In Main::Actions::Posts::Show, this looks up "posts.show.not_found"
31
+ # t(".not_found")
32
+ #
33
+ # @see Hanami::Helpers::I18nHelper::Methods
34
+ #
35
+ # @api public
36
+ # @since 3.0.0
37
+ module I18nHelper
38
+ include Hanami::Helpers::I18nHelper::Methods
39
+
40
+ private
41
+
42
+ def _i18n
43
+ i18n
44
+ end
45
+
46
+ def _resolve_i18n_key(key)
47
+ return key unless key.to_s.start_with?(".")
48
+
49
+ key_base = self.class.config.i18n_key_base
50
+
51
+ unless key_base
52
+ raise(
53
+ ::I18n::ArgumentError,
54
+ "Cannot use relative translation key #{key.inspect} outside of a slice-configured action. " \
55
+ "Use an absolute key (without a leading dot) instead."
56
+ )
57
+ end
58
+
59
+ "#{key_base}#{key}"
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Extensions
5
+ module Action
6
+ # Infers an action's name (e.g. `posts.show`) from its class name relative to its slice
7
+ # namespace.
8
+ #
9
+ # @api private
10
+ class NameInferrer
11
+ class << self
12
+ # @example
13
+ # NameInferrer.call(action_class_name: "Main::Actions::Posts::Show", slice: Main::Slice)
14
+ # # => "posts.show"
15
+ #
16
+ # @param action_class_name [String, nil] the action class name
17
+ # @param slice [Hanami::Slice] the slice the action belongs to
18
+ #
19
+ # @return [String, nil] the inferred name, or nil if `action_class_name` is nil
20
+ def call(action_class_name:, slice:)
21
+ return nil unless action_class_name
22
+
23
+ slice
24
+ .inflector
25
+ .underscore(action_class_name)
26
+ .sub(%r{^#{slice.slice_name.path}#{PATH_DELIMITER}}, "")
27
+ .sub(%r{^#{slice.config.actions.name_inference_base}#{PATH_DELIMITER}}, "")
28
+ .gsub("/", CONTAINER_KEY_DELIMITER)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -3,11 +3,12 @@
3
3
  module Hanami
4
4
  module Extensions
5
5
  module Action
6
+ require_relative "name_inferrer"
7
+
6
8
  # Provides slice-specific configuration and behavior for any action class defined
7
9
  # within a slice's module namespace.
8
10
  #
9
11
  # @api private
10
- # @since 2.0.0
11
12
  class SliceConfiguredAction < Module
12
13
  attr_reader :slice
13
14
 
@@ -18,7 +19,9 @@ module Hanami
18
19
 
19
20
  def extended(action_class)
20
21
  configure_action(action_class)
22
+ configure_action_i18n(action_class)
21
23
  extend_behavior(action_class)
24
+ define_inherited
22
25
  define_new
23
26
  end
24
27
 
@@ -34,6 +37,7 @@ module Hanami
34
37
  view_context_class = method(:view_context_class)
35
38
  resolve_routes = method(:resolve_routes)
36
39
  resolve_rack_monitor = method(:resolve_rack_monitor)
40
+ resolve_i18n = method(:resolve_i18n)
37
41
 
38
42
  define_method(:new) do |**kwargs|
39
43
  super(
@@ -41,11 +45,24 @@ module Hanami
41
45
  view_context_class: kwargs.fetch(:view_context_class) { view_context_class.() },
42
46
  routes: kwargs.fetch(:routes) { resolve_routes.() },
43
47
  rack_monitor: kwargs.fetch(:rack_monitor) { resolve_rack_monitor.() },
48
+ i18n: kwargs.fetch(:i18n) { resolve_i18n.() },
44
49
  **kwargs,
45
50
  )
46
51
  end
47
52
  end
48
53
 
54
+ # Defines an `inherited` hook on this module so each subclass of the slice-configured
55
+ # action gets its own `config.i18n_key_base`, inferred from its class name. Mirrors the
56
+ # approach used by `SliceConfiguredView` for `config.template`.
57
+ def define_inherited
58
+ configure_action_i18n = method(:configure_action_i18n)
59
+
60
+ define_method(:inherited) do |subclass|
61
+ super(subclass)
62
+ configure_action_i18n.call(subclass)
63
+ end
64
+ end
65
+
49
66
  def configure_action(action_class)
50
67
  action_class.settings.each do |setting|
51
68
  # Configure the action from config on the slice, _unless it has already been configured
@@ -98,6 +115,14 @@ module Hanami
98
115
  end
99
116
  end
100
117
 
118
+ def configure_action_i18n(action_class)
119
+ return unless action_class.config.respond_to?(:i18n_key_base=)
120
+
121
+ action_class.config.i18n_key_base = NameInferrer.call(
122
+ action_class_name: action_class.name, slice:
123
+ )
124
+ end
125
+
101
126
  def extend_behavior(action_class)
102
127
  if actions_config.sessions.enabled?
103
128
  require "hanami/action/session"
@@ -152,6 +177,10 @@ module Hanami
152
177
  slice.app["rack.monitor"] if slice.app.key?("rack.monitor")
153
178
  end
154
179
 
180
+ def resolve_i18n
181
+ slice["i18n"] if slice.key?("i18n")
182
+ end
183
+
155
184
  def actions_config
156
185
  slice.config.actions
157
186
  end
@@ -43,8 +43,7 @@ module Hanami
43
43
  # @api private
44
44
  attr_reader :view_context_class
45
45
 
46
- # Returns the app or slice's {Hanami::Slice::RoutesHelper RoutesHelper} for use within
47
- # action instance methods.
46
+ # Returns the slice's {Hanami::Slice::RoutesHelper RoutesHelper}.
48
47
  #
49
48
  # @return [Hanami::Slice::RoutesHelper]
50
49
  #
@@ -52,8 +51,7 @@ module Hanami
52
51
  # @since 2.0.0
53
52
  attr_reader :routes
54
53
 
55
- # Returns the app or slice's `Dry::Monitor::Rack::Middleware` for use within
56
- # action instance methods.
54
+ # Returns the slice's `Dry::Monitor::Rack::Middleware`.
57
55
  #
58
56
  # @return [Dry::Monitor::Rack::Middleware]
59
57
  #
@@ -61,6 +59,14 @@ module Hanami
61
59
  # @since 2.0.0
62
60
  attr_reader :rack_monitor
63
61
 
62
+ # Returns the slice's i18n backend.
63
+ #
64
+ # @return [Hanami::Providers::I18n::Backend]
65
+ #
66
+ # @api public
67
+ # @since x.x.x
68
+ attr_reader :i18n
69
+
64
70
  # @overload def initialize(routes: nil, **kwargs)
65
71
  # Returns a new `Hanami::Action` with app components injected as dependencies.
66
72
  #
@@ -71,11 +77,12 @@ module Hanami
71
77
  #
72
78
  # @api public
73
79
  # @since 2.0.0
74
- def initialize(view: nil, view_context_class: nil, rack_monitor: nil, routes: nil, **kwargs)
80
+ def initialize(view: nil, view_context_class: nil, rack_monitor: nil, routes: nil, i18n: nil, **kwargs)
75
81
  @view = view
76
82
  @view_context_class = view_context_class
77
83
  @routes = routes
78
84
  @rack_monitor = rack_monitor
85
+ @i18n = i18n
79
86
 
80
87
  super(**kwargs)
81
88
  end
@@ -132,3 +139,9 @@ module Hanami
132
139
  end
133
140
 
134
141
  Hanami::Action.include(Hanami::Extensions::Action)
142
+
143
+ if Hanami.bundled?("i18n")
144
+ require_relative "action/i18n_helper"
145
+ Hanami::Action.setting(:i18n_key_base)
146
+ Hanami::Action.include(Hanami::Extensions::Action::I18nHelper)
147
+ end
@@ -25,6 +25,7 @@ module Hanami
25
25
  def extended(mailer_class)
26
26
  configure_mailer(mailer_class)
27
27
  define_new
28
+ define_inherited
28
29
  end
29
30
 
30
31
  def inspect
@@ -44,6 +45,19 @@ module Hanami
44
45
  end
45
46
  end
46
47
 
48
+ # Reconfigures the template name for each subclass.
49
+ def define_inherited
50
+ template_name = method(:template_name)
51
+
52
+ define_method(:inherited) do |subclass|
53
+ super(subclass)
54
+
55
+ if (template = template_name.(subclass))
56
+ subclass.config.template = template
57
+ end
58
+ end
59
+ end
60
+
47
61
  def resolve_delivery_method
48
62
  slice["mailers.delivery_method"] if slice.key?("mailers.delivery_method")
49
63
  end
@@ -203,7 +203,7 @@ module Hanami
203
203
  # @raise [Hanami::ComponentLoadError] if the i18n gem is not bundled
204
204
  #
205
205
  # @api public
206
- # @since x.x.x
206
+ # @since 3.0.0
207
207
  def i18n
208
208
  unless @i18n
209
209
  raise Hanami::ComponentLoadError, "the i18n gem is required to access translations"
@@ -1,140 +1,226 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "hanami/view"
3
+ require "cgi/escape"
4
4
 
5
5
  module Hanami
6
6
  module Helpers
7
- # Helper methods for translating and localizing content using the slice's i18n backend.
7
+ # View-layer translation and localization helpers.
8
8
  #
9
- # These helpers will be automatically available in your view templates, part classes, and scope
10
- # classes when the `i18n` gem is bundled.
9
+ # These helpers are automatically available in your view templates, part classes, and scope
10
+ # classes when the `i18n` gem is bundled. They provide `translate`/`t`, `translate!`/`t!`, and
11
+ # `localize`/`l`, sourcing the i18n backend from the view context and expanding relative
12
+ # (leading-dot) translation keys against the currently-rendering template name.
13
+ #
14
+ # The shared `translate`/`localize` logic lives in {Methods}, which is also included by the
15
+ # action-layer i18n helper ({Hanami::Extensions::Action::I18nHelper}). This module supplies
16
+ # the two view-specific concrete implementations of {Methods}'s abstract hooks: {#_i18n}
17
+ # returns the i18n backend from the view context, and {#_resolve_i18n_key} expands relative
18
+ # keys against the template name.
19
+ #
20
+ # @example Basic translation
21
+ # <%= translate("messages.welcome") %>
22
+ # # => "Welcome"
23
+ #
24
+ # @example HTML-safe translation
25
+ # # en.yml
26
+ # # greeting_html: "Hello, <strong>%{name}</strong>!"
27
+ # <%= translate("greeting_html", name: "<script>") %>
28
+ # # => "Hello, <strong>&lt;script&gt;</strong>!" (marked HTML-safe)
29
+ #
30
+ # @example Missing translation
31
+ # <%= translate("missing.key") %>
32
+ # # => '<span class="translation_missing" title="...">missing.key</span>'
33
+ #
34
+ # @example Relative key lookup
35
+ # # In app/templates/users/index.html.erb:
36
+ # <%= translate(".title") %>
37
+ # # Looks up "users.index.title"
38
+ #
39
+ # # In app/templates/users/_form.html.erb (a partial):
40
+ # <%= translate(".label") %>
41
+ # # Looks up "users._form.label"
11
42
  #
12
43
  # @api public
13
- # @since x.x.x
44
+ # @since 3.0.0
14
45
  module I18nHelper
15
- # Matches keys whose final segment is `html` or whose final segment ends in `_html`. These
16
- # translated values are treated as HTML-safe and any string interpolation values are
17
- # HTML-escaped before substitution.
18
- #
19
- # @api private
20
- HTML_SAFE_TRANSLATION_KEY = /(\b|_)html\z/
21
-
22
- # Translates the given key using the slice's i18n backend.
23
- #
24
- # When the key's final segment is `html` or ends in `_html`, the result is marked HTML-safe
25
- # and any string interpolation values are HTML-escaped first.
46
+ # Shared `translate` / `localize` (and shorthand) helper methods used by both view-layer
47
+ # consumers and action-layer consumers.
26
48
  #
27
- # When a translation is missing and neither `:default` nor `:raise` was supplied, returns a
28
- # `<span class="translation_missing">` element containing the missing key, useful for
29
- # spotting missing translations during development.
49
+ # **This module is abstract.** Including modules must override two private hooks:
30
50
  #
31
- # When the key begins with a `.`, it is treated as relative to the currently-rendering
32
- # template and expanded against its name (slashes become dots; partial basenames keep
33
- # their leading underscore). For example, `translate(".title")` inside
34
- # `users/index.html.erb` resolves to `users.index.title`, and `translate(".label")`
35
- # inside `users/_form.html.erb` resolves to `users._form.label`. Raises
36
- # `I18n::ArgumentError` if called outside a template render.
37
- #
38
- # @param key [String, Symbol] the translation key to look up
39
- # @param options [Hash] translation options forwarded to the backend (`:locale`, `:scope`,
40
- # `:default`, `:count`, `:raise`, etc.), plus any interpolation values
41
- #
42
- # @return [String, Hanami::View::HTML::SafeString] the translated string
43
- #
44
- # @example Basic translation
45
- # <%= translate("messages.welcome") %>
46
- # # => "Welcome"
47
- #
48
- # @example HTML-safe translation
49
- # # en.yml
50
- # # greeting_html: "Hello, <strong>%{name}</strong>!"
51
- # <%= translate("greeting_html", name: "<script>") %>
52
- # # => "Hello, <strong>&lt;script&gt;</strong>!" (marked HTML-safe)
53
- #
54
- # @example Missing translation
55
- # <%= translate("missing.key") %>
56
- # # => '<span class="translation_missing" title="...">missing.key</span>'
57
- #
58
- # @example Relative key lookup
59
- # # In app/templates/users/index.html.erb:
60
- # <%= translate(".title") %>
61
- # # Looks up "users.index.title"
62
- #
63
- # # In app/templates/users/_form.html.erb (a partial):
64
- # <%= translate(".label") %>
65
- # # Looks up "users._form.label"
51
+ # - {#_i18n} returns the i18n backend to delegate to.
52
+ # - {#_resolve_i18n_key} expands relative (leading-dot) keys against the consumer's
53
+ # context, and is a no-op for absolute keys.
66
54
  #
67
55
  # @api public
68
- # @since x.x.x
69
- def translate(key, **options)
70
- key = _resolve_i18n_key(key)
56
+ # @since 3.0.0
57
+ module Methods
58
+ # Matches keys whose final segment is `html` or whose final segment ends in `_html`. These
59
+ # translated values are treated as HTML-safe and any string interpolation values are
60
+ # HTML-escaped before substitution.
61
+ #
62
+ # @api private
63
+ HTML_SAFE_TRANSLATION_KEY = /(\b|_)html\z/
64
+
65
+ # Translates the given key using the slice's i18n backend.
66
+ #
67
+ # When the key's final segment is `html` or ends in `_html`, the result is marked HTML-safe
68
+ # and any string interpolation values are HTML-escaped first.
69
+ #
70
+ # When a translation is missing and neither `:default` nor `:raise` was supplied, returns a
71
+ # `<span class="translation_missing">` element containing the missing key, useful for
72
+ # spotting missing translations during development.
73
+ #
74
+ # When the key begins with a `.`, it is treated as relative to the consumer's context and
75
+ # expanded by {#_resolve_i18n_key}.
76
+ #
77
+ # @param key [String, Symbol] the translation key to look up
78
+ # @param options [Hash] translation options forwarded to the backend (`:locale`, `:scope`,
79
+ # `:default`, `:count`, `:raise`, etc.), plus any interpolation values
80
+ #
81
+ # @return [String] the translated string (marked HTML-safe when hanami-view is bundled and
82
+ # the key ends in `_html` or `html`)
83
+ #
84
+ # @api public
85
+ # @since 3.0.0
86
+ def translate(key, **options)
87
+ key = _resolve_i18n_key(key)
88
+
89
+ html_safe = _html_safe_translation_key?(key)
90
+
91
+ options = _escape_translation_options(options) if html_safe
92
+
93
+ result =
94
+ if options.key?(:default) || options[:raise]
95
+ _i18n.translate(key, **options)
96
+ else
97
+ begin
98
+ _i18n.translate(key, **options, raise: true)
99
+ rescue ::I18n::MissingTranslationData => exception
100
+ return _missing_translation_markup(key, exception)
101
+ end
102
+ end
71
103
 
72
- html_safe = _html_safe_translation_key?(key)
104
+ html_safe ? _i18n_mark_html_safe(result.to_s) : result
105
+ end
73
106
 
74
- options = _escape_translation_options(options) if html_safe
107
+ # @api public
108
+ # @since 3.0.0
109
+ alias_method :t, :translate
110
+
111
+ # Translates the given key, raising an exception if the translation is missing.
112
+ #
113
+ # @param (see #translate)
114
+ #
115
+ # @return [String] the translated string (marked HTML-safe when hanami-view is bundled and
116
+ # the key ends in `_html` or `html`)
117
+ #
118
+ # @raise [I18n::MissingTranslationData] if the translation is missing
119
+ #
120
+ # @api public
121
+ # @since 3.0.0
122
+ def translate!(key, **options)
123
+ translate(key, **options, raise: true)
124
+ end
75
125
 
76
- result =
77
- if options.key?(:default) || options[:raise]
78
- _context.i18n.translate(key, **options)
79
- else
80
- begin
81
- _context.i18n.translate(key, **options, raise: true)
82
- rescue ::I18n::MissingTranslationData => exception
83
- return _missing_translation_markup(key, exception)
84
- end
85
- end
126
+ # @api public
127
+ # @since 3.0.0
128
+ alias_method :t!, :translate!
129
+
130
+ # Localizes the given object (e.g. a date, time, or number) using the slice's i18n backend.
131
+ #
132
+ # @param object [Date, Time, DateTime, Numeric] the object to localize
133
+ # @param options [Hash] localization options forwarded to the backend (`:locale`, `:format`,
134
+ # etc.)
135
+ #
136
+ # @return [String] the localized string
137
+ #
138
+ # @api public
139
+ # @since 3.0.0
140
+ def localize(object, **options)
141
+ _i18n.localize(object, **options)
142
+ end
86
143
 
87
- html_safe ? result.to_s.html_safe : result
88
- end
144
+ # @api public
145
+ # @since 3.0.0
146
+ alias_method :l, :localize
89
147
 
90
- # @api public
91
- # @since x.x.x
92
- alias_method :t, :translate
148
+ private
93
149
 
94
- # Translates the given key, raising an exception if the translation is missing.
95
- #
96
- # @param (see #translate)
97
- #
98
- # @return [String, Hanami::View::HTML::SafeString] the translated string
99
- #
100
- # @raise [I18n::MissingTranslationData] if the translation is missing
101
- #
102
- # @example
103
- # <%= translate!("messages.welcome") %>
104
- #
105
- # @api public
106
- # @since x.x.x
107
- def translate!(key, **options)
108
- translate(key, **options, raise: true)
109
- end
150
+ # Returns the i18n backend the helper methods delegate to.
151
+ #
152
+ # Including modules must override this method to return an I18n backend instance.
153
+ #
154
+ # @return [Hanami::Providers::I18n::Backend]
155
+ def _i18n
156
+ raise NoMethodError, "#{self.class} must implement #_i18n to return an i18n backend"
157
+ end
110
158
 
111
- # @api public
112
- # @since x.x.x
113
- alias_method :t!, :translate!
159
+ # Resolves the given key, returning it unchanged by default.
160
+ #
161
+ # Override this hook to expand relative (leading-dot) keys against a context.
162
+ #
163
+ # @param key [String, Symbol]
164
+ #
165
+ # @return [String, Symbol] the resolved key
166
+ def _resolve_i18n_key(key)
167
+ key
168
+ end
114
169
 
115
- # Localizes the given object (e.g. a date, time, or number) using the slice's i18n backend.
116
- #
117
- # @param object [Date, Time, DateTime, Numeric] the object to localize
118
- # @param options [Hash] localization options forwarded to the backend (`:locale`, `:format`,
119
- # etc.)
120
- #
121
- # @return [String] the localized string
122
- #
123
- # @example
124
- # <%= localize(Date.today, format: :long) %>
125
- #
126
- # @api public
127
- # @since x.x.x
128
- def localize(object, **options)
129
- _context.i18n.localize(object, **options)
170
+ def _html_safe_translation_key?(key)
171
+ HTML_SAFE_TRANSLATION_KEY.match?(key.to_s)
172
+ end
173
+
174
+ def _escape_translation_options(options)
175
+ options.each_with_object({}) do |(key, value), result|
176
+ result[key] =
177
+ if ::I18n::RESERVED_KEYS.include?(key) || !value.is_a?(String) || _i18n_html_safe?(value)
178
+ value
179
+ else
180
+ _i18n_html_escape(value)
181
+ end
182
+ end
183
+ end
184
+
185
+ def _missing_translation_markup(key, error)
186
+ title = _i18n_html_escape(error.message)
187
+ body = _i18n_html_escape(key.to_s)
188
+ _i18n_mark_html_safe(%(<span class="translation_missing" title="#{title}">#{body}</span>))
189
+ end
190
+
191
+ # Escapes `value` for HTML. Prefers `Hanami::View::Helpers::EscapeHelper#escape_html` when
192
+ # Hanami View's EscapeHelper is included, falling back to stdlib `CGI.escapeHTML` so the
193
+ # helper works in API-only apps that don't bundle hanami-view.
194
+ def _i18n_html_escape(value)
195
+ if respond_to?(:escape_html)
196
+ escape_html(value)
197
+ else
198
+ CGI.escapeHTML(value.to_s)
199
+ end
200
+ end
201
+
202
+ # Marks the given string as HTML-safe when Hanami View is loaded, otherwise returns the
203
+ # string unchanged.
204
+ #
205
+ # The HTML-safe marker is only meaningful to Hanami View's template rendering, so there's
206
+ # nothing to do in its absence.
207
+ def _i18n_mark_html_safe(str)
208
+ str.respond_to?(:html_safe) ? str.html_safe : str
209
+ end
210
+
211
+ def _i18n_html_safe?(value)
212
+ value.respond_to?(:html_safe?) && value.html_safe?
213
+ end
130
214
  end
131
215
 
132
- # @api public
133
- # @since x.x.x
134
- alias_method :l, :localize
216
+ include Methods
135
217
 
136
218
  private
137
219
 
220
+ def _i18n
221
+ _context.i18n
222
+ end
223
+
138
224
  def _resolve_i18n_key(key)
139
225
  return key unless key.to_s.start_with?(".")
140
226
 
@@ -150,27 +236,6 @@ module Hanami
150
236
 
151
237
  "#{template_name.tr("/", ".")}#{key}"
152
238
  end
153
-
154
- def _html_safe_translation_key?(key)
155
- HTML_SAFE_TRANSLATION_KEY.match?(key.to_s)
156
- end
157
-
158
- def _escape_translation_options(options)
159
- options.each_with_object({}) do |(key, value), result|
160
- result[key] =
161
- if ::I18n::RESERVED_KEYS.include?(key) || !value.is_a?(String) || value.html_safe?
162
- value
163
- else
164
- escape_html(value)
165
- end
166
- end
167
- end
168
-
169
- def _missing_translation_markup(key, error)
170
- title = escape_html(error.message)
171
- body = escape_html(key.to_s)
172
- %(<span class="translation_missing" title="#{title}">#{body}</span>).html_safe
173
- end
174
239
  end
175
240
  end
176
241
  end
@@ -26,9 +26,9 @@ module Hanami
26
26
  # highlighted using Rouge's SQL lexer. This is a soft dependency; if Rouge is not installed,
27
27
  # queries output as plain, unhighlighted text.
28
28
  #
29
- # The Rouge theme defaults to Gruvbox and can be customized by setting the `HANAMI_SQL_THEME`
30
- # environment variable to any Rouge theme name (e.g. "github.dark", "monokai", "gruvbox.light").
31
- # See `Rouge::Theme.registry` for available themes.
29
+ # The Rouge theme defaults to "pastie" and can be customized by setting the
30
+ # `HANAMI_LOG_SYNTAX_THEME` environment variable to any Rouge theme name. See
31
+ # `Rouge::Theme.registry` for available themes.
32
32
  #
33
33
  # @see Hanami::Logger::SQLLogger
34
34
  #
@@ -67,8 +67,8 @@ module Hanami
67
67
  return nil
68
68
  end
69
69
 
70
- theme_name = ENV.fetch("HANAMI_SQL_THEME", "gruvbox")
71
- theme_class = Rouge::Theme.find(theme_name) || Rouge::Themes::Gruvbox
70
+ theme_name = ENV.fetch("HANAMI_LOG_SYNTAX_THEME", "pastie")
71
+ theme_class = Rouge::Theme.find(theme_name) || Rouge::Themes::Pastie
72
72
  formatter = Rouge::Formatters::Terminal256.new(theme_class.new)
73
73
 
74
74
  lexer = Rouge::Lexers::SQL.new
@@ -10,7 +10,7 @@ module Hanami
10
10
  # this wrapper maintains per-instance state for true isolation between slices.
11
11
  #
12
12
  # @api public
13
- # @since x.x.x
13
+ # @since 3.0.0
14
14
  class Backend
15
15
  attr_reader :backend
16
16
 
@@ -19,7 +19,7 @@ module Hanami
19
19
  # @return [Symbol] the default locale
20
20
  #
21
21
  # @api public
22
- # @since x.x.x
22
+ # @since 3.0.0
23
23
  attr_reader :default_locale
24
24
 
25
25
  # Creates a new Backend instance.
@@ -31,7 +31,7 @@ module Hanami
31
31
  # @param fallbacks [I18n::Locale::Fallbacks, nil] fallbacks configuration for missing translations
32
32
  #
33
33
  # @api private
34
- # @since x.x.x
34
+ # @since 3.0.0
35
35
  def initialize(backend, locale: nil, default_locale: :en, available_locales: [], fallbacks: nil)
36
36
  @backend = backend
37
37
  @default_locale = default_locale.to_sym
@@ -69,7 +69,7 @@ module Hanami
69
69
  # translate("hello", locale: :fr) # => "Bonjour"
70
70
  #
71
71
  # @api public
72
- # @since x.x.x
72
+ # @since 3.0.0
73
73
  def translate(key, **options)
74
74
  locale = options[:locale] || self.locale
75
75
 
@@ -105,7 +105,7 @@ module Hanami
105
105
  # rubocop:enable Metrics/PerceivedComplexity
106
106
 
107
107
  # @api public
108
- # @since x.x.x
108
+ # @since 3.0.0
109
109
  alias_method :t, :translate
110
110
 
111
111
  # Translates the given key, raising an exception if translation is missing.
@@ -118,15 +118,19 @@ module Hanami
118
118
  # @raise [I18n::MissingTranslationData] if translation is missing
119
119
  #
120
120
  # @example
121
- # t!("hello") # => "Hello"
122
- # t!("missing.key") # raises I18n::MissingTranslationData
121
+ # translate!("hello") # => "Hello"
122
+ # translate!("missing.key") # raises I18n::MissingTranslationData
123
123
  #
124
124
  # @api public
125
- # @since x.x.x
126
- def t!(key, **options)
125
+ # @since 3.0.0
126
+ def translate!(key, **options)
127
127
  translate(key, **options.merge(raise: true))
128
128
  end
129
129
 
130
+ # @api public
131
+ # @since 3.0.0
132
+ alias_method :t!, :translate!
133
+
130
134
  # Localizes the given date or time.
131
135
  #
132
136
  # Resolves symbol formats through this instance's translations (e.g. `:short` becomes
@@ -147,7 +151,7 @@ module Hanami
147
151
  # localize(Date.today, locale: :fr, format: :long) # => "19 janvier 2026"
148
152
  #
149
153
  # @api public
150
- # @since x.x.x
154
+ # @since 3.0.0
151
155
  def localize(object, locale: nil, format: :default, **options)
152
156
  locale ||= self.locale
153
157
 
@@ -172,7 +176,7 @@ module Hanami
172
176
  end
173
177
 
174
178
  # @api public
175
- # @since x.x.x
179
+ # @since 3.0.0
176
180
  alias_method :l, :localize
177
181
 
178
182
  # Returns true if a translation exists for the given key.
@@ -188,7 +192,7 @@ module Hanami
188
192
  # exists?("missing.key") # => false
189
193
  #
190
194
  # @api public
191
- # @since x.x.x
195
+ # @since 3.0.0
192
196
  def exists?(key, locale: nil, **options)
193
197
  locale ||= self.locale
194
198
  @backend.exists?(locale, key, options)
@@ -207,7 +211,7 @@ module Hanami
207
211
  # transliterate("Ærøskøbing") # => "AEroskobing"
208
212
  #
209
213
  # @api public
210
- # @since x.x.x
214
+ # @since 3.0.0
211
215
  def transliterate(key, locale: nil, replacement: nil, **options)
212
216
  locale ||= self.locale
213
217
  @backend.transliterate(locale, key, replacement)
@@ -219,7 +223,7 @@ module Hanami
219
223
  # Otherwise, returns all locales detected from loaded translation files.
220
224
  #
221
225
  # @api public
222
- # @since x.x.x
226
+ # @since 3.0.0
223
227
  def available_locales
224
228
  if @available_locales.any?
225
229
  @available_locales
@@ -233,7 +237,7 @@ module Hanami
233
237
  # @return [void]
234
238
  #
235
239
  # @api public
236
- # @since x.x.x
240
+ # @since 3.0.0
237
241
  def reload!
238
242
  @backend.reload!
239
243
  end
@@ -243,7 +247,7 @@ module Hanami
243
247
  # @return [void]
244
248
  #
245
249
  # @api public
246
- # @since x.x.x
250
+ # @since 3.0.0
247
251
  def eager_load!
248
252
  @backend.eager_load! if @backend.respond_to?(:eager_load!)
249
253
  end
@@ -261,7 +265,7 @@ module Hanami
261
265
  # locale # => :fr
262
266
  #
263
267
  # @api public
264
- # @since x.x.x
268
+ # @since 3.0.0
265
269
  def locale
266
270
  Thread.current[@storage_key] || default_locale
267
271
  end
@@ -281,7 +285,7 @@ module Hanami
281
285
  # self.locale = nil # resets to default_locale
282
286
  #
283
287
  # @api public
284
- # @since x.x.x
288
+ # @since 3.0.0
285
289
  def locale=(value)
286
290
  Thread.current[@storage_key] = value&.to_sym
287
291
  end
@@ -312,7 +316,7 @@ module Hanami
312
316
  # end
313
317
  #
314
318
  # @api public
315
- # @since x.x.x
319
+ # @since 3.0.0
316
320
  def with_locale(tmp_locale)
317
321
  previous_locale = Thread.current[@storage_key]
318
322
  Thread.current[@storage_key] = tmp_locale&.to_sym
@@ -12,8 +12,30 @@ module Hanami
12
12
  class Logger < Hanami::Provider::Source
13
13
  # @api private
14
14
  def start
15
- register :logger, Hanami.app.config.logger_instance
15
+ register :logger, logger
16
+ end
17
+
18
+ # Returns the logger instance that will be registered as the slice's `"logger"` component.
19
+ #
20
+ # This is memoized, so lifecycle callbacks added when extending the provider via
21
+ # {Hanami::Slice::ClassMethods#configure_provider} (such as a `before :start` or `after :start`
22
+ # hook) can access and customize the very same logger that `start` registers — for example, to
23
+ # add a logging backend.
24
+ #
25
+ # @return [Dry::Logger::Dispatcher] the default logger, or the logger instance configured via
26
+ # {Hanami::Config#logger=}
27
+ #
28
+ # @api public
29
+ # @since 3.0.0
30
+ def logger
31
+ @logger ||= Hanami.app.config.logger_instance
16
32
  end
17
33
  end
34
+
35
+ Dry::System.register_provider_source(
36
+ :logger,
37
+ source: Logger,
38
+ group: :hanami
39
+ )
18
40
  end
19
41
  end
@@ -17,7 +17,7 @@ module Hanami
17
17
  # )
18
18
  #
19
19
  # @api public
20
- # @since x.x.x
20
+ # @since 3.0.0
21
21
  class CompositeStore
22
22
  # @api private
23
23
  Undefined = Dry::Core::Constants::Undefined
@@ -36,7 +36,7 @@ module Hanami
36
36
  # @raise [KeyError] if no store has the key and no default is given
37
37
  #
38
38
  # @api public
39
- # @since x.x.x
39
+ # @since 3.0.0
40
40
  def fetch(name, *args, &block)
41
41
  @stores.each do |store|
42
42
  value = store.fetch(name, Undefined)
data/lib/hanami/slice.rb CHANGED
@@ -394,6 +394,26 @@ module Hanami
394
394
  @slices ||= SliceRegistrar.new(self)
395
395
  end
396
396
 
397
+ # Returns an array of this slice and all of its nested slices.
398
+ #
399
+ # The nested slices are returned first (with their more specific namespaces ahead of their
400
+ # parents'), and this slice is returned last. This ordering means a class can be matched to
401
+ # its most specific slice by detecting the first slice whose namespace it belongs to. It is
402
+ # useful when you need to operate across every container, such as in test support code.
403
+ #
404
+ # @example
405
+ # Hanami.app.with_slices # => [Main::Nested::Slice, Main::Slice, Admin::Slice, MyApp::App]
406
+ #
407
+ # @return [Array<Hanami::Slice>] this slice and all of its (nested) slices, this slice last
408
+ #
409
+ # @see #slices
410
+ #
411
+ # @api public
412
+ # @since 3.0.0
413
+ def with_slices
414
+ slices.with_nested + [self]
415
+ end
416
+
397
417
  # @overload register_slice(name, &block)
398
418
  # Registers a nested slice with the given name.
399
419
  #
@@ -1009,7 +1029,7 @@ module Hanami
1009
1029
  require_relative "providers/mailers"
1010
1030
 
1011
1031
  # Only register the provider if the user hasn't provided their own.
1012
- unless container.providers[:mailers]
1032
+ if register_mailers_provider? && !container.providers[:mailers]
1013
1033
  register_provider(:mailers, namespace: true, source: Providers::Mailers)
1014
1034
  end
1015
1035
  end
@@ -1172,6 +1192,17 @@ module Hanami
1172
1192
  !config.shared_app_component_keys.include?("i18n")
1173
1193
  end
1174
1194
 
1195
+ # Ensures a mailers provider is available in every slice.
1196
+ #
1197
+ # For the app, this will always be a standalone provider. For slices, this will be a
1198
+ # standalone provider unless the slice is configured to share the app's
1199
+ # "mailers.delivery_method" component.
1200
+ def register_mailers_provider?
1201
+ return true if self == app
1202
+
1203
+ !config.shared_app_component_keys.include?("mailers.delivery_method")
1204
+ end
1205
+
1175
1206
  def register_db_provider?
1176
1207
  concrete_db_provider? ||
1177
1208
  db_config_dir? ||
@@ -57,9 +57,7 @@ module Hanami
57
57
  def slice_for(klass)
58
58
  return unless klass.name
59
59
 
60
- slices = Hanami.app.slices.with_nested + [Hanami.app]
61
-
62
- slices.detect { |slice| klass.name.start_with?("#{slice.namespace}#{MODULE_DELIMITER}") }
60
+ Hanami.app.with_slices.detect { |slice| klass.name.start_with?("#{slice.namespace}#{MODULE_DELIMITER}") }
63
61
  end
64
62
  end
65
63
 
@@ -23,7 +23,7 @@ module Hanami
23
23
  # This adapter is used for all loggers configured in Hanami apps.
24
24
  #
25
25
  # @api public
26
- # @since x.x.x
26
+ # @since 3.0.0
27
27
  class UniversalLogger
28
28
  class << self
29
29
  # Wrap a logger if needed, or return it as-is if fully compatible.
@@ -59,7 +59,7 @@ module Hanami
59
59
  end
60
60
 
61
61
  # @api public
62
- # @since x.x.x
62
+ # @since 3.0.0
63
63
  attr_reader :logger
64
64
 
65
65
  # @api private
@@ -82,7 +82,7 @@ module Hanami
82
82
  # @return [void]
83
83
  #
84
84
  # @api public
85
- # @since x.x.x
85
+ # @since 3.0.0
86
86
 
87
87
  # @!method info(message = nil, **payload, &blk)
88
88
  # Logs an info message.
@@ -92,7 +92,7 @@ module Hanami
92
92
  # @yieldreturn [Hash] additional payload data to merge
93
93
  # @return [void]
94
94
  # @api public
95
- # @since x.x.x
95
+ # @since 3.0.0
96
96
 
97
97
  # @!method warn(message = nil, **payload, &blk)
98
98
  # Logs a warning message.
@@ -103,7 +103,7 @@ module Hanami
103
103
  # @return [void]
104
104
  #
105
105
  # @api public
106
- # @since x.x.x
106
+ # @since 3.0.0
107
107
 
108
108
  # @!method error(message = nil, **payload, &blk)
109
109
  # Logs an error message.
@@ -114,7 +114,7 @@ module Hanami
114
114
  # @return [void]
115
115
  #
116
116
  # @api public
117
- # @since x.x.x
117
+ # @since 3.0.0
118
118
 
119
119
  # @!method fatal(message = nil, **payload, &blk)
120
120
  # Logs a fatal message.
@@ -125,7 +125,7 @@ module Hanami
125
125
  # @return [void]
126
126
  #
127
127
  # @api public
128
- # @since x.x.x
128
+ # @since 3.0.0
129
129
 
130
130
  # @!method unknown(message = nil, **payload, &blk)
131
131
  # Logs a message with unknown severity.
@@ -136,7 +136,7 @@ module Hanami
136
136
  # @return [void]
137
137
  #
138
138
  # @api public
139
- # @since x.x.x
139
+ # @since 3.0.0
140
140
 
141
141
  LOG_LEVEL_METHODS.each do |level|
142
142
  define_method(level) do |message = nil, **payload, &blk|
@@ -145,7 +145,7 @@ module Hanami
145
145
  end
146
146
 
147
147
  # @api public
148
- # @since x.x.x
148
+ # @since 3.0.0
149
149
  def add(severity, message = nil, progname = nil, &blk)
150
150
  # Convert severity to a level symbol if it's an integer (the standard Logger uses integers).
151
151
  level = _severity_to_level(severity)
@@ -157,11 +157,11 @@ module Hanami
157
157
  end
158
158
 
159
159
  # @api public
160
- # @since x.x.x
160
+ # @since 3.0.0
161
161
  alias_method :log, :add
162
162
 
163
163
  # @api public
164
- # @since x.x.x
164
+ # @since 3.0.0
165
165
  def tagged(*tags)
166
166
  previous_tags = _current_tags
167
167
  self._current_tags = tags
@@ -7,7 +7,7 @@ module Hanami
7
7
  # @api private
8
8
  module Version
9
9
  # @api public
10
- VERSION = "3.0.0.rc1"
10
+ VERSION = "3.0.0"
11
11
 
12
12
  # @since 0.9.0
13
13
  # @api private
@@ -59,7 +59,7 @@ module Hanami
59
59
  # @api private
60
60
  # @since 2.0.0
61
61
  def initialize(logger, env: :development)
62
- @logger = Hanami::UniversalLogger[logger]
62
+ @logger = logger
63
63
  extend(Development) if %i[development test].include?(env)
64
64
  end
65
65
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanami
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.rc1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hanakai team
@@ -173,16 +173,16 @@ dependencies:
173
173
  name: hanami-utils
174
174
  requirement: !ruby/object:Gem::Requirement
175
175
  requirements:
176
- - - ">="
176
+ - - "~>"
177
177
  - !ruby/object:Gem::Version
178
- version: 2.3.0
178
+ version: 3.0.0
179
179
  type: :runtime
180
180
  prerelease: false
181
181
  version_requirements: !ruby/object:Gem::Requirement
182
182
  requirements:
183
- - - ">="
183
+ - - "~>"
184
184
  - !ruby/object:Gem::Version
185
- version: 2.3.0
185
+ version: 3.0.0
186
186
  - !ruby/object:Gem::Dependency
187
187
  name: json
188
188
  requirement: !ruby/object:Gem::Requirement
@@ -302,6 +302,8 @@ files:
302
302
  - lib/hanami/errors.rb
303
303
  - lib/hanami/extensions.rb
304
304
  - lib/hanami/extensions/action.rb
305
+ - lib/hanami/extensions/action/i18n_helper.rb
306
+ - lib/hanami/extensions/action/name_inferrer.rb
305
307
  - lib/hanami/extensions/action/slice_configured_action.rb
306
308
  - lib/hanami/extensions/db/repo.rb
307
309
  - lib/hanami/extensions/mailer.rb