SwiftGen: Strings: valid format `% d` is ignored by SwiftGen, but still formatted.

100% daily becomes 1000aily because the Swift template function tr always returns String(format: format, locale: Locale.current, arguments: args) even if args is empty.

% (followed by space) is afaik the invisible plus sign so somehow String(format: interprets it like a valid format argument (not sure if it should).

But the problem is that the tr function should just return the result from NSLocalizedString if no args. So just adding guard !args.isEmpty else { return format } before the last line would fix it.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 28 (9 by maintainers)

Most upvoted comments

The problem is that, per fprintf-style standard, % is interpreted as a placeholder. Like for %d or %@ etc. If you want a literal %, you need to escape it by doubling it, using %% in your Localizable.strings (see the man page for the fprintf-family of functions). You can see it as being similar to when you have \n or \r in a string and if you want a literal \ you need to double it using \\. This is very similar here.

This is not really related to SwiftGen itself, but to how String(format: ) works.

@djbe @AliSoftware Yes! thank you so much for your understanding! great product by the way!

@franmowinckel I’d like to again apologise for quickly closing and dismissing the issue, we’ve had some previous issues where the user didn’t understand the %% syntax, and we just assumed this was a duplicate.

I’m really glad you stuck with us though, and helped us understand the underlying issue. Hopefully when you report any future issues or requests, this will go better. 😅

Wooow I actually really thought that we were already skipping the String(format:) for let constants… but indeed I just looked back at the generated code and we call the same tr function, you’re right. We should indeed have a different tr function with no additional CVaArg and that skips the call to String(format:) then, indeed… My apologies, I was so convinced we already had a separate function, and was so confused by the fact that your original issue looked a lot like what other people have reported when they just didn’t know that they should use %% 😓

So in the end:

  1. We should fix the fact that % d isn’t recognised as a valid placeholder, while per the sprintf spec it should be seen as one. That means that 100% daily should lead to a static func foo(_ p1: Int) -> String and not a let foo: String like it does currently.
  2. We should indeed skip the call to String(format: ) when using a let i.e when there’s no placeholders detected and extra parameters to pass
  3. Those both things would not change the fact that your "100% daily" example would still need to be escaped to "100%% daily" — because SwiftGen couldn’t know that you meant a literal % otherwise, given that % d should be considered as a valid placeholder — once we fixed (1).

I apologise, I only got around to reading the format docs now (on phone all day), and you’re completely right, % d is a valid conversion specification, and SwiftGen incorrectly ignores it.

We’ve been focusing the whole time on the wrong issue (thinking we should somehow circumvent this), whereas the actual issue is that SwiftGen should properly parse this and generate a static func with an integer parameter.