Repository: freedomofpress/securedrop

Issue:
anonymous string-formatting fields break translation
**This description is superseded by <https://github.com/freedomofpress/securedrop/issues/6367#issuecomment-1082391316>!**

## Description

When a `gettext()`-wrapped string uses anonymous string-formatting fields `{}`, rather than positional fields `{n}` and named fields `{foo}`, translation of that string breaks silently:

1. in Jinja templates, entirely, falling back to the English source string; and
2. in Flask views, by omitting the field(s).

## Steps to Reproduce

TK, reproduction branch not yet pushed

1. From the admin interface, enable a 20-character minimum a source's first message.
2. Start a new Source Interface session.
3. Switch to any translated locale (e.g. `de_DE`).

## Expected Behavior

TK, reproduction branch not yet pushed

### Source string
`Your first message must be at least {} characters long.`

### Translation template
[`Ihre erste Nachricht muss mindestens {} Zeichen lang sein.`](https://weblate.securedrop.org/translate/securedrop/securedrop/de/?checksum=461cee53e88af00c)

### Translated string
`Ihre erste Nachricht muss mindestens 20 Zeichen lang sein.`

## Actual Behavior

<img width="1114" alt="Screen Shot 2022-03-29 at 13 11 56" src="https://user-images.githubusercontent.com/357435/160699870-2a690bfe-5b7f-4025-b116-45d270736ff0.png">

## Comments

For the two source strings that display this behavior, the code-level fix is as simple as `s/{}/{foo}`.  This will propagate downstream to Weblate as a source-string change requiring translation changes.  We could make the latter on our own, without involving translators, but not without further delaying the current v2.3.0 release.


Discussion:
good catch @cfm, sadly I agree that this will probably require a translator pass to fully resolve which puts it outside the 2.3.0 window
Interestingly it looks like some translators are already inserting `{num}` into their translations, I wonder why:
https://weblate.securedrop.org/translate/securedrop/securedrop/en/?checksum=461cee53e88af00c#translations
I think it's just a teeny bug in the code that should be trivial to fix.

```python3
                flash(gettext(
                    "Your first message must be at least {} characters long.".format(min_len)),
                    "error")
```

We are running string.format *before* calling gettext(). It should be:

```python3
                flash(gettext(
                    "Your first message must be at least {} characters long.").format(min_len),
                    "error")
```

Resolving patch:
diff --git a/securedrop/source_app/main.py b/securedrop/source_app/main.py
--- a/securedrop/source_app/main.py
+++ b/securedrop/source_app/main.py
@@ -204,7 +204,7 @@ def submit(logged_in_source: SourceUser) -> werkzeug.Response:
             min_len = InstanceConfig.get_default().initial_message_min_len
             if (min_len > 0) and (msg and not fh) and (len(msg) < min_len):
                 flash(gettext(
-                    "Your first message must be at least {} characters long.".format(min_len)),
+                    "Your first message must be at least {} characters long.").format(min_len),
                     "error")
                 return redirect(url_for('main.lookup'))
 


Test patch: