A Better Way to Test Multilanguage Apps in Cypress
Table of contents
- ACT 1: EXPOSITION
ACT 2: CONFRONTATION
- The Problem With Copy-Pasting Translations
- What if Cypress Could Be Polyglot?
- Let Me Introduce You to i18next, If You Haven’t Met It Yet
- Where Should the Translations Live?
- Creating Small
cy.initI18n()andcy.t()Commands - What About Fallbacks?
- Running the Same Test Across Multiple Languages
- A Small Example With a Language Switcher:
cy.changeLanguage()
- Should We Use Translated Text to Click Buttons?
- When Exact Text Actually Matters
- A Healthier Mental Model
- Interpolation: When Translations Need Dynamic Values
- Namespaces: When One Translation File Is Not Enough
- i18next Can Do More Than Just Interpolation
- ACT 3: RESOLUTION
ACT 1: EXPOSITION
There is a very common situation that appears when testing applications that support multiple languages.
At first, everything looks innocent.
You have an application in English, so you write something like this:
cy.contains('Login').click()
cy.contains('Welcome back').should('be.visible')
Easy enough, right?
Then, one day, the Product team says: “We are going international!”
And suddenly your application also supports Spanish.
So now you write:
cy.contains('Iniciar sesión').click()
cy.contains('Bienvenido de nuevo').should('be.visible')
Easy enough!
Then French arrives.
Then Portuguese.
Then German.
And how about Mandarin?
After that someone from UX changes "Welcome back" to "Good to see you again".
And of course, the Spanish translator decides that "Bienvenido de nuevo" should actually be "Qué bueno verte de nuevo".
So your Cypress test fails, and then you ask yourself the existential automation question:
Is my test failing because the application is broken, or because somebody "improved" the copy?
And that, my QA friend, is where the fun begins.
There are some articles about internationalization in Cypress, but surprisingly, not that many.
And as a multicultural and multilingual person, I have always been interested in how, a well-designed multilingual website, can connect with people across languages, cultures, and contexts, and in doing so, create a much bigger impact.
ACT 2: CONFRONTATION
The problem is not testing multilingual applications. Obviously, we absolutely should test them.
The problem (and big mistake) starts when we copy-paste translated text directly into our Cypress tests and pretend that this is a stable strategy.
This could be a typical test for creating a new project while also validating the messages shown to the user:
cy.contains('Nuevo proyecto').click()
cy.contains('Nombre del proyecto').type('Mi proyecto Cypress')
cy.contains('Crear proyecto').click()
cy.contains('Proyecto creado correctamente')
.should('be.visible')
This looks simple!
But what are we really testing?
Are we testing that the login flow works?
Are we testing that the _Spanish _translation exists?
Are we testing that the exact copy has not changed?
Are we testing that the translator did not open the thesaurus with too much confidence?
Maybe all of the above?
That is the problem.
Do not get me wrong.
Using cy.contains() with visible text is not evil at all. Actually, I like it a lot when it is used with intention. It makes tests readable, and in many cases, it reflects how users experience the application.
But in a multilingual app, hardcoded translated strings can quickly become a maintenance nightmare.
The test is no longer expressing the meaning of the user journey. It is expressing one particular version of the translated words at one particular moment in time.
In other words: We are testing words instead of meaning.
The Problem With Copy-Pasting Translations
A very common Cypress approach for testing the login user journey would look something like this:
cy.visit('/login?lng=es')
cy.contains('Iniciar sesión').click()
cy.contains('Bienvenido de nuevo').should('be.visible')
This works.
Until it does not! 🤔
The moment the Spanish translation changes, the test breaks, even if the application is working perfectly.
Of course, sometimes this is exactly what we want. If the purpose of the test is to validate a very specific legal disclaimer, marketing text, warning message, or compliance-related copy, then yes, the exact words matter.
But for most application flows, the exact words are not the real behavior.
The behavior looks something like:
- The app loads in Spanish.
- The login button is translated.
- The user can log in.
- The welcome message is shown in the selected language.
That is the meaning. The translated string is only the visible representation of that meaning.
So the real question is: How can Cypress verify the meaning without copy-pasting every translated word?
What if Cypress Could Be Polyglot?
Imagine this.
Instead of telling Cypress to find one exact Spanish sentence, we ask Cypress to find whatever welcome means in Spanish, and then we assert that the application shows it.
Something like this:
cy.t('auth.welcome', { lng: 'es' }).then((text) => {
cy.contains(text)
.should('be.visible')
})
A command like cy.t('auth.welcome', { lng: 'es' }) would get the Spanish translation for the auth welcome message, and then we can use that value in the assertion.
Now the test is not responsible for knowing the final translated sentence. The test only knows the meaning, and that, after all, is what is actually relevant for the test.
The key:
auth.welcome
represents the meaning.
The language:
es
represents the locale we want.
And the translation system takes care of the rest. Definitely that would be a much better contract.
The test would no longer be saying find "Bienvenido de nuevo", but find whatever auth.welcome means in Spanish.
Let Me Introduce You to i18next, If You Haven’t Met It Yet
If your application already uses
ACT 3: RESOLUTION
Testing multilingual applications is not just about running the same test in different languages.
It is about deciding what your test should actually care about.
If your test hardcodes every translated string, you may end up with a suite that fails every time copy changes, even when the product works perfectly.
But if your test uses the same i18next translation keys as the application, your assertions become closer to the real meaning of the product.
The key idea is simple: Test the meaning, not the hardcoded words.
Use stable selectors for interactions.
Use translation keys for localization assertions.
Use exact text only when exact text is truly the requirement.
Use interpolation, namespaces, fallbacks, plurals, formatting, and context through i18next, not through custom Cypress reinventions.
That small shift will make your multilingual Cypress tests cleaner, less repetitive, and much easier to maintain.
So, the next time you are about to copy-paste a Spanish, French, Portuguese, German, or Klingon translation into your Cypress spec, pause for a second and ask yourself:
Am I testing the product behavior, or am I just testing today’s wording?
Because in multilingual testing, words change. Meaning should not.
Cheers!
I'd love to hear from you! Please don't forget to follow me, leave a comment, or a reaction if you found this article useful or insightful. ❤️ 🦄 🤯 🙌 🔥
👉 My LinkedIn:
👉 You can also connect with me on my new YouTube channel:
SOCIAL SHARE CARD GENERATOR