route_translator 15.0.0 → 15.2.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: 5a393716835c2c542d7f158782dcf1dcbaf163185bb4a1f85ff1be5507accbbf
4
- data.tar.gz: 9ea86dcbebec6a9cea19b280da5a821cfccd6951e5d4c5b79ca43582053c8869
3
+ metadata.gz: 13e7ca36c52b904acf706a63b2cc0b352ed0206010b69dbf65eba016ab2fefdb
4
+ data.tar.gz: 6721c17d101b7528a9a603ff196c0fabdc49d4b0ca54a42e3b842213ef585ead
5
5
  SHA512:
6
- metadata.gz: b1fe1869d56070ea8de79618f8c6cd8fe5e6360b6be50637ac94ac9a13db13d6ce44f807a4745b4973cef5126049b1d7314d2c46401d7aa6af1d081cb63c9f84
7
- data.tar.gz: d91df79e0dabed0f438757b364e467d85c1d1691c324e9098b5c1446805e6735a594e5d425d48345c47dcdb01f6438a30358a09a52195de93a46c425b5ccc944
6
+ metadata.gz: 4837bc35c697c191a1219d42c5e8a6a05241aaa2cce847dfee6e000acfb460335b6007c1d499c1a049ece53cbff466a3647385e1a5b85c8ff9e6b65f7da98cbc
7
+ data.tar.gz: 2fe7754133ec168a03208316f98ea24696700dc03766d9b86bbbee25785e77f85d0445f82fedebc45fd43a8b51f4fc28bbae57d817d0f1bb206a0bc0b82fcb9b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 15.2.0 / 2025-07-06
4
+
5
+ * [FEATURE] Rails 8.1 edge support ([#344](https://github.com/enriclluelles/route_translator/pull/344))
6
+
7
+ ## 15.1.0 / 2025-06-15
8
+
9
+ * [BUGFIX] Ensure `@localized` is always reset after yield ([#333](https://github.com/enriclluelles/route_translator/issues/333))
10
+ * [ENHANCEMENT] Optimize host locale detection ([#337](https://github.com/enriclluelles/route_translator/pull/337))
11
+ * [ENHANCEMENT] Add `locale_from_request` method ([#340](https://github.com/enriclluelles/route_translator/pull/340))
12
+
3
13
  ## 15.0.0 / 2025-06-12
4
14
 
5
15
  * [FEATURE] Drop Ruby < 3.1 support
data/README.md CHANGED
@@ -385,6 +385,39 @@ scope ':country/:locale' do
385
385
  end
386
386
  ```
387
387
 
388
+ ## Using RouteTranslator with Devise
389
+
390
+ When using RouteTranslator with Devise, you may notice that some authentication-related flash messages (such as errors after failed logins) are shown in the wrong language. This happens because Devise builds some messages in middleware, after your controller’s actions have completed. If you use RouteTranslator’s `around_action` (or legacy `around_filter`) to set the locale, the locale is reset after the controller yields, and middleware like Devise will see the default locale.
391
+
392
+ This only affects you if Devise is mounted inside a localized block (for example, `localized { devise_for :users }`). If you do not use localized Devise routes, you do not need to change anything.
393
+
394
+ To ensure Devise’s middleware uses the correct locale, some users suggest replacing RouteTranslator’s `around_action` with a `before_action`. However, this is discouraged, as it can leave your application in the wrong locale after the request and may cause subtle bugs.
395
+
396
+ A better approach is to explicitly set the locale for Devise’s failure responses. Starting from Devise version 4.9.4, you can customize how Devise determines the locale for error messages, thanks to the ability to override the `i18n_locale` method in the failure app. This allows you to set the locale for Devise’s middleware without changing RouteTranslator’s recommended usage.
397
+
398
+ Here’s an example:
399
+
400
+ ```rb
401
+ # config/initializers/devise.rb
402
+
403
+ Devise.setup do |config|
404
+ # ...
405
+
406
+ ActiveSupport.on_load(:devise_failure_app) do
407
+ def i18n_locale
408
+ RouteTranslator.locale_from_request(request)
409
+ end
410
+ end
411
+ end
412
+ ```
413
+
414
+ ### Summary
415
+
416
+ - You only need this workaround if Devise is mounted in a localized block.
417
+ - Do not replace RouteTranslator’s `around_action` with a `before_action`.
418
+ - Instead, set the locale for Devise failure responses as shown above.
419
+ - The solution above requires Devise version 4.9.4 or higher, which allows customizing the `i18n_locale` method in the failure app.
420
+
388
421
  ## Testing
389
422
  Testing your controllers with routes-translator is easy, just add a locale parameter as `String` for your localized routes. Otherwise, an `ActionController::UrlGenerationError` will raise.
390
423
 
@@ -9,7 +9,8 @@ module RouteTranslator
9
9
  private
10
10
 
11
11
  def set_locale_from_url
12
- locale_from_url = RouteTranslator.locale_from_params(params) || RouteTranslator::Host.locale_from_host(request.host)
12
+ locale_from_url = RouteTranslator.locale_from_request(request)
13
+
13
14
  if locale_from_url
14
15
  old_locale = I18n.locale
15
16
  I18n.locale = locale_from_url
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ module Routing
5
+ class Mapper
6
+ def localized
7
+ @localized = true
8
+ yield
9
+ ensure
10
+ @localized = false
11
+ end
12
+
13
+ # rubocop:disable Lint/UnderscorePrefixedVariableName, Metrics/PerceivedComplexity
14
+ def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
15
+ return super unless @localized
16
+
17
+ path = path_for_action(action, _path)
18
+ raise ArgumentError, 'path is required' if path.blank?
19
+
20
+ action = action.to_s
21
+
22
+ default_action = options.delete(:action) || @scope[:action]
23
+
24
+ if %r{^[\w\-\/]+$}.match?(action)
25
+ default_action ||= action.tr('-', '_') unless action.include?('/')
26
+ else
27
+ action = nil
28
+ end
29
+
30
+ as = if options.fetch(:as, true)
31
+ name_for_action(options.delete(:as), action)
32
+ else
33
+ options.delete(:as)
34
+ end
35
+
36
+ path = Mapping.normalize_path URI::DEFAULT_PARSER.escape(path), formatted
37
+ ast = Journey::Parser.parse path
38
+
39
+ mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
40
+ @set.add_localized_route(mapping, as, anchor, @scope, path, controller, default_action, to, via, formatted, options_constraints, options)
41
+ end
42
+ # rubocop:enable Lint/UnderscorePrefixedVariableName, Metrics/PerceivedComplexity
43
+
44
+ private
45
+
46
+ def define_generate_prefix(app, name)
47
+ return super unless @localized
48
+
49
+ RouteTranslator::Translator.available_locales.each do |locale|
50
+ super(app, "#{name}_#{locale.to_s.underscore}")
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ module Routing
5
+ class RouteSet
6
+ def add_localized_route(mapping, name, anchor, scope, path, controller, default_action, to, via, formatted, options_constraints, options)
7
+ route = RouteTranslator::Route.new(self, path, name, options_constraints, options, mapping)
8
+
9
+ RouteTranslator::Translator.translations_for(route) do |locale, translated_name, translated_path, translated_options_constraints, translated_options|
10
+ translated_path_ast = ::ActionDispatch::Journey::Parser.parse(translated_path)
11
+ translated_mapping = translate_mapping(locale, self, translated_options, translated_path_ast, scope, controller, default_action, to, formatted, via, translated_options_constraints, anchor)
12
+
13
+ add_route translated_mapping, translated_name
14
+ end
15
+
16
+ if RouteTranslator.config.generate_unnamed_unlocalized_routes
17
+ add_route mapping, nil
18
+ elsif RouteTranslator.config.generate_unlocalized_routes
19
+ add_route mapping, name
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def translate_mapping(locale, route_set, translated_options, translated_path_ast, scope, controller, default_action, to, formatted, via, translated_options_constraints, anchor)
26
+ scope_params = {
27
+ blocks: (scope[:blocks] || []).dup,
28
+ constraints: scope[:constraints] || {},
29
+ defaults: scope[:defaults] || {},
30
+ module: scope[:module],
31
+ options: scope[:options] ? scope[:options].merge(translated_options) : translated_options
32
+ }
33
+
34
+ if RouteTranslator.config.host_locales.present?
35
+ scope_params[:blocks].push RouteTranslator::Host.lambdas_for_locale(locale)
36
+ end
37
+
38
+ ::ActionDispatch::Routing::Mapper::Mapping.build scope_params, route_set, translated_path_ast, controller, default_action, to, via, formatted, translated_options_constraints, anchor, translated_options
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,18 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'action_dispatch'
4
-
5
3
  module ActionDispatch
6
4
  module Routing
7
5
  class Mapper
8
6
  def localized
9
7
  @localized = true
10
8
  yield
9
+ ensure
11
10
  @localized = false
12
11
  end
13
12
 
14
13
  # rubocop:disable Lint/UnderscorePrefixedVariableName, Metrics/PerceivedComplexity
15
- def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
14
+ def add_route(action, controller, as, options_action, _path, to, via, formatted, anchor, options_constraints, internal, options_mapping) # :nodoc:
16
15
  return super unless @localized
17
16
 
18
17
  path = path_for_action(action, _path)
@@ -20,25 +19,20 @@ module ActionDispatch
20
19
 
21
20
  action = action.to_s
22
21
 
23
- default_action = options.delete(:action) || @scope[:action]
22
+ default_action = options_action || @scope[:action]
24
23
 
25
- if %r{^[\w\-\/]+$}.match?(action)
24
+ if %r{^[\w\-/]+$}.match?(action)
26
25
  default_action ||= action.tr('-', '_') unless action.include?('/')
27
26
  else
28
27
  action = nil
29
28
  end
30
29
 
31
- as = if options.fetch(:as, true)
32
- name_for_action(options.delete(:as), action)
33
- else
34
- options.delete(:as)
35
- end
36
-
37
- path = Mapping.normalize_path URI::DEFAULT_PARSER.escape(path), formatted
38
- ast = Journey::Parser.parse path
30
+ as = name_for_action(as, action) if as
31
+ path = Mapping.normalize_path URI::RFC2396_PARSER.escape(path), formatted
32
+ ast = Journey::Parser.parse path
39
33
 
40
- mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
41
- @set.add_localized_route(mapping, as, anchor, @scope, path, controller, default_action, to, via, formatted, options_constraints, options)
34
+ mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, internal, options_mapping)
35
+ @set.add_localized_route(mapping, as, anchor, @scope, path, controller, default_action, to, via, formatted, options_constraints, internal, options_mapping)
42
36
  end
43
37
  # rubocop:enable Lint/UnderscorePrefixedVariableName, Metrics/PerceivedComplexity
44
38
 
@@ -1,16 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'action_dispatch'
4
-
5
3
  module ActionDispatch
6
4
  module Routing
7
5
  class RouteSet
8
- def add_localized_route(mapping, name, anchor, scope, path, controller, default_action, to, via, formatted, options_constraints, options)
9
- route = RouteTranslator::Route.new(self, path, name, options_constraints, options, mapping)
6
+ def add_localized_route(mapping, name, anchor, scope, path, controller, default_action, to, via, formatted, options_constraints, internal, options_mapping)
7
+ route = RouteTranslator::Route.new(self, path, name, options_constraints, options_mapping, mapping)
10
8
 
11
9
  RouteTranslator::Translator.translations_for(route) do |locale, translated_name, translated_path, translated_options_constraints, translated_options|
12
10
  translated_path_ast = ::ActionDispatch::Journey::Parser.parse(translated_path)
13
- translated_mapping = translate_mapping(locale, self, translated_options, translated_path_ast, scope, controller, default_action, to, formatted, via, translated_options_constraints, anchor)
11
+ translated_mapping = translate_mapping(locale, self, translated_options, translated_path_ast, scope, controller, default_action, to, formatted, via, translated_options_constraints, anchor, internal)
14
12
 
15
13
  add_route translated_mapping, translated_name
16
14
  end
@@ -24,7 +22,7 @@ module ActionDispatch
24
22
 
25
23
  private
26
24
 
27
- def translate_mapping(locale, route_set, translated_options, translated_path_ast, scope, controller, default_action, to, formatted, via, translated_options_constraints, anchor)
25
+ def translate_mapping(locale, route_set, translated_options, translated_path_ast, scope, controller, default_action, to, formatted, via, translated_options_constraints, anchor, internal)
28
26
  scope_params = {
29
27
  blocks: (scope[:blocks] || []).dup,
30
28
  constraints: scope[:constraints] || {},
@@ -37,7 +35,7 @@ module ActionDispatch
37
35
  scope_params[:blocks].push RouteTranslator::Host.lambdas_for_locale(locale)
38
36
  end
39
37
 
40
- ::ActionDispatch::Routing::Mapper::Mapping.build scope_params, route_set, translated_path_ast, controller, default_action, to, via, formatted, translated_options_constraints, anchor, translated_options
38
+ ::ActionDispatch::Routing::Mapper::Mapping.build scope_params, route_set, translated_path_ast, controller, default_action, to, via, formatted, translated_options_constraints, anchor, internal, translated_options
41
39
  end
42
40
  end
43
41
  end
@@ -1,5 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'extensions/mapper'
4
- require_relative 'extensions/route_set'
3
+ require 'action_dispatch'
4
+
5
+ # TODO: Remove `else` branch when dropping Rails < 8.1 support
6
+ if ActionDispatch::Routing::Mapper.instance_method(:add_route).arity == 12
7
+ require_relative 'extensions/mapper'
8
+ require_relative 'extensions/route_set'
9
+ else
10
+ require_relative 'extensions/legacy/mapper'
11
+ require_relative 'extensions/legacy/route_set'
12
+ end
13
+
5
14
  require_relative 'extensions/action_controller'
@@ -18,11 +18,11 @@ module RouteTranslator
18
18
  module_function
19
19
 
20
20
  def locale_from_host(host)
21
- locales = RouteTranslator.config.host_locales.each_with_object([]) do |(pattern, locale), result|
22
- result << locale.to_sym if host&.match?(regex_for(pattern))
23
- end
24
- locales &= I18n.available_locales
25
- locales.first&.to_sym
21
+ available_locales = I18n.available_locales
22
+
23
+ RouteTranslator.config.host_locales.find do |pattern, locale|
24
+ host&.match?(regex_for(pattern)) && available_locales.include?(locale&.to_sym)
25
+ end&.last&.to_sym
26
26
  end
27
27
 
28
28
  def lambdas_for_locale(locale)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RouteTranslator
4
- VERSION = '15.0.0'
4
+ VERSION = '15.2.0'
5
5
  end
@@ -81,6 +81,10 @@ module RouteTranslator
81
81
  locale if I18n.available_locales.include?(locale)
82
82
  end
83
83
 
84
+ def locale_from_request(request)
85
+ locale_from_params(request.params) || Host.locale_from_host(request.host)
86
+ end
87
+
84
88
  def deprecator
85
89
  @deprecator ||= ActiveSupport::Deprecation.new(RouteTranslator::VERSION, 'RouteTranslator')
86
90
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: route_translator
3
3
  version: !ruby/object:Gem::Version
4
- version: 15.0.0
4
+ version: 15.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geremia Taglialatela
@@ -56,6 +56,8 @@ files:
56
56
  - lib/route_translator.rb
57
57
  - lib/route_translator/extensions.rb
58
58
  - lib/route_translator/extensions/action_controller.rb
59
+ - lib/route_translator/extensions/legacy/mapper.rb
60
+ - lib/route_translator/extensions/legacy/route_set.rb
59
61
  - lib/route_translator/extensions/mapper.rb
60
62
  - lib/route_translator/extensions/route_set.rb
61
63
  - lib/route_translator/extensions/test_case.rb
@@ -88,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
90
  - !ruby/object:Gem::Version
89
91
  version: '0'
90
92
  requirements: []
91
- rubygems_version: 3.6.7
93
+ rubygems_version: 3.6.9
92
94
  specification_version: 4
93
95
  summary: Translate your Rails routes in a simple manner
94
96
  test_files: []