@@ -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
0 commit comments