Tools

Gravity Forms + WPML: the plugin that translates validation messages that remain in English

If you manage a multilingual WordPress site using Gravity Forms and WPML, you’ve probably come across this scenario: the form is translated, the labels are translated, the placeholders are translated. And then the user clicks ‘Submit’ without filling in a required field, and a flawless ‘This field is required.’ appears in the middle of a page in Spanish, French or Czech.

We came across this in a client project with forms across several markets. We ruled out the settings, the cache, the loading order and the usual suspects. It turned out the problem wasn’t with the site.

It was structural.

That’s why we developed a Mu-plugin that resolves it in three different ways, and we’ve released it as open-source software under the GPL-2.0 licence – free of charge and with no registration required.

What problem exactly does this plugin solve?

It resolves the issue where Gravity Forms’ default validation messages continue to appear in English across all secondary languages on a site using WPML, even if the site is correctly configured and Gravity Forms Multilingual (GFML) is active.

The messages affected are always the same:

  • This field is required.
  • There was a problem with your submission.
  • Please review the fields below.
  • Please enter a valid email address.
  • Invalid selection. Please select from the available choices.
  • Please enter a valid value.

It’s clearly not a fault that would bring a website down. But on a lead capture form, an error message in a language the user isn’t expecting is pure friction: it shatters trust at the very moment you’re asking for their details.

Why doesn’t WPML translate Gravity Forms’ default messages?

Because Gravity Forms has two types of validation message, and only one of them is processed by WPML. Let me explain 🙂

Custom messages are translated

These are the ones the administrator types in manually for each field, in the ‘Custom Validation Message’ setting. GFML registers them as translatable strings in WPML String Translation and they work without any issues.

Default messages are not

These are the ones generated by Gravity Forms itself when there is no custom message, using __(‘This field is required.’, ‘gravityforms’). These do not go through either WPML or GFML: they rely on WordPress’s standard translation system, i.e. the .mo files.

And this raises three problems at once:

  1. WPML changes the language dynamically at runtime, but the Gravity Forms textdomain may already have been loaded in the wrong language.
  2. There are no official Gravity Forms .mo files for several common locales: Czech, Greek, Romanian or the Latin American variants of Spanish, to name four.
  3. GFML only hooks into the `gform_field_validation` filter, which translates custom `errorMessages`. Messages based on gettext slip through the net.

Basically, each component does its job well, yet the result is still a message in English. It’s like a conference with flawless simultaneous interpretation in every room, except for the one next to the cloakroom – which happens to be exactly where the speaker utters the key phrase.

How does the three-tier system work?

The plugin doesn’t put all its eggs in one basket. It chains together three solutions, ordered from highest to lowest priority: if the first one fails to resolve the message, the second one tries, and if that fails too, the third one kicks in.

Layer 1 — Automatic reloading of the textdomain

When WPML switches languages (wpml_language_has_switched hook), the plugin unloads the active Gravity Forms textdomain using unload_textdomain(), determines the correct locale via the wpml_locale filter, and loads the corresponding .mo file from wp-content/languages/plugins/.

If the exact .mo file does not exist — let’s say es_CL — it tries the language’s base locale — es_ES —.

The key point about this layer is that there is no manual dictionary. The locale is determined from the WPML settings themselves, so it works with any language you have currently registered and with any you may register in the future.

Layer 2 — WPML String Translation as a fallback

If, after reloading, there is no .mo file for that locale, the plugin hooks into the gettext filter and attempts to translate the string via `apply_filters(‘wpml_translate_single_string’, …)`.

What does this mean in practice? It means you can translate the messages manually in WPML → String Translation → Gravity Forms context, and the plugin will pick them up. Without touching a single line of code.

Layer 3 — Emergency translations

As a last resort, the plugin includes direct translations of the four most critical messages for 35 languages. These are only activated if the two previous layers have failed.

I’d recommend thinking of it as a car’s airbag: it’s there so you never need it, not so you can rely on it whilst driving.

¿Qué mensajes e idiomas cubre la capa de emergencia?

Layer 3 covers, quite literally, these four messages, which are the ones that appear in 90 per cent of real-world validations:

Original messageCovered in Layer 3?
This field is required.Yes
There was a problem with your submission.Yes
Please review the fields below.Yes
Please enter a valid email address.Yes
Remaining messages (default)Via layers 1 and 2

And covers them in 35 languages: German, Arabic, Bulgarian, Catalan, Czech, Chinese, Korean, Croatian, Danish, Slovak, Slovenian, Spanish, Basque, Finnish, French, Galician, Greek, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Malay, Dutch, Norwegian, Polish, Portuguese, Romanian, Russian, Swedish, Thai, Turkish, Ukrainian and Vietnamese.

How do I install the plugin?

  1. Download the file amdt-gf-wpml-validation-fix.php from the GitHub repository.
  2. Upload it to the /wp-content/mu-plugins/ folder of your installation. If that folder does not exist, create it.
  3. Check the permissions: 644 for the file and 755 for the folder.
  4. Do not activate anything. Mu-plugins load automatically.
  5. Check this under Plugins → Essential on the WordPress dashboard.


Total time: less than two minutes, and no FTP required if you have access to your hosting provider’s file manager.

Why a mu-plugin and not a normal plugin?

Four reasons, and none of them are just a whim:

  • It cannot be accidentally deactivated from the dashboard.
  • It loads before normal plugins, which is exactly where it needs to be to intercept the textdomain load.
  • It’s more resilient against compromised administrator accounts.
  • It survives updates to WordPress and other plugins.


I’ve seen multilingual sites lose the translation of their forms after a routine update because someone had added the fix to the theme’s functions.php file and the theme was updated. That’s why we use a mu-plugin.

Will it still work if I add a new language?

ScenarioAutomatic?
New language and Gravity Forms has a .mo file for itYes — Layer 1
New language without a .mo file, translated in WPML String TranslationYes — layer 2
New language without a .mo file, already included in the fallbackYes — layer 3
New language without .mo, not translated in WPML ST and outside the fallbackNo — remains in English

Only the fourth scenario requires action, and it doesn’t even involve editing the code: simply translate the string in WPML String Translation.

¿Qué hago si un mensaje sigue apareciendo en inglés?

The plugin doesn’t appear in ‘Essentials’

Check that the file is located exactly at /wp-content/mu-plugins/amdt-gf-wpml-validation-fix.php and not inside a subfolder: WordPress only reads .php files that are directly under mu-plugins/, it does not scan subdirectories. Also check the permissions.

A specific message is still in English

Check whether that string is in the $emergency_fallback array in the code. If it isn’t, you have three options:

  1. Translate it in WPML → String Translation, filtering by the ‘gravityforms’ domain. No need to touch the code.
  2. Add it to the plugin’s fallback array along with its translations.
  3. Define a custom errorMessage in the field editor: GFML will translate this as normal.

Conflicts with other code

The plugin registers its filters with priorities 20 and 25 specifically so that they run after GFML, which operates at priority 10. If you have your own code running at priority 30 or higher, it will override the plugin’s output.

Technical specifications

FieldValue
NameGF WPML Validation Messages Fix
Identifieramdt-gf-wpml-validation-fix
Version2.0.0
Typemu-plugin (must-use plugin)
LicenceGPL-2.0 or later
AuthorAMDT — Even More Difficult Still
RequisitosWordPress · Gravity Forms · WPML (Gravity Forms Multilingual recommended)
Files1 · amdt-gf-wpml-validation-fix.php
Fallback languages35
PriceFree
Repository

Frequently Asked Questions

Is the plugin free?

Yes. It is released under the GPL-2.0 licence and can be downloaded directly from GitHub, with no registration or email address required.

It isn’t essential for the plugin to work, but I recommend keeping it. GFML translates forms and custom messages; this plugin fills the gap for default messages. They complement each other, they don’t compete.

No. The plugin relies on specific WPML hooks, so outside of WPML it has nothing to latch onto.

The plugin internally caches the language and locale to avoid repeated calls to WPML within the same request. The gettext filter is the most sensitive part, which is why it only activates when the active language is not English.

The code would run, yes, but you’d lose the guarantee that it loads before the other plugins, which is only half the solution. I recommend the mu-plugin route.

It’s published on GitHub and issues are reported in the ‘Issues’ section of the repository.

Yes. The GPL-2.0 licence permits commercial use and modification.

In summary

If you have a multilingual WordPress site using Gravity Forms and WPML and the validation messages remain in English, it’s not down to your settings. It’s a system flaw that allows the default messages to slip through, and this mu-plugin patches it in three different places so it’ll still work when you add a sixth language.

We’ve released it because finding the real cause took us a few hours of debugging, and it doesn’t make much sense for another team to go through exactly the same process.

Remember, ultimately, a website’s translation isn’t judged by its homepage, but by the error message the user sees just before deciding whether to submit their details or close the tab 😉