0

I'm trying to solve a seemingly simple issue: display free trial terms to the user in SwiftUI Text view. I have a unit string set to "Week" and a value that I can switch from 1 to 2. To handle plurals, I'm taking advantage of automatic grammar agreement on iOS 15. I have a string ready in my Localizable.strings file like so:

"Subscription period: %lld %@" = "^[%lld ^[%@](grammar: { partOfSpeech: \"noun\" })](inflect: true)";

To build out the string, I need to use string interpolation to add the word 'free'. I know that adding the word inside strings file will solve the issue, but I don't want to do that. I want to use that string universally, not necessarily pertaining to free trials. I don't understand why I can't call some init, pass it a key, and get a plain string back that I can interpolate as I wish. I've tried a lot of ways that are showcased in the code snippet attached. Either the string is not found, or the automatic grammar agreement is not applied. There appears to be String.LocalizationValue.StringInterpolation struct, but I can't figure out how to use it since there is no documentation whatsoever. What am I doing wrong? I would appreciate any help!

struct ContentView: View {
    /// This value mimics what **StoreKit 2** returns for `Product.SubscriptionPeriod.Unit` in its `localizedDescription` on iOS 15.4.
    let unit = "Week"
    
    @State var value = 1
    
    var key: LocalizedStringKey {
        "Subscription period: \(value) \(unit)"
    }
    
    var plainStringKey: String {
        "Subscription period: \(value) \(unit)"
    }
    
    var body: some View {
        Form {
            Section {
                // Works fine without string interpolation.
                Text(key)
                    .foregroundColor(.green)
                
                // `NSLocalizedString` doesn't work and it shouldn't as per its docs.
                Text("\(NSLocalizedString(plainStringKey, comment: "")) free")
                    .foregroundColor(.red)
                
                // Doesn't see the string.
                Text("\(String(localized: String.LocalizationValue(stringLiteral: plainStringKey))) free")
                    .foregroundColor(.red)
                
                // This way also doesn't see the string, which is strange, because it should be just like the following way, which does see the string.
                Text("\(String(localized: String.LocalizationValue.init(plainStringKey))) free")
                    .foregroundColor(.red)
                
                // Sees the string (only if I pass literal, not the plainStringKey property), but doesn't apply automatic grammar agreement.
                Text("\(String(localized: String.LocalizationValue("Subscription period: \(value) \(unit)"))) free")
                    .foregroundColor(.red)
                
                
                // MARK: Bad solution:
                /*
                 - Doesn't work with Dynamic Type properly
                 - You have to approximate horizontal spacing
                 */
                
                HStack(spacing: 3) {
                    Text("Subscription period: \(value) \(unit)")
                        .textCase(.lowercase)
                    Text("free")
                }
                .foregroundColor(.orange)
            }
            
            Section {
                Stepper(value: $value, in: 1...2) {
                    Text("Value: \(value)")
                }
            }
        }
    }
}

You can download the sample project here.

Gene Bogdanovich
  • 773
  • 1
  • 7
  • 21
  • Perhaps `.stringdict`? https://stackoverflow.com/a/63605964/351305 – pronebird Apr 22 '22 at 12:19
  • Does this answer your question https://stackoverflow.com/a/62042837/12299030? – Asperi Apr 22 '22 at 12:33
  • I didn’t use stringdict because I prefer to use automatic grammar agreement. I like the idea of Apple worrying about different localizations and plurals. Not the developer. – Gene Bogdanovich Apr 22 '22 at 12:59
  • No, Asperi, it doesn’t work. I tried your way before posting the question and now double-checked and added it to the sample project. It also shoots right through automatic grammar agreement and displays this: `^[1 ^[Week] (grammar: { partOfSpeech: "noun" })I (inflect: true) free` – Gene Bogdanovich Apr 22 '22 at 13:00

1 Answers1

2

I was actually very close with this piece of code:

Text("\(String(localized: String.LocalizationValue("Subscription period: \(value) \(unit)"))) free")

But using String(localized:) isn't correct. I guess it's because automatic grammar agreement uses markdown, which is the prerogative of AttributedString. We should use AttributedString(localized:). But even then, it's not going to inflect because apparently there is a bug in Foundation. But once it gets patched, it will work like a charm.

Here is the correct solution:

func freeTrialDuration() -> Text {
    // Use plain string.
    let duration: String = "^[\(value) ^[\(unit.lowercased())](grammar: { partOfSpeech: \"noun\" })](inflect: true)"
    // Use the following way to get the localized string.
    let localizedDuration = AttributedString(localized: String.LocalizationValue(duration))
    
    return Text("\(localizedDuration) free")
}
Gene Bogdanovich
  • 773
  • 1
  • 7
  • 21