Skip to content

Commit b3a71e2

Browse files
committed
Enhance human_attribute_name to support explicit locale options for nested attributes
1 parent a0ab34f commit b3a71e2

File tree

2 files changed

+66
-12
lines changed

2 files changed

+66
-12
lines changed

lib/structured_params/i18n.rb

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,28 @@ def human_attribute_name(attribute, options = {})
4646
return super if parts.length == 1
4747
return super unless structured_attributes.key?(parts.first)
4848

49-
resolve_nested_human_attribute_name(parts)
49+
resolve_nested_human_attribute_name(parts, options)
5050
end
5151

5252
private
5353

5454
# Walk +parts+ (e.g. ["hobbies", "0", "name"]) and build a human-readable
5555
# label by delegating each segment to the appropriate nested class.
5656
#
57-
#: (Array[String]) -> String
58-
def resolve_nested_human_attribute_name(parts)
57+
# Only +:locale+ is forwarded to inner +human_attribute_name+ calls.
58+
# Options such as +:default+ are specific to the outer call (e.g. from
59+
# +full_messages+) and must not bleed into individual segment lookups,
60+
# where they would replace the segment's own translation fallback.
61+
#
62+
#: (Array[String], Hash[untyped, untyped]) -> String
63+
def resolve_nested_human_attribute_name(parts, options)
5964
label = nil
6065
klass = self
66+
inner_opts = options.slice(:locale)
6167

6268
attr_segments(parts).each do |index, attr|
63-
human = klass&.human_attribute_name(attr) || attr.humanize
64-
label = build_nested_label(label, index, human)
69+
human = klass&.human_attribute_name(attr, inner_opts) || attr.humanize
70+
label = build_nested_label(label, index, human, options)
6571
klass &&= klass.structured_attributes[attr]
6672
end
6773

@@ -93,24 +99,31 @@ def attr_segments(parts)
9399
# activemodel.errors.nested_attribute.array (parent, index, child)
94100
# activemodel.errors.nested_attribute.object (parent, child)
95101
#
96-
#: (String?, String?, String) -> String
97-
def build_nested_label(result, index, attr_human)
98-
if result.nil?
99-
attr_human
100-
elsif index
102+
# The +locale:+ key from +options+ is forwarded to ::I18n.t so that an
103+
# explicit locale passed to human_attribute_name is honoured.
104+
#
105+
#: (String?, String?, String, Hash[untyped, untyped]) -> String
106+
def build_nested_label(result, index, attr_human, options)
107+
return attr_human if result.nil?
108+
109+
i18n_opts = options.slice(:locale)
110+
111+
if index
101112
::I18n.t(
102113
'activemodel.errors.nested_attribute.array',
103114
parent: result,
104115
index: index,
105116
child: attr_human,
106-
default: "#{result} #{index} #{attr_human}"
117+
default: "#{result} #{index} #{attr_human}",
118+
**i18n_opts
107119
)
108120
else
109121
::I18n.t(
110122
'activemodel.errors.nested_attribute.object',
111123
parent: result,
112124
child: attr_human,
113-
default: "#{result} #{attr_human}"
125+
default: "#{result} #{attr_human}",
126+
**i18n_opts
114127
)
115128
end
116129
end

spec/i18n_spec.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,45 @@
190190
end
191191
end
192192
end
193+
194+
describe 'explicit locale: option threading' do
195+
include_context 'with ja locale'
196+
197+
let(:ja_overrides) do
198+
{
199+
activemodel: {
200+
errors: {
201+
nested_attribute: {
202+
array: '%<parent>s %<index>s 番目の%<child>s',
203+
object: '%<parent>sの%<child>s'
204+
}
205+
}
206+
}
207+
}
208+
end
209+
210+
context 'when locale: :ja is passed while the current locale is :en' do
211+
it 'resolves array attribute labels in ja' do
212+
I18n.with_locale(:en) do
213+
expect(UserParameter.human_attribute_name(:'hobbies.0.name', locale: :ja)).to eq('趣味 0 番目の名前')
214+
end
215+
end
216+
217+
it 'resolves object attribute labels in ja' do
218+
I18n.with_locale(:en) do
219+
expect(UserParameter.human_attribute_name(:'address.postal_code', locale: :ja)).to eq('住所の郵便番号')
220+
end
221+
end
222+
end
223+
224+
context 'when locale: :en is passed while the current locale is :ja' do
225+
it 'resolves array attribute labels in en' do
226+
expect(UserParameter.human_attribute_name(:'hobbies.0.name', locale: :en)).to eq('Hobbies 0 Name')
227+
end
228+
229+
it 'resolves object attribute labels in en' do
230+
expect(UserParameter.human_attribute_name(:'address.postal_code', locale: :en)).to eq('Address Postal code')
231+
end
232+
end
233+
end
193234
end

0 commit comments

Comments
 (0)