1

There are lots of general discussions about localization but none I've found thus far have addressed my issue.

I am using a Localizable.strings file and then another swift file containing an enum called LocalizationStrings. In the enum I am using static let statements so that I can avoid typo mistakes in my various files. (There's a lot to translate with more coming each day)

All of this works well, except when you have a localized string that contains string interpolation. This scenario fails because you cannot enclose the enum in quotes or it is read back as the string entered instead of being translated to what the enum actually equates to (which is the expected behavior).

Of course, taking the enum out of the equation and using string interpolation works just fine, but I would very much like to know if there's a way to continue to use the enum values, even when string interpolation exists.

Here's some code to explain:

Localizable.strings

"MY_NAME %@" = "My name is %@";
"YOUR_NAME" = "Your name is Fred";

LocalizationString.swift

enum LocalizationString {
    static let myName: LocalizedStringKey = "MY_NAME %@"
    static let yourName: LocalizedStringKey = "YOUR_NAME"
}

ContentView

struct CA_EmailVerifyView: View {

    let name = "Tom"

    var body: some View {
        VStack {
            // This works
            Text(LocalizationString.yourName)

            // This works (not using the enum)
            Text("myName \(name)")

            // This does not work (won't compile, but this is how I would love to use it)
            Text(LocalizationString.myName \(name))

            // This does not work (output is LocalizationString.myName Tom)
            Text("LocalizationString.myName \(name)")
        }
    }
}
kittonian
  • 1,020
  • 10
  • 22
  • Does this answer your question? [Localization with String interpolation in SwiftUI](https://stackoverflow.com/questions/62042521/localization-with-string-interpolation-in-swiftui) especially the answer wit NSLocalisationString – Ptit Xav Mar 06 '22 at 21:01
  • Sorry it does not. I had already read that post and it doesn't discuss my question at all. My question is regarding the use of enums when there is string interpolation. I am well aware of how to do localization in general, and how to do it with string interpolation, but not when using enums instead of straight string localization. – kittonian Mar 06 '22 at 21:21

2 Answers2

0

Does this work ?

enum LocalizationString {
    static func myName(_ name: String) -> String {
        String(format: NSLocalizedString("MY_NAME", comment: "My Name"), name)
    }
    static let yourName: LocalizedStringKey = "YOUR_NAME"
}

var name = "Nobody"
Text(LocalizationString. myName(name))

Another possibility is to uses extension for CustomStringConvertible or CVarArg types:

enum LocalizationString {
    static let myName: String = "MY_NAME"
    static let yourName: LocalizedStringKey = "YOUR_NAME"
}

extension String {
    func format(_ str: CustomStringConvertible) -> String {
        String(format: NSLocalizedString(self, comment: self), "\(str)")
    }
    func formatArg(_ arg: CVarArg) -> String {
        String(format: NSLocalizedString(self, comment: self), arg)
    }
}


var name = "Nobody"
var other = 500
Text(LocalizationString.myName.format(name))
Text(LocalizationString.myName.formatArg(name))
Text(LocalizationString.myName.format(other))
Text(LocalizationString.myName.formatArg(other))

Another one using % :

enum LocalizationString {
    static let myName: String = "MY_NAME"
}

extension String {
    static func %(_ key: String, _ arg: CVarArg) -> String {
        String(format: NSLocalizedString(key, comment: key), arg)
    }
}

var name = "Nobody"
var other = 500
Text(LocalizationString.myName % name)
Text(LocalizationString.myName % other)

EDIT : For multiple arguments, use an array and arguments: in format :

    static func %(_ key: String, _ arg: [CVarArg]) -> String {
        String(format: NSLocalizedString(key, comment: key), arguments: arg)
    }

    Text(LocalizationString.myName % [other, name])

EDIT: using LOcalizedStringKey :

extension LocalizedStringKey {
    // [how-to-change-localizedstringkey-to-string-in-swiftui](https://stackoverflow.com/questions/60841915/how-to-change-localizedstringkey-to-string-in-swiftui)
    var stringKey: String {
        let description = "\(self)"
        let components = description.components(separatedBy: "key: \"")
                    .map { $0.components(separatedBy: "\",") }
        return components[1][0]
    }
    static func %(_ key: LocalizedStringKey, _ arg: [CVarArg]) -> String {
        String(format: NSLocalizedString(key.stringKey, comment: key.stringKey), arguments: arg)
    }
    static func %(_ key: LocalizedStringKey, _ arg: CVarArg) -> String {
        String(format: NSLocalizedString(key.stringKey, comment: key.stringKey), arg)
    }
}

enum LocalizationString {
    static let myName: LocalizedStringKey = "MY_NAME"
    static let yourName: LocalizedStringKey = "YOUR_NAME"
}

var name = "Nobody"
var other = 500
Text(LocalizationString.myName % name)
Text(LocalizationString.myName % [other, name])

I found stringKey in how-to-change-localizedstringkey-to-string-in-swiftui

Ptit Xav
  • 3,006
  • 2
  • 6
  • 15
  • I appreciate the effort but there's no point in writing all that code just to use an enum. The other issue is that while my example shows a variable in the ContentView file, reality is that these variables will be coming from ObservedObjects or Core Data. – kittonian Mar 06 '22 at 21:22
  • I wonder if instead there could be an extension written using generics where it would allow me to type LocalizationString.myName(name) OR in reality something more like LocalizationString.myName(obsobj.name) and have it display properly? – kittonian Mar 06 '22 at 22:21
  • Added another possibility with extension – Ptit Xav Mar 06 '22 at 22:45
  • @kittonian Added the possibility to use % – Ptit Xav Mar 07 '22 at 10:04
  • Your % example is great and a really good idea. The only issue is that I also have various structs that format text so I can call something like BodyText(text: "blah") and that text variable is a type of LocalizedStringKey, not string. So, I can make it work by creating a duplicate of BodyText and name it something like BodyTextVar and change the text type to String and it all works perfectly. Is there a way to modify the string extension to be an extension on LocalizedStringKey instead so I don't have to do that? I tried but couldn't figure out how to make it work. – kittonian Mar 07 '22 at 15:28
  • I also want to clarify that it'll actually be two extensions, one on String as you have written and the other on LocalizedStringKey. This way it will work for my formatting structs AND for regular string entries. I should also add that there are plenty of times where more than one variable is in the string, so the extensions need to support an unlimited number of variables, not just one. – kittonian Mar 07 '22 at 15:40
  • @kittonian : I could add extension for LocalizationStringKey and also for array of arguments. could not manage wit CVarArg... due to operator limitation – Ptit Xav Mar 07 '22 at 17:09
0

The answer is much simpler than writing extensions and doing complicated work. Instead of writing a static let, when a variable is present you write a static func. This all takes place in the LocalizationString.swift file's enum and looks like this:

static func testText(name: String) -> LocalizedStringKey { LocalizedStringKey("TEST_TEXT \(name)") }

To use this in your view:

Text(LocalizationStrings.testTest(name: varname))

If you need more variables, just add them into the function.

kittonian
  • 1,020
  • 10
  • 22