SFDigitalServices / formio-sfds

The form.io theme for sf.gov
https://formio-sfds.herokuapp.com/
MIT License
15 stars 2 forks source link

Component labels aren't translated in validation messages #150

Open shawnbot opened 3 years ago

shawnbot commented 3 years ago

Well, this is not great:

The problem here is that even though it's properly translating the error message, formio.js is passing an un-translated string into it:

message(component) {
  //                                        "required" is translated...
  //                                         ↓
  return component.t(component.errorMessage('required'), {
    field: component.errorLabel, // ← but the error label placeholder is not
    data: component.data
  });
}

In this case, it's using the correct required translation ("{{field}} es requerido"), and interpolating the {{field}} placeholder with the un-translated label ("Your name") instead of the translation of the field's label ("Su nombre").

Workaround

I believe the workaround is to add Phrase translations for {key}.validate.customMessage, as formio.js's validation logic appears to prefer the component's validate.customMessage property over the message of the validation(s) that failed. In this case, the name.validate.customMessage Phrase translations could be "Your name is required" in English and "Su nombre es requerido" in Spanish.

Patch

The place to patch this is in the errorLabel getter of the Component class:

  /**
   * Returns the error label for this component.
   * @return {*}
   */
  get errorLabel() {
    return this.t(this.component.errorLabel
      || this.component.label
      || this.component.placeholder
      || this.key);
  }

For our case (Phrase translations with predictable keys), we'd patch it as something like this:

  get errorLabel () {
    const { key, errorLabel, label, placeholder } = this.component
    return this.t([
      `${key}.errorLabel`,
      `${key}.label`,
      `${key}.placeholder`,
      errorLabel || label || placeholder || key
    ])
  }
shawnbot commented 3 years ago

Another workaround for this might be to pass the field key of the interpolated data through t():

const Component = Formio.Components.components.component
hook(Component.prototype, 't', function (t, [text, params, ...rest]) {
  if (params?.label) {
    params.label = this.i18next.t(params.label)
  }
  return t(text, params, ...rest)
})

This approach would give us more fine-grained control over how any of the interpolated strings are translated. We could even iterate over all the keys of the params object and translate any that are strings. 🤷