@@ -119,66 +119,128 @@ public struct TextState: Equatable, Hashable, Sendable {
119119 fileprivate enum Storage : Equatable , Hashable , @unchecked Sendable {
120120 indirect case concatenated( TextState , TextState )
121121 #if canImport(SwiftUI)
122- case localized(
123- LocalizedStringKey , tableName: String ? , bundle: Bundle ? , comment: StaticString ? )
122+ case localizedStringKey(
123+ LocalizedStringKey ,
124+ tableName: String ? ,
125+ bundle: Bundle ? ,
126+ comment: StaticString ?
127+ )
124128 #endif
129+ case localizedStringResource( LocalizedStringResourceBox )
125130 case verbatim( String )
126131
127132 static func == ( lhs: Self , rhs: Self ) -> Bool {
128133 switch ( lhs, rhs) {
129134 case ( . concatenated( let l1, let l2) , . concatenated( let r1, let r2) ) :
130135 return l1 == r1 && l2 == r2
136+ case ( . concatenated, . localizedStringResource) ,
137+ ( . localizedStringResource, . concatenated) ,
138+ ( . concatenated, . verbatim) ,
139+ ( . verbatim, . concatenated) :
140+ // NB: We do not attempt to equate concatenated cases.
141+ return false
142+ case ( . verbatim( let lhs) , . verbatim( let rhs) ) :
143+ return lhs == rhs
144+
145+ case ( . verbatim( let string) , . localizedStringResource( let resource) ) ,
146+ ( . localizedStringResource( let resource) , . verbatim( let string) ) :
147+ return string == resource. asString ( )
148+
149+ case ( . localizedStringResource( let lhs) , . localizedStringResource( let rhs) ) :
150+ return lhs. asString ( ) == rhs. asString ( )
131151
132152 #if canImport(SwiftUI)
153+ case ( . concatenated, . localizedStringKey) ,
154+ ( . localizedStringKey, . concatenated) :
155+ // NB: We do not attempt to equate concatenated cases.
156+ return false
157+ case (
158+ . verbatim( let string) , . localizedStringKey( let key, let table, let bundle, let comment)
159+ ) ,
160+ ( . localizedStringKey( let key, let table, let bundle, let comment) , . verbatim( let string) ) :
161+ return string == key. formatted ( tableName: table, bundle: bundle, comment: comment)
162+
133163 case (
134- . localized( let lk, let lt, let lb, let lc) , . localized( let rk, let rt, let rb, let rc)
164+ . localizedStringKey( let lk, let lt, let lb, let lc) ,
165+ . localizedStringKey( let rk, let rt, let rb, let rc)
135166 ) :
136167 return lk. formatted ( tableName: lt, bundle: lb, comment: lc)
137168 == rk. formatted ( tableName: rt, bundle: rb, comment: rc)
138- #endif
139169
140- case ( . verbatim( let lhs) , . verbatim( let rhs) ) :
141- return lhs == rhs
170+ case (
171+ . localizedStringKey( let key, let table, let bundle, let comment) ,
172+ . localizedStringResource( let resource)
173+ ) ,
174+ (
175+ . localizedStringResource( let resource) ,
176+ . localizedStringKey( let key, let table, let bundle, let comment)
177+ ) :
178+ return key. formatted ( tableName: table, bundle: bundle, comment: comment)
179+ == resource. asString ( )
142180
143- #if canImport(SwiftUI)
144- case ( . localized( let key, let tableName, let bundle, let comment) , . verbatim( let string) ) ,
145- ( . verbatim( let string) , . localized( let key, let tableName, let bundle, let comment) ) :
146- return key. formatted ( tableName: tableName, bundle: bundle, comment: comment) == string
147181 #endif
148-
149- // NB: We do not attempt to equate concatenated cases.
150- default :
151- return false
152182 }
153183 }
154184
155185 func hash( into hasher: inout Hasher ) {
156- enum Key {
157- case concatenated
158- case localized
159- case verbatim
160- }
161-
162186 switch self {
163187 case ( . concatenated( let first, let second) ) :
164- hasher. combine ( Key . concatenated)
165188 hasher. combine ( first)
166189 hasher. combine ( second)
167190
168191 #if canImport(SwiftUI)
169- case . localized( let key, let tableName, let bundle, let comment) :
170- hasher. combine ( Key . localized)
192+ case . localizedStringKey( let key, let tableName, let bundle, let comment) :
171193 hasher. combine ( key. formatted ( tableName: tableName, bundle: bundle, comment: comment) )
172194 #endif
173195
196+ case . localizedStringResource( let resource) :
197+ hasher. combine ( resource. asString ( ) )
198+
174199 case . verbatim( let string) :
175- hasher. combine ( Key . verbatim)
176200 hasher. combine ( string)
177201 }
178202 }
179203 }
180204}
181205
206+ // MARK: - LocalizedStringResourceBox
207+
208+ private struct LocalizedStringResourceBox : @unchecked Sendable {
209+ // REVISIT: Make 'Any' into 'any Sendable' when minimum deployment target is iOS 18
210+ let value : Any
211+
212+ @available ( iOS 16 , macOS 13 , tvOS 16 , watchOS 9 , * )
213+ init ( _ resource: LocalizedStringResource ) {
214+ self . value = resource
215+ }
216+
217+ func asText( ) -> Text {
218+ guard
219+ #available( iOS 16 , macOS 13 , tvOS 16 , watchOS 9 , * ) ,
220+ let resource = value as? LocalizedStringResource
221+ else {
222+ preconditionFailure (
223+ " LocalizedStringResourceBox should only be exposed where LocalizedStringResource is available. "
224+ )
225+ }
226+
227+ return Text ( resource)
228+ }
229+
230+ func asString( ) -> String {
231+ guard
232+ #available( iOS 16 , macOS 13 , tvOS 16 , watchOS 9 , * ) ,
233+ let resource = value as? LocalizedStringResource
234+ else {
235+ preconditionFailure (
236+ " LocalizedStringResourceBox should only be exposed where LocalizedStringResource is available. "
237+ )
238+ }
239+
240+ return String ( localized: resource)
241+ }
242+ }
243+
182244// MARK: - API
183245
184246extension TextState {
@@ -198,10 +260,24 @@ extension TextState {
198260 bundle: Bundle ? = nil ,
199261 comment: StaticString ? = nil
200262 ) {
201- self . storage = . localized( key, tableName: tableName, bundle: bundle, comment: comment)
263+ self . storage = . localizedStringKey(
264+ key,
265+ tableName: tableName,
266+ bundle: bundle,
267+ comment: comment
268+ )
202269 }
203270 #endif
204271
272+ @available ( iOS 16 , macOS 13 , tvOS 16 , watchOS 9 , * )
273+ public init (
274+ _ resource: LocalizedStringResource
275+ ) {
276+ self . storage = . localizedStringResource(
277+ LocalizedStringResourceBox ( resource)
278+ )
279+ }
280+
205281 public static func + ( lhs: Self , rhs: Self ) -> Self {
206282 . init( storage: . concatenated( lhs, rhs) )
207283 }
@@ -391,13 +467,27 @@ extension TextState {
391467 return `self`
392468 }
393469
470+ @available ( iOS 16 , macOS 13 , tvOS 16 , watchOS 9 , * )
394471 public func accessibilityLabel(
395- _ key: LocalizedStringKey , tableName: String ? = nil , bundle: Bundle ? = nil ,
472+ _ resource: LocalizedStringResource
473+ ) -> Self {
474+ var `self` = self
475+ `self`. modifiers. append (
476+ . accessibilityLabel( . init( verbatim: String ( localized: resource) ) )
477+ )
478+ return `self`
479+ }
480+
481+ public func accessibilityLabel(
482+ _ key: LocalizedStringKey ,
483+ tableName: String ? = nil ,
484+ bundle: Bundle ? = nil ,
396485 comment: StaticString ? = nil
397486 ) -> Self {
398487 var `self` = self
399488 `self`. modifiers. append (
400- . accessibilityLabel( . init( key, tableName: tableName, bundle: bundle, comment: comment) ) )
489+ . accessibilityLabel( . init( key, tableName: tableName, bundle: bundle, comment: comment) )
490+ )
401491 return `self`
402492 }
403493
@@ -447,8 +537,10 @@ extension TextState {
447537 switch state. storage {
448538 case . concatenated( let first, let second) :
449539 text = Text ( first) + Text( second)
450- case . localized ( let content, let tableName, let bundle, let comment) :
540+ case . localizedStringKey ( let content, let tableName, let bundle, let comment) :
451541 text = . init( content, tableName: tableName, bundle: bundle, comment: comment)
542+ case . localizedStringResource( let resourceBox) :
543+ text = resourceBox. asText ( )
452544 case . verbatim( let content) :
453545 text = . init( verbatim: content)
454546 }
@@ -465,9 +557,14 @@ extension TextState {
465557 switch value. storage {
466558 case . verbatim( let string) :
467559 return text. accessibilityLabel ( string)
468- case . localized ( let key, let tableName, let bundle, let comment) :
560+ case . localizedStringKey ( let key, let tableName, let bundle, let comment) :
469561 return text. accessibilityLabel (
470- Text ( key, tableName: tableName, bundle: bundle, comment: comment) )
562+ Text ( key, tableName: tableName, bundle: bundle, comment: comment)
563+ )
564+ case . localizedStringResource( let resourceBox) :
565+ return text. accessibilityLabel (
566+ resourceBox. asText ( )
567+ )
471568 case . concatenated( _, _) :
472569 assertionFailure ( " `.accessibilityLabel` does not support concatenated `TextState` " )
473570 return text
@@ -572,7 +669,7 @@ extension String {
572669 self = String ( state: lhs, locale: locale) + String( state: rhs, locale: locale)
573670
574671 #if canImport(SwiftUI)
575- case . localized ( let key, let tableName, let bundle, let comment) :
672+ case . localizedStringKey ( let key, let tableName, let bundle, let comment) :
576673 self = key. formatted (
577674 locale: locale,
578675 tableName: tableName,
@@ -581,6 +678,9 @@ extension String {
581678 )
582679 #endif
583680
681+ case . localizedStringResource( let resourceBox) :
682+ self = resourceBox. asString ( )
683+
584684 case . verbatim( let string) :
585685 self = string
586686 }
@@ -637,9 +737,12 @@ extension TextState: CustomDumpRepresentable {
637737 case . concatenated( let lhs, let rhs) :
638738 output = dumpHelp ( lhs) + dumpHelp( rhs)
639739 #if canImport(SwiftUI)
640- case . localized ( let key, let tableName, let bundle, let comment) :
740+ case . localizedStringKey ( let key, let tableName, let bundle, let comment) :
641741 output = key. formatted ( tableName: tableName, bundle: bundle, comment: comment)
642742 #endif
743+ case . localizedStringResource( let resourceBox) :
744+ output = resourceBox. asString ( )
745+
643746 case . verbatim( let string) :
644747 output = string
645748 }
0 commit comments