Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
pimnijman opened this issue Sep 6, 2023 · 5 comments
Open

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

pimnijman opened this issue Sep 6, 2023 · 5 comments

Comments

@pimnijman
Copy link

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
Copy link

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
Copy link

@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
Copy link

bartoszirzyk commented Sep 18, 2023

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
Copy link
Author

Correct. This issue was introduced by #768 by @tomlokhorst.
I think there should be a way to opt-out of this behavior.

@lursk
Copy link

lursk commented Jun 24, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants