Repository: freedomofpress/securedrop

Issue:
Missing translation for 'This field is required' across all languages
# Bug

## Description

When navigating to `/login` and pressing submit on an empty, the default (English) error message string is displayed.

## Steps to Reproduce

1. Click on the localized equivalent of 'Check for a response`
2. Click submit (while leaving an empty string)
3. Observe the error message.

## Expected Behavior
The error message should be displayed in the localized language.

## Actual Behavior

The message is displayed in English.


Discussion:
Nice catch - generally the WTForms aren't respecting the locale, similarly if you use an invalid character, e.g. "$":

<img width="1092" alt="screen shot 2017-11-30 at 1 21 04 pm" src="https://user-images.githubusercontent.com/7832803/33455567-5723fa40-d5d1-11e7-9928-b224d898869b.png">

(Note that these strings were wrapped in `gettext` and are translated)
Ouch, that's going to be a non trivial one I think. I'll take it.

Resolving patch:
diff --git a/securedrop/journalist_app/forms.py b/securedrop/journalist_app/forms.py
--- a/securedrop/journalist_app/forms.py
+++ b/securedrop/journalist_app/forms.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-from flask_babel import gettext
+from flask_babel import lazy_gettext
 from flask_wtf import FlaskForm
 from wtforms import (TextAreaField, TextField, BooleanField, HiddenField,
                      ValidationError)
@@ -12,25 +12,26 @@
 def otp_secret_validation(form, field):
     strip_whitespace = field.data.replace(' ', '')
     if len(strip_whitespace) != 40:
-        raise ValidationError(gettext('Field must be 40 characters long but '
-                                      'got {num_chars}.'.format(
-                                          num_chars=len(strip_whitespace)
-                                      )))
+        raise ValidationError(lazy_gettext(
+            'Field must be 40 characters long but '
+            'got {num_chars}.'.format(
+                num_chars=len(strip_whitespace)
+            )))
 
 
 def minimum_length_validation(form, field):
     if len(field.data) < Journalist.MIN_USERNAME_LEN:
         raise ValidationError(
-            gettext('Field must be at least {min_chars} '
-                    'characters long but only got '
-                    '{num_chars}.'.format(
-                        min_chars=Journalist.MIN_USERNAME_LEN,
-                        num_chars=len(field.data))))
+            lazy_gettext('Field must be at least {min_chars} '
+                         'characters long but only got '
+                         '{num_chars}.'.format(
+                             min_chars=Journalist.MIN_USERNAME_LEN,
+                             num_chars=len(field.data))))
 
 
 class NewUserForm(FlaskForm):
     username = TextField('username', validators=[
-        InputRequired(message=gettext('This field is required.')),
+        InputRequired(message=lazy_gettext('This field is required.')),
         minimum_length_validation
     ])
     password = HiddenField('password')
@@ -47,6 +48,7 @@ class ReplyForm(FlaskForm):
         u'Message',
         id="content-area",
         validators=[
-            InputRequired(message=gettext('You cannot send an empty reply.')),
+            InputRequired(message=lazy_gettext(
+                'You cannot send an empty reply.')),
         ],
     )
diff --git a/securedrop/source_app/forms.py b/securedrop/source_app/forms.py
--- a/securedrop/source_app/forms.py
+++ b/securedrop/source_app/forms.py
@@ -1,4 +1,4 @@
-from flask_babel import gettext
+from flask_babel import lazy_gettext
 from flask_wtf import FlaskForm
 from wtforms import PasswordField
 from wtforms.validators import InputRequired, Regexp, Length
@@ -8,11 +8,12 @@
 
 class LoginForm(FlaskForm):
     codename = PasswordField('codename', validators=[
-        InputRequired(message=gettext('This field is required.')),
+        InputRequired(message=lazy_gettext('This field is required.')),
         Length(1, Source.MAX_CODENAME_LEN,
-               message=gettext('Field must be between 1 and '
-                               '{max_codename_len} characters long.'.format(
-                                   max_codename_len=Source.MAX_CODENAME_LEN))),
+               message=lazy_gettext(
+                   'Field must be between 1 and '
+                   '{max_codename_len} characters long.'.format(
+                       max_codename_len=Source.MAX_CODENAME_LEN))),
         # Make sure to allow dashes since some words in the wordlist have them
-        Regexp(r'[\sA-Za-z0-9-]+$', message=gettext('Invalid input.'))
+        Regexp(r'[\sA-Za-z0-9-]+$', message=lazy_gettext('Invalid input.'))
     ])


Test patch: