mac-cain13 / R.swift

Strong typed, autocompleted resources like images, fonts and segues in Swift projects
MIT License
9.5k stars 763 forks source link

Bypass String(format:) to prevent escaping percentage symbols #848

Open pimnijman opened 1 year ago

pimnijman commented 1 year ago

Now that strings are always formatted using String(format:) (even strings without arguments) we have to escape any percentage signs (%) with %%

Our strings are hosted on phrase.com and also used in our Android project. This means I cannot go to the source and replace things like "get a 40% discount" with "get a 40%% discount", because that will have effects on other platforms as well.

Is there a possibility to opt out of this behavior, so that we don't have to escape the percentage symbol in strings that do not contain arguments?

mokshitgogia commented 1 year ago

func escapePercentIfNeeded(_ input: String) -> String { if input.contains("%") { return input.replacingOccurrences(of: "%", with: "%%") } else { return input } } Just add this Function and use it where you need this. Like: String(Format: escapePercentIfNeeded("get a 40% discount")) or String(Format: escapePercentIfNeeded("get a 40% discount")) Both will work Fine.

Let Me Know If I am Wrong Somewhere.

bartoszirzyk commented 1 year ago

@mokshitgogia this is wrong code and doesn't solve the issue. I've got same problem. I think using String(format:locale:arguments) when no arguments is just a mistake and generates wrong text whenever % is in the translation.

@mac-cain13 could you check it out?

bartoszirzyk commented 1 year ago

The issue is in StringResource+Integration:

    init(key: StaticString, tableName: String, source: StringResource.Source, developmentValue: String?, locale overrideLocale: Locale?, arguments: [CVarArg]) {
        switch source {
        case let .hosting(bundle):
            // With fallback to developmentValue
            let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: developmentValue ?? "", comment: "")
            self = String(format: format, locale: overrideLocale, arguments: arguments)

        case let .selected(bundle, locale):
            // Don't use developmentValue with selected bundle/locale
            let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: "", comment: "")
            self = String(format: format, locale: overrideLocale ?? locale, arguments: arguments)

        case .none:
            self = key.description
        }
    }

I think when there are no arguments provided, String should initialise from format, otherwise keep old implementation. Am I missing something?

pimnijman commented 1 year ago

Correct. This issue was introduced by https://github.com/mac-cain13/R.swift/pull/768 by @tomlokhorst. I think there should be a way to opt-out of this behavior.

lursk commented 4 months ago

I've run into the very same problem as well. Temporally as a workaround I've added a script with a small regex substitution to escape % sings in translations. It's not perfect but sufficient for my project. I run it after downloading translations. s/%?%(?!(?:[0-9]+\$)?(?:(?:l|ll)?(?:i|u|d)|@|[0-9]*\.?[0-9]*f))/%%/g it ignores only simple specifiers d, u, i, f and @ and escapes the rest

PS. Remember not to push modified files to the phrase back