view_component 2.25.1 → 2.29.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.
Potentially problematic release.
This version of view_component might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -0
- data/README.md +0 -8
- data/lib/rails/generators/erb/component_generator.rb +7 -7
- data/lib/rails/generators/haml/component_generator.rb +3 -1
- data/lib/rails/generators/preview/component_generator.rb +5 -1
- data/lib/rails/generators/slim/component_generator.rb +3 -1
- data/lib/view_component.rb +1 -0
- data/lib/view_component/base.rb +86 -7
- data/lib/view_component/collection.rb +0 -1
- data/lib/view_component/compiler.rb +12 -66
- data/lib/view_component/engine.rb +1 -1
- data/lib/view_component/slot_v2.rb +15 -8
- data/lib/view_component/slotable.rb +8 -2
- data/lib/view_component/slotable_v2.rb +9 -1
- data/lib/view_component/test_helpers.rb +15 -1
- data/lib/view_component/translatable.rb +81 -0
- data/lib/view_component/version.rb +4 -2
- metadata +18 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8c387807061db7889dbc2e49ccb75a15a2cacd416048a6681b046eb321fb27b
|
4
|
+
data.tar.gz: fbddc58ff50b3ddaf615f773a681a7a8b7bef3972581dad8b87bf8425efff275
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63a070fc0a5001b888dbd57a2a9fb0660b351a05b522d9a832efdd2a60f981d218eda2f500a50d01ca6a8c5ada4cccb61f2837e7b956215e89528d9454d3fd39
|
7
|
+
data.tar.gz: 8176f0427272a8305a520eeaa72ae58be2d9df42282a981f376eb69ac0543017f98d737ecb09f6fa6fc8cacf85adbbf82fbefe337da22432448ab361f35ae447
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,75 @@
|
|
2
2
|
|
3
3
|
## main
|
4
4
|
|
5
|
+
## 2.29.0
|
6
|
+
|
7
|
+
* Allow Slot lambdas to share data from the parent component and allow chaining on the returned component.
|
8
|
+
|
9
|
+
*Sjors Baltus, Blake Williams*
|
10
|
+
|
11
|
+
* Experimental: Add `ViewComponent::Translatable`
|
12
|
+
* `t` and `translate` now will look first into the sidecar YAML translations file.
|
13
|
+
* `helpers.t` and `I18n.t` still reference the global Rails translation files.
|
14
|
+
* `l` and `localize` will still reference the global Rails translation files.
|
15
|
+
|
16
|
+
*Elia Schito*
|
17
|
+
|
18
|
+
* Fix rendering output of pass through slots when using HAML.
|
19
|
+
|
20
|
+
*Alex Robbin, Blake Williams*
|
21
|
+
|
22
|
+
* Experimental: call `._sidecar_files` to fetch the sidecar files for a given list of extensions, e.g. passing `["yml", "yaml"]`.
|
23
|
+
|
24
|
+
*Elia Schito*
|
25
|
+
|
26
|
+
* Fix bug where a single `jbuilder` template matched multiple template handlers.
|
27
|
+
|
28
|
+
*Niels Slot*
|
29
|
+
|
30
|
+
## 2.28.0
|
31
|
+
|
32
|
+
* Include SlotableV2 by default in Base. **Note:** It's no longer necessary to include `ViewComponent::SlotableV2` to use Slots.
|
33
|
+
|
34
|
+
*Joel Hawksley*
|
35
|
+
|
36
|
+
* Prepend Preview routes instead of appending, accounting for cases where host application has catchall route.
|
37
|
+
|
38
|
+
*Joel Hawksley*
|
39
|
+
|
40
|
+
* Fix bug where blocks passed to lambda slots will render incorrectly in certain situations.
|
41
|
+
|
42
|
+
*Blake Williams*
|
43
|
+
|
44
|
+
## 2.27.0
|
45
|
+
|
46
|
+
* Allow customization of the controller used in component tests.
|
47
|
+
|
48
|
+
*Alex Robbin*
|
49
|
+
|
50
|
+
* Generate preview at overridden path if one exists when using `--preview` flag.
|
51
|
+
|
52
|
+
*Nishiki Liu*
|
53
|
+
|
54
|
+
## 2.26.1
|
55
|
+
|
56
|
+
* Fix bug that raises when trying to use a collection before the component has been compiled.
|
57
|
+
|
58
|
+
*Blake Williams*
|
59
|
+
|
60
|
+
## 2.26.0
|
61
|
+
|
62
|
+
* Lazily evaluate component `content` in `render?`, preventing the `content` block from being evaluated when `render?` returns false.
|
63
|
+
|
64
|
+
*Blake Williams*
|
65
|
+
|
66
|
+
* Do not generate template when using `--inline` flag.
|
67
|
+
|
68
|
+
*Hans Lemuet*
|
69
|
+
|
70
|
+
* Add `--inline` option to the Haml and Slim generators
|
71
|
+
|
72
|
+
*Hans Lemuet*
|
73
|
+
|
5
74
|
## 2.25.1
|
6
75
|
|
7
76
|
* Experimental: call `._after_compile` class method after a component is compiled.
|
data/README.md
CHANGED
@@ -6,14 +6,6 @@ A framework for building reusable, testable & encapsulated view components in Ru
|
|
6
6
|
|
7
7
|
See [viewcomponent.org](https://viewcomponent.org/) for documentation.
|
8
8
|
|
9
|
-
## Installation
|
10
|
-
|
11
|
-
In `Gemfile`, add:
|
12
|
-
|
13
|
-
```ruby
|
14
|
-
gem "view_component", require: "view_component/engine"
|
15
|
-
```
|
16
|
-
|
17
9
|
## Contributing
|
18
10
|
|
19
11
|
This project is intended to be a safe, welcoming space for collaboration. Contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./CONTRIBUTING.md) as well.
|
@@ -10,18 +10,18 @@ module Erb
|
|
10
10
|
class_option :inline, type: :boolean, default: false
|
11
11
|
|
12
12
|
def copy_view_file
|
13
|
-
|
13
|
+
unless options["inline"]
|
14
|
+
template "component.html.erb", destination
|
15
|
+
end
|
14
16
|
end
|
15
17
|
|
16
18
|
private
|
17
19
|
|
18
20
|
def destination
|
19
|
-
if
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
File.join("app/components", class_path, "#{file_name}_component.html.erb")
|
24
|
-
end
|
21
|
+
if options["sidecar"]
|
22
|
+
File.join("app/components", class_path, "#{file_name}_component", "#{file_name}_component.html.erb")
|
23
|
+
else
|
24
|
+
File.join("app/components", class_path, "#{file_name}_component.html.erb")
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -8,7 +8,11 @@ module Preview
|
|
8
8
|
check_class_collision suffix: "ComponentPreview"
|
9
9
|
|
10
10
|
def create_preview_file
|
11
|
-
|
11
|
+
preview_paths = Rails.application.config.view_component.preview_paths
|
12
|
+
return if preview_paths.count > 1
|
13
|
+
|
14
|
+
path_prefix = preview_paths.one? ? preview_paths.first : "test/components/previews"
|
15
|
+
template "component_preview.rb", File.join(path_prefix, class_path, "#{file_name}_component_preview.rb")
|
12
16
|
end
|
13
17
|
|
14
18
|
private
|
data/lib/view_component.rb
CHANGED
data/lib/view_component/base.rb
CHANGED
@@ -12,6 +12,7 @@ module ViewComponent
|
|
12
12
|
class Base < ActionView::Base
|
13
13
|
include ActiveSupport::Configurable
|
14
14
|
include ViewComponent::Previewable
|
15
|
+
include ViewComponent::SlotableV2
|
15
16
|
|
16
17
|
ViewContextCalledBeforeRenderError = Class.new(StandardError)
|
17
18
|
|
@@ -78,8 +79,8 @@ module ViewComponent
|
|
78
79
|
old_current_template = @current_template
|
79
80
|
@current_template = self
|
80
81
|
|
81
|
-
|
82
|
-
@
|
82
|
+
@_content_evaluated = false
|
83
|
+
@_render_in_block = block
|
83
84
|
|
84
85
|
before_render
|
85
86
|
|
@@ -130,7 +131,7 @@ module ViewComponent
|
|
130
131
|
@helpers ||= controller.view_context
|
131
132
|
end
|
132
133
|
|
133
|
-
# Exposes .
|
134
|
+
# Exposes .virtual_path as an instance method
|
134
135
|
def virtual_path
|
135
136
|
self.class.virtual_path
|
136
137
|
end
|
@@ -177,11 +178,25 @@ module ViewComponent
|
|
177
178
|
@request ||= controller.request
|
178
179
|
end
|
179
180
|
|
180
|
-
attr_reader :
|
181
|
+
attr_reader :view_context
|
182
|
+
|
183
|
+
def content
|
184
|
+
return @_content if defined?(@_content)
|
185
|
+
@_content_evaluated = true
|
186
|
+
|
187
|
+
@_content = if @view_context && @_render_in_block
|
188
|
+
view_context.capture(self, &@_render_in_block)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def content_evaluated?
|
193
|
+
@_content_evaluated
|
194
|
+
end
|
181
195
|
|
182
196
|
# The controller used for testing components.
|
183
|
-
# Defaults to ApplicationController
|
184
|
-
#
|
197
|
+
# Defaults to ApplicationController, but can be configured
|
198
|
+
# on a per-test basis using `with_controller_class`.
|
199
|
+
# This should be set early in the initialization process and should be a string.
|
185
200
|
mattr_accessor :test_controller
|
186
201
|
@@test_controller = "ApplicationController"
|
187
202
|
|
@@ -191,6 +206,47 @@ module ViewComponent
|
|
191
206
|
class << self
|
192
207
|
attr_accessor :source_location, :virtual_path
|
193
208
|
|
209
|
+
# EXPERIMENTAL: This API is experimental and may be removed at any time.
|
210
|
+
# Find sidecar files for the given extensions.
|
211
|
+
#
|
212
|
+
# The provided array of extensions is expected to contain
|
213
|
+
# strings starting without the "dot", example: `["erb", "haml"]`.
|
214
|
+
#
|
215
|
+
# For example, one might collect sidecar CSS files that need to be compiled.
|
216
|
+
def _sidecar_files(extensions)
|
217
|
+
return [] unless source_location
|
218
|
+
|
219
|
+
extensions = extensions.join(",")
|
220
|
+
|
221
|
+
# view files in a directory named like the component
|
222
|
+
directory = File.dirname(source_location)
|
223
|
+
filename = File.basename(source_location, ".rb")
|
224
|
+
component_name = name.demodulize.underscore
|
225
|
+
|
226
|
+
# Add support for nested components defined in the same file.
|
227
|
+
#
|
228
|
+
# e.g.
|
229
|
+
#
|
230
|
+
# class MyComponent < ViewComponent::Base
|
231
|
+
# class MyOtherComponent < ViewComponent::Base
|
232
|
+
# end
|
233
|
+
# end
|
234
|
+
#
|
235
|
+
# Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb`
|
236
|
+
nested_component_files = if name.include?("::") && component_name != filename
|
237
|
+
Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"]
|
238
|
+
else
|
239
|
+
[]
|
240
|
+
end
|
241
|
+
|
242
|
+
# view files in the same directory as the component
|
243
|
+
sidecar_files = Dir["#{directory}/#{component_name}.*{#{extensions}}"]
|
244
|
+
|
245
|
+
sidecar_directory_files = Dir["#{directory}/#{component_name}/#{filename}.*{#{extensions}}"]
|
246
|
+
|
247
|
+
(sidecar_files - [source_location] + sidecar_directory_files + nested_component_files).uniq
|
248
|
+
end
|
249
|
+
|
194
250
|
# Render a component collection.
|
195
251
|
def with_collection(collection, **args)
|
196
252
|
Collection.new(self, collection, **args)
|
@@ -256,7 +312,14 @@ module ViewComponent
|
|
256
312
|
if areas.include?(:content)
|
257
313
|
raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
|
258
314
|
end
|
259
|
-
|
315
|
+
|
316
|
+
areas.each do |area|
|
317
|
+
define_method area.to_sym do
|
318
|
+
content unless content_evaluated? # ensure content is loaded so content_areas will be defined
|
319
|
+
instance_variable_get(:"@#{area}") if instance_variable_defined?(:"@#{area}")
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
260
323
|
self.content_areas = areas
|
261
324
|
end
|
262
325
|
|
@@ -304,6 +367,22 @@ module ViewComponent
|
|
304
367
|
)
|
305
368
|
end
|
306
369
|
|
370
|
+
def collection_parameter
|
371
|
+
if provided_collection_parameter
|
372
|
+
provided_collection_parameter
|
373
|
+
else
|
374
|
+
name && name.demodulize.underscore.chomp("_component").to_sym
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def collection_counter_parameter
|
379
|
+
"#{collection_parameter}_counter".to_sym
|
380
|
+
end
|
381
|
+
|
382
|
+
def counter_argument_present?
|
383
|
+
instance_method(:initialize).parameters.map(&:second).include?(collection_counter_parameter)
|
384
|
+
end
|
385
|
+
|
307
386
|
private
|
308
387
|
|
309
388
|
def initialize_parameter_names
|
@@ -10,7 +10,6 @@ module ViewComponent
|
|
10
10
|
def render_in(view_context, &block)
|
11
11
|
iterator = ActionView::PartialIteration.new(@collection.size)
|
12
12
|
|
13
|
-
component.compile(raise_errors: true)
|
14
13
|
component.validate_collection_parameter!(validate_default: true)
|
15
14
|
|
16
15
|
@collection.map do |item|
|
@@ -24,28 +24,6 @@ module ViewComponent
|
|
24
24
|
)
|
25
25
|
end
|
26
26
|
|
27
|
-
# Remove any existing singleton methods,
|
28
|
-
# as Ruby warns when redefining a method.
|
29
|
-
component_class.remove_possible_singleton_method(:collection_parameter)
|
30
|
-
component_class.remove_possible_singleton_method(:collection_counter_parameter)
|
31
|
-
component_class.remove_possible_singleton_method(:counter_argument_present?)
|
32
|
-
|
33
|
-
component_class.define_singleton_method(:collection_parameter) do
|
34
|
-
if provided_collection_parameter
|
35
|
-
provided_collection_parameter
|
36
|
-
else
|
37
|
-
name.demodulize.underscore.chomp("_component").to_sym
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
component_class.define_singleton_method(:collection_counter_parameter) do
|
42
|
-
"#{collection_parameter}_counter".to_sym
|
43
|
-
end
|
44
|
-
|
45
|
-
component_class.define_singleton_method(:counter_argument_present?) do
|
46
|
-
instance_method(:initialize).parameters.map(&:second).include?(collection_counter_parameter)
|
47
|
-
end
|
48
|
-
|
49
27
|
if raise_errors
|
50
28
|
component_class.validate_initialization_parameters!
|
51
29
|
component_class.validate_collection_parameter!
|
@@ -83,7 +61,7 @@ module ViewComponent
|
|
83
61
|
"elsif variant.to_sym == :#{variant}\n #{call_method_name(variant)}"
|
84
62
|
end.join("\n")
|
85
63
|
|
86
|
-
component_class.class_eval <<-RUBY
|
64
|
+
component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
87
65
|
def render_template_for(variant = nil)
|
88
66
|
if variant.nil?
|
89
67
|
call
|
@@ -136,50 +114,18 @@ module ViewComponent
|
|
136
114
|
end
|
137
115
|
|
138
116
|
def templates
|
139
|
-
@templates ||=
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
def matching_views_in_source_location
|
151
|
-
source_location = component_class.source_location
|
152
|
-
return [] unless source_location
|
153
|
-
|
154
|
-
extensions = ActionView::Template.template_handler_extensions.join(",")
|
155
|
-
|
156
|
-
# view files in a directory named like the component
|
157
|
-
directory = File.dirname(source_location)
|
158
|
-
filename = File.basename(source_location, ".rb")
|
159
|
-
component_name = component_class.name.demodulize.underscore
|
160
|
-
|
161
|
-
# Add support for nested components defined in the same file.
|
162
|
-
#
|
163
|
-
# e.g.
|
164
|
-
#
|
165
|
-
# class MyComponent < ViewComponent::Base
|
166
|
-
# class MyOtherComponent < ViewComponent::Base
|
167
|
-
# end
|
168
|
-
# end
|
169
|
-
#
|
170
|
-
# Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb`
|
171
|
-
nested_component_files = if component_class.name.include?("::") && component_name != filename
|
172
|
-
Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"]
|
173
|
-
else
|
174
|
-
[]
|
117
|
+
@templates ||= begin
|
118
|
+
extensions = ActionView::Template.template_handler_extensions
|
119
|
+
|
120
|
+
component_class._sidecar_files(extensions).each_with_object([]) do |path, memo|
|
121
|
+
pieces = File.basename(path).split(".")
|
122
|
+
memo << {
|
123
|
+
path: path,
|
124
|
+
variant: pieces.second.split("+").second&.to_sym,
|
125
|
+
handler: pieces.last
|
126
|
+
}
|
127
|
+
end
|
175
128
|
end
|
176
|
-
|
177
|
-
# view files in the same directory as the component
|
178
|
-
sidecar_files = Dir["#{directory}/#{component_name}.*{#{extensions}}"]
|
179
|
-
|
180
|
-
sidecar_directory_files = Dir["#{directory}/#{component_name}/#{filename}.*{#{extensions}}"]
|
181
|
-
|
182
|
-
(sidecar_files - [source_location] + sidecar_directory_files + nested_component_files)
|
183
129
|
end
|
184
130
|
|
185
131
|
def inline_calls
|
@@ -90,7 +90,7 @@ module ViewComponent
|
|
90
90
|
options = app.config.view_component
|
91
91
|
|
92
92
|
if options.show_previews
|
93
|
-
app.routes.
|
93
|
+
app.routes.prepend do
|
94
94
|
preview_controller = options.preview_controller.sub(/Controller$/, "").underscore
|
95
95
|
|
96
96
|
get options.preview_route, to: "#{preview_controller}#index", as: :preview_view_components, internal: true
|
@@ -25,15 +25,22 @@ module ViewComponent
|
|
25
25
|
return @content if defined?(@content)
|
26
26
|
|
27
27
|
view_context = @parent.send(:view_context)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
28
|
+
|
29
|
+
@content = if defined?(@_component_instance)
|
30
|
+
# render_in is faster than `parent.render`
|
31
|
+
if defined?(@_content_block)
|
32
|
+
view_context.capture do
|
33
|
+
@_component_instance.render_in(view_context, &@_content_block)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
view_context.capture do
|
37
|
+
@_component_instance.render_in(view_context)
|
38
|
+
end
|
36
39
|
end
|
40
|
+
elsif defined?(@_content)
|
41
|
+
@_content
|
42
|
+
elsif defined?(@_content_block)
|
43
|
+
view_context.capture(&@_content_block)
|
37
44
|
end
|
38
45
|
|
39
46
|
@content
|
@@ -49,13 +49,19 @@ module ViewComponent
|
|
49
49
|
|
50
50
|
# If the slot is a collection, define an accesor that defaults to an empty array
|
51
51
|
if collection
|
52
|
-
class_eval <<-RUBY
|
52
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
53
53
|
def #{accessor_name}
|
54
|
+
content unless content_evaluated? # ensure content is loaded so slots will be defined
|
54
55
|
#{instance_variable_name} ||= []
|
55
56
|
end
|
56
57
|
RUBY
|
57
58
|
else
|
58
|
-
|
59
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
60
|
+
def #{accessor_name}
|
61
|
+
content unless content_evaluated? # ensure content is loaded so slots will be defined
|
62
|
+
#{instance_variable_name} if defined?(#{instance_variable_name})
|
63
|
+
end
|
64
|
+
RUBY
|
59
65
|
end
|
60
66
|
|
61
67
|
# Default class_name to ViewComponent::Slot
|
@@ -183,6 +183,8 @@ module ViewComponent
|
|
183
183
|
end
|
184
184
|
|
185
185
|
def get_slot(slot_name)
|
186
|
+
content unless content_evaluated? # ensure content is loaded so slots will be defined
|
187
|
+
|
186
188
|
slot = self.class.registered_slots[slot_name]
|
187
189
|
@_set_slots ||= {}
|
188
190
|
|
@@ -224,7 +226,13 @@ module ViewComponent
|
|
224
226
|
# Use `bind(self)` to ensure lambda is executed in the context of the
|
225
227
|
# current component. This is necessary to allow the lambda to access helper
|
226
228
|
# methods like `content_tag` as well as parent component state.
|
227
|
-
renderable_value =
|
229
|
+
renderable_value = if block_given?
|
230
|
+
slot_definition[:renderable_function].bind(self).call(*args, **kwargs) do |*args, **kwargs|
|
231
|
+
view_context.capture(*args, **kwargs, &block)
|
232
|
+
end
|
233
|
+
else
|
234
|
+
slot_definition[:renderable_function].bind(self).call(*args, **kwargs)
|
235
|
+
end
|
228
236
|
|
229
237
|
# Function calls can return components, so if it's a component handle it specially
|
230
238
|
if renderable_value.respond_to?(:render_in)
|
@@ -35,7 +35,7 @@ module ViewComponent
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def controller
|
38
|
-
@controller ||= Base.test_controller.constantize
|
38
|
+
@controller ||= build_controller(Base.test_controller.constantize)
|
39
39
|
end
|
40
40
|
|
41
41
|
def request
|
@@ -47,7 +47,21 @@ module ViewComponent
|
|
47
47
|
|
48
48
|
controller.view_context.lookup_context.variants = variant
|
49
49
|
yield
|
50
|
+
ensure
|
50
51
|
controller.view_context.lookup_context.variants = old_variants
|
51
52
|
end
|
53
|
+
|
54
|
+
def with_controller_class(klass)
|
55
|
+
old_controller = defined?(@controller) && @controller
|
56
|
+
|
57
|
+
@controller = build_controller(klass)
|
58
|
+
yield
|
59
|
+
ensure
|
60
|
+
@controller = old_controller
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_controller(klass)
|
64
|
+
klass.new.tap { |c| c.request = request }.extend(Rails.application.routes.url_helpers)
|
65
|
+
end
|
52
66
|
end
|
53
67
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require "i18n"
|
5
|
+
require "action_view/helpers/translation_helper"
|
6
|
+
require "active_support/concern"
|
7
|
+
|
8
|
+
module ViewComponent
|
9
|
+
module Translatable
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
included do
|
13
|
+
class_attribute :i18n_backend, instance_writer: false, instance_predicate: false
|
14
|
+
end
|
15
|
+
|
16
|
+
class_methods do
|
17
|
+
def i18n_scope
|
18
|
+
@i18n_scope ||= virtual_path.sub(%r{^/}, "").gsub(%r{/_?}, ".")
|
19
|
+
end
|
20
|
+
|
21
|
+
def _after_compile
|
22
|
+
super
|
23
|
+
|
24
|
+
unless CompileCache.compiled? self
|
25
|
+
self.i18n_backend = I18nBackend.new(
|
26
|
+
i18n_scope: i18n_scope,
|
27
|
+
load_paths: _sidecar_files(%w[yml yaml]),
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class I18nBackend < ::I18n::Backend::Simple
|
34
|
+
EMPTY_HASH = {}.freeze
|
35
|
+
|
36
|
+
def initialize(i18n_scope:, load_paths:)
|
37
|
+
@i18n_scope = i18n_scope.split(".")
|
38
|
+
@load_paths = load_paths
|
39
|
+
end
|
40
|
+
|
41
|
+
# Ensure the Simple backend won't load paths from ::I18n.load_path
|
42
|
+
def load_translations
|
43
|
+
super(@load_paths)
|
44
|
+
end
|
45
|
+
|
46
|
+
def scope_data(data)
|
47
|
+
@i18n_scope.reverse_each do |part|
|
48
|
+
data = { part => data}
|
49
|
+
end
|
50
|
+
data
|
51
|
+
end
|
52
|
+
|
53
|
+
def store_translations(locale, data, options = EMPTY_HASH)
|
54
|
+
super(locale, scope_data(data), options)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def translate(key = nil, locale: nil, **options)
|
59
|
+
locale ||= ::I18n.locale
|
60
|
+
|
61
|
+
key = "#{i18n_scope}#{key}" if key.start_with?(".")
|
62
|
+
|
63
|
+
result = catch(:exception) do
|
64
|
+
i18n_backend.translate(locale, key, options)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Fallback to the global translations
|
68
|
+
if result.is_a? ::I18n::MissingTranslation
|
69
|
+
result = helpers.t(key, locale: locale, **options)
|
70
|
+
end
|
71
|
+
|
72
|
+
result
|
73
|
+
end
|
74
|
+
alias :t :translate
|
75
|
+
|
76
|
+
# Exposes .i18n_scope as an instance method
|
77
|
+
def i18n_scope
|
78
|
+
self.class.i18n_scope
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: view_component
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.29.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Open Source
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-02
|
11
|
+
date: 2021-04-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -128,6 +128,20 @@ dependencies:
|
|
128
128
|
- - "~>"
|
129
129
|
- !ruby/object:Gem::Version
|
130
130
|
version: '1'
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: jbuilder
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '2'
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - "~>"
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '2'
|
131
145
|
- !ruby/object:Gem::Dependency
|
132
146
|
name: rubocop
|
133
147
|
requirement: !ruby/object:Gem::Requirement
|
@@ -249,6 +263,7 @@ files:
|
|
249
263
|
- lib/view_component/template_error.rb
|
250
264
|
- lib/view_component/test_case.rb
|
251
265
|
- lib/view_component/test_helpers.rb
|
266
|
+
- lib/view_component/translatable.rb
|
252
267
|
- lib/view_component/version.rb
|
253
268
|
homepage: https://github.com/github/view_component
|
254
269
|
licenses:
|
@@ -270,7 +285,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
270
285
|
- !ruby/object:Gem::Version
|
271
286
|
version: '0'
|
272
287
|
requirements: []
|
273
|
-
rubygems_version: 3.
|
288
|
+
rubygems_version: 3.1.2
|
274
289
|
signing_key:
|
275
290
|
specification_version: 4
|
276
291
|
summary: View components for Rails
|