String(localized:) has no separate key and value?

Viewed 1059

New in iOS 15, we are invited to use this String initializer method to make localizable strings in our Swift code:

init(localized keyAndValue: String.LocalizationValue, 
    table: String? = nil, bundle: Bundle? = nil, 
    locale: Locale = .current, comment: StaticString? = nil)

The trouble is that the first parameter is, as the internal name suggests, used for both the key and the value. You can see that from this localized French strings file:

/* Alert message: Report a tap */
"You tapped me!" = "Vous m'avez tapé!";

That resulted from my saying

String(localized:"You tapped me!", comment: "Alert message: Report a tap")

and localizing for French.

That's totally wrong! This is supposed to be a list of key–value pairs; we shouldn't be using the English user-facing text as a key.

For one thing, if we now change the English text in our String(localized:comment:) call, our French translation will break. Also, we would be unable to have different French translations for the same English text used in different contexts.

What are we supposed to do about this?

3 Answers

I regard this as a major bug in String(localizable:). If we were using NSLocalizedString, we would have individual key: and value: parameters. String(localizable:) needs that.

I can think of two workarounds. One is: don't use String(localizable:). Just keep on using NSLocalizedString.

The other is to localize explicitly for English. Instead of entering the English user-facing text as the localized: parameter, enter a key string. Then, to prevent the keys from appearing in the user interface, export the English localization and "translate" the keys into the desired English user-facing text. Now import the localization to generate the correct English .strings files.

(If your development language isn't English, substitute the development language into those instructions.)

Now when you export a different localization, such as French, the <trans-unit> element's id value is the key, to which the translator pays no attention, and the <source> is the English, which the translator duly translates.

To change the English user-facing text later on, edit the English Localizable.strings file — not the code. Nothing will break because the key remains constant.

I was experiencing the same problem using SwiftGen to produce my localized strings and the solution for me was to ensure that my generated localized strings file is inside of the appropriate language folder rather than the separate Generated folder I would otherwise use.

Folder hierarchy

The explicit localization approach using keys is imo the correct way here, I think the parameter name keyAndValue is just misleading.

See another initializer using String.LocalizationValue, for AttributedString:

https://developer.apple.com/documentation/foundation/attributedstring/3867590-init

On that one the parameter is just named key:

init(localized key: String.LocalizationValue, options: AttributedString.FormattingOptions = [], table: String? = nil, bundle: Bundle? = nil, locale: Locale? = nil, comment: StaticString? = nil)

After all that seems to align well with SwiftUI's usage of LocalizedStringKey, where no value is given next to the key too.

Beside the current lack of documentation, I don't understand the need to introduce String.LocalizationValue having the mentioned LocalizedStringKey but at least it seems to be aligned from the way it's done in SwiftUI, but not from the previous localizedString(forKey:value:table) / NSLocalizedString way.

In my recent projects we always used key-based translations even for the development language, so this new initializer would work pretty well. But Apple should change the parameter name and update the documentation accordingly, as they did for AttributedString.

Related