最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - SwiftUI Localization of interpolated strings in a Framework with LocalizedStringResource - Stack Overflow

programmeradmin1浏览0评论

I'm working on a SwiftUI iOS app that uses a custom framework I have created to be able to share code with an app extension.

In that framework I've created a CustomError enum with a message parameter that needs to be translated so that users see the error in their own language. Often, I have to pass interpolated strings to the CustomError to clarify where the error is.

The translation of the message works well but for interpolated strings. With those the test is translated but the but the placeholders of the interpolated string are not replaced by its value.

To showcase the problem, I've created some demo code which I've separated in two parts: the part of the app and the part of the framework.

Below, is th part of the app:

struct ContentView: View {
    @State private var message: String = "No messages"
    @State private var otherMessage: String = "No messages"
    
    var body: some View {
        VStack {
            Text("Super app")
            Text(message)
            Text(otherMessage)
        }
        .padding()
        .onAppear {
            let fwCode = FrameworkCode()
            do {
                try fwCode.doStuff()
            }
            catch {
                self.message = error.localizedDescription
            }
            do {
                try fwCode.doOtherStuff()
            }
            catch {
                self.otherMessage = error.localizedDescription
            }

        }
    }
}

///Localizable.xcstrings for the app:
{
  "sourceLanguage" : "en",
  "strings" : {
    "Super app" : {
      "localizations" : {
        "es" : {
          "stringUnit" : {
            "state" : "translated",
            "value" : "Super aplicación"
          }
        }
      }
    }
  },
  "version" : "1.0"
}

and next the part of the framework (the framework is created from Xcode goint to File -> New -> Target -> Framework):

public enum CustomError :  LocalizedError {
    
    case myError(message: LocalizedStringResource)
    
    public var errorDescription: String? {
        switch self {
            case let .myError(message):
                return String(localizedFromFramework:message)
        }
    }
}

public class FrameworkCode {
    
    public init() {}
    
    public func doStuff() throws {
        let param = "XBox"
        throw CustomError.myError(message: "The best console is \(param)")
    }
    
    public func doOtherStuff() throws {
        throw CustomError.myError(message: "The best console is PlayStation")
    }
}

public extension String {
    
    init(localizedFromFramework name: LocalizedStringResource, comment: StaticString? = nil) {
        self.init(localized: String.LocalizationValue(name.key), table: "Localizable", bundle: Bundle(identifier:"eversoft.TestFramework"), comment: comment)
    }

}

///Below is the code for the Localizable.xcstrings for the farmework:
{
  "sourceLanguage" : "en",
  "strings" : {
    "The best console is %@" : {
      "localizations" : {
        "es" : {
          "stringUnit" : {
            "state" : "translated",
            "value" : "La mejor consola es %@"
          }
        }
      }
    },
    "The best console is PlayStation" : {
      "localizations" : {
        "es" : {
          "stringUnit" : {
            "state" : "translated",
            "value" : "La mejor consola es la PlayStation"
          }
        }
      }
    }
  },
  "version" : "1.0"
}

When executing the app, the error thrown from the method doOtherStuff() is translated correctly, but the error thrown by the method doStuff() gets translated but the placeholders are not replaced by the value.

What is causing the placeholders not to be replaced?

I'm using Xcode 16.2 and I've tested this with iOS 17.4, 18.1 and 18.2 and all of them behave equally.

I'm working on a SwiftUI iOS app that uses a custom framework I have created to be able to share code with an app extension.

In that framework I've created a CustomError enum with a message parameter that needs to be translated so that users see the error in their own language. Often, I have to pass interpolated strings to the CustomError to clarify where the error is.

The translation of the message works well but for interpolated strings. With those the test is translated but the but the placeholders of the interpolated string are not replaced by its value.

To showcase the problem, I've created some demo code which I've separated in two parts: the part of the app and the part of the framework.

Below, is th part of the app:

struct ContentView: View {
    @State private var message: String = "No messages"
    @State private var otherMessage: String = "No messages"
    
    var body: some View {
        VStack {
            Text("Super app")
            Text(message)
            Text(otherMessage)
        }
        .padding()
        .onAppear {
            let fwCode = FrameworkCode()
            do {
                try fwCode.doStuff()
            }
            catch {
                self.message = error.localizedDescription
            }
            do {
                try fwCode.doOtherStuff()
            }
            catch {
                self.otherMessage = error.localizedDescription
            }

        }
    }
}

///Localizable.xcstrings for the app:
{
  "sourceLanguage" : "en",
  "strings" : {
    "Super app" : {
      "localizations" : {
        "es" : {
          "stringUnit" : {
            "state" : "translated",
            "value" : "Super aplicación"
          }
        }
      }
    }
  },
  "version" : "1.0"
}

and next the part of the framework (the framework is created from Xcode goint to File -> New -> Target -> Framework):

public enum CustomError :  LocalizedError {
    
    case myError(message: LocalizedStringResource)
    
    public var errorDescription: String? {
        switch self {
            case let .myError(message):
                return String(localizedFromFramework:message)
        }
    }
}

public class FrameworkCode {
    
    public init() {}
    
    public func doStuff() throws {
        let param = "XBox"
        throw CustomError.myError(message: "The best console is \(param)")
    }
    
    public func doOtherStuff() throws {
        throw CustomError.myError(message: "The best console is PlayStation")
    }
}

public extension String {
    
    init(localizedFromFramework name: LocalizedStringResource, comment: StaticString? = nil) {
        self.init(localized: String.LocalizationValue(name.key), table: "Localizable", bundle: Bundle(identifier:"eversoft.TestFramework"), comment: comment)
    }

}

///Below is the code for the Localizable.xcstrings for the farmework:
{
  "sourceLanguage" : "en",
  "strings" : {
    "The best console is %@" : {
      "localizations" : {
        "es" : {
          "stringUnit" : {
            "state" : "translated",
            "value" : "La mejor consola es %@"
          }
        }
      }
    },
    "The best console is PlayStation" : {
      "localizations" : {
        "es" : {
          "stringUnit" : {
            "state" : "translated",
            "value" : "La mejor consola es la PlayStation"
          }
        }
      }
    }
  },
  "version" : "1.0"
}

When executing the app, the error thrown from the method doOtherStuff() is translated correctly, but the error thrown by the method doStuff() gets translated but the placeholders are not replaced by the value.

What is causing the placeholders not to be replaced?

I'm using Xcode 16.2 and I've tested this with iOS 17.4, 18.1 and 18.2 and all of them behave equally.

Share Improve this question edited Mar 15 at 3:55 HangarRash 15.1k5 gold badges20 silver badges55 bronze badges asked Mar 14 at 20:28 EnricEnric 3371 silver badge8 bronze badges 1
  • Just mention that if all the code is moved to the app, dicarding the framework, then the translations work as expected. – Enric Commented Mar 14 at 20:31
Add a comment  | 

1 Answer 1

Reset to default 2

In your String.init, you only extracted the key from the string resource.

self.init(localized: String.LocalizationValue(name.key), table: "Localizable", bundle: Bundle(identifier:"eversoft.TestFramework"), comment: comment)
                                              ^^^^^^^^

You basically lose the values to substituted here.


Your String.init is totally unnecessary. If you have a LocalizableStringResource, you can just localise it directly. LocalizedStringResource contains information about which bundle the resource is in. It's the main bundle by default, but you can specify your framework bundle.

public var errorDescription: String? {
    switch self {
        case let .myError(message):
            return String(localized: message)
    }
}
public func doStuff() throws {
    let param = "XBox"
    throw CustomError.myError(message: LocalizedStringResource("The best console is \(param)", bundle: .forClass(FrameworkCode.self)))
}

public func doOtherStuff() throws {
    throw CustomError.myError(message: LocalizedStringResource("The best console is PlayStation", bundle: .forClass(FrameworkCode.self)))
}

To still be able to pass a string interpolation to myError(message:), have the error message be a String.LocalizationValue and you can fet about LocalizedStringResource:

public enum CustomError : LocalizedError {
    
    case myError(message: String.LocalizationValue)
    
    public var errorDescription: String? {
        switch self {
            case let .myError(message):
            return String(localized: message, bundle: Bundle(for: FrameworkCode.self))
        }
    }
}

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论