diff --git a/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyController.java b/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyController.java
index de6875c2ade487535b5f9d59b0f87190436ec6a4..989b9a498d271c071e041c46bb3a34f01ac650d0 100644
--- a/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyController.java
+++ b/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyController.java
@@ -4,9 +4,11 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import com.google.common.collect.Lists;
 import fr.inra.urgi.faidare.api.NotFoundException;
@@ -31,6 +33,7 @@ import org.apache.logging.log4j.util.Strings;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Controller;
+import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -71,7 +74,7 @@ public class StudyController {
     }
 
     @GetMapping("/{studyId}")
-    public ModelAndView get(@PathVariable("studyId") String studyId) {
+    public ModelAndView get(@PathVariable("studyId") String studyId, Locale locale) {
         StudyDetailVO study = studyRepository.getById(studyId);
 
         if (study == null) {
@@ -83,7 +86,7 @@ public class StudyController {
         );
 
         List<GermplasmVO> germplasms = getGermplasms(study);
-        List<ObservationVariableVO> variables = getVariables(study);
+        List<ObservationVariableVO> variables = getVariables(study, locale);
         List<TrialVO> trials = getTrials(study);
         LocationVO location = getLocation(study);
 
@@ -139,14 +142,78 @@ public class StudyController {
         }
     }
 
-    private List<ObservationVariableVO> getVariables(StudyDetailVO study) {
+    private List<ObservationVariableVO> getVariables(StudyDetailVO study, Locale locale) {
         Set<String> variableIds = studyRepository.getVariableIds(study.getStudyDbId());
-        return cropOntologyRepository.getVariableByIds(variableIds)
-            .stream()
+        List<ObservationVariableVO> variables = cropOntologyRepository.getVariableByIds(variableIds);
+        return filterVariablesForLocale(variables, locale)
             .sorted(Comparator.comparing(ObservationVariableVO::getObservationVariableDbId))
             .collect(Collectors.toList());
     }
 
+    /**
+     * Filter the variables by language. The principles are the following. First, the languages of the variables
+     * are normalized (to transform FRA into fr for example).
+     * Then, several cases are possible.
+     *
+     * If there is no variable with the requested language, then we find the reference language.
+     * The reference language is en if there is at least one variable with that language.
+     * The reference is the first non null language found if there is no variable with the en language.
+     * Then, we keep all the variables with the reference language (if any), and all the variables without language.
+     *
+     * If there is at least one variable with the requested language, then we keep all the variables
+     * with the requested language, and all the variables without language.
+     */
+    private Stream<ObservationVariableVO> filterVariablesForLocale(List<ObservationVariableVO> variables, Locale locale) {
+        if (variables.isEmpty()) {
+            return variables.stream();
+        }
+
+        String requestedLanguage = locale.getLanguage();
+        String referenceLanguage = findReferenceLanguage(requestedLanguage, variables);
+
+        return variables.stream()
+                        .filter(variable ->
+                                    referenceLanguage == null
+                                        || !StringUtils.hasText(variable.getLanguage())
+                                        || normalizeLanguage(variable.getLanguage()).equals(referenceLanguage));
+    }
+
+    private String findReferenceLanguage(String requestedLanguage, List<ObservationVariableVO> variables) {
+        Set<String> normalizedVariableLanguages =
+            variables.stream()
+                     .map(ObservationVariableVO::getLanguage)
+                     .filter(StringUtils::hasText)
+                     .map(this::normalizeLanguage)
+                     .collect(Collectors.toSet());
+
+        String referenceLanguage = null;
+        if (normalizedVariableLanguages.contains(requestedLanguage)) {
+            referenceLanguage = requestedLanguage;
+        } else if (normalizedVariableLanguages.contains("en")) {
+            referenceLanguage = "en";
+        } else if (!normalizedVariableLanguages.isEmpty()) {
+            referenceLanguage = normalizedVariableLanguages.iterator().next();
+        }
+        return referenceLanguage;
+    }
+
+    private String normalizeLanguage(String language) {
+        // this is a hack trying to accomodate for languages not bein standard in the data
+        String languageInLowerCase = language.toLowerCase();
+        if (languageInLowerCase.length() == 3) {
+            switch (languageInLowerCase) {
+                case "fra":
+                    return "fr";
+                case "esp":
+                case "spa":
+                    return "es";
+                case "eng":
+                    return "en";
+            }
+        }
+        return languageInLowerCase;
+    }
+
     private List<TrialVO> getTrials(StudyDetailVO study) {
         if (study.getTrialDbIds() == null || study.getTrialDbIds().isEmpty()) {
             return Collections.emptyList();
diff --git a/backend/src/test/java/fr/inra/urgi/faidare/web/study/StudyControllerTest.java b/backend/src/test/java/fr/inra/urgi/faidare/web/study/StudyControllerTest.java
index a05ee9583fea4514c2b0e9b54265c371cd83812d..784f3cbcd28ac88caaceef3e6ff76b956b68d093 100644
--- a/backend/src/test/java/fr/inra/urgi/faidare/web/study/StudyControllerTest.java
+++ b/backend/src/test/java/fr/inra/urgi/faidare/web/study/StudyControllerTest.java
@@ -2,6 +2,7 @@ package fr.inra.urgi.faidare.web.study;
 
 import static fr.inra.urgi.faidare.web.Fixtures.createSite;
 import static fr.inra.urgi.faidare.web.Fixtures.htmlContent;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.when;
@@ -12,6 +13,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Set;
 
 import fr.inra.urgi.faidare.config.FaidareProperties;
@@ -21,6 +23,7 @@ import fr.inra.urgi.faidare.domain.data.TrialVO;
 import fr.inra.urgi.faidare.domain.data.germplasm.GermplasmVO;
 import fr.inra.urgi.faidare.domain.data.study.StudyDetailVO;
 import fr.inra.urgi.faidare.domain.data.study.StudySitemapVO;
+import fr.inra.urgi.faidare.domain.data.variable.ObservationVariableVO;
 import fr.inra.urgi.faidare.domain.datadiscovery.data.DataSource;
 import fr.inra.urgi.faidare.domain.response.PaginatedList;
 import fr.inra.urgi.faidare.domain.xref.XRefDocumentSearchCriteria;
@@ -37,6 +40,7 @@ import fr.inra.urgi.faidare.web.site.SiteController;
 import fr.inra.urgi.faidare.web.thymeleaf.CoordinatesDialect;
 import fr.inra.urgi.faidare.web.thymeleaf.FaidareDialect;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
@@ -45,6 +49,7 @@ import org.springframework.context.annotation.Import;
 import org.springframework.http.MediaType;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.web.servlet.ModelAndView;
 
 /**
  * MVC tests for {@link StudyController}
@@ -76,6 +81,9 @@ public class StudyControllerTest {
     @MockBean
     private LocationRepository mockLocationRepository;
 
+    @Autowired
+    private StudyController studyController;
+
     private StudyDetailVO study;
     private GermplasmVO germplasm;
     private List<XRefDocumentVO> crossReferences;
@@ -148,6 +156,78 @@ public class StudyControllerTest {
                .andExpect(status().isNotFound());
     }
 
+    @Nested
+    class Variables {
+        @Test
+        void shouldFilterVariablesByLanguageWhenRequestedLanguageIsFound() throws Exception {
+            ObservationVariableVO variableWithEnglishLanguage = Fixtures.createVariable();
+            variableWithEnglishLanguage.setLanguage("EN");
+
+            ObservationVariableVO variableWithFrenchLanguage = Fixtures.createVariable();
+            variableWithFrenchLanguage.setLanguage("FRA");
+
+            ObservationVariableVO variableWithNoLanguage = Fixtures.createVariable();
+            variableWithNoLanguage.setLanguage(null);
+
+            when(mockCropOntologyRepository.getVariableByIds(any())).thenReturn(
+                Arrays.asList(variableWithEnglishLanguage, variableWithFrenchLanguage, variableWithNoLanguage)
+            );
+
+            ModelAndView modelAndView = mockMvc.perform(get("/studies/{id}", study.getStudyDbId())
+                                                            .locale(Locale.FRENCH))
+                                               .andReturn()
+                                               .getModelAndView();
+            StudyModel model = (StudyModel) modelAndView.getModel().get("model");
+            assertThat(model.getVariables()).containsOnly(variableWithFrenchLanguage, variableWithNoLanguage);
+        }
+
+        @Test
+        void shouldFilterVariablesByLanguageWhenRequestedLanguageIsNotFound() throws Exception {
+            ObservationVariableVO variableWithEnglishLanguage = Fixtures.createVariable();
+            variableWithEnglishLanguage.setLanguage("EN");
+
+            ObservationVariableVO variableWithFrenchLanguage = Fixtures.createVariable();
+            variableWithFrenchLanguage.setLanguage("FRA");
+
+            ObservationVariableVO variableWithNoLanguage = Fixtures.createVariable();
+            variableWithNoLanguage.setLanguage(null);
+
+            when(mockCropOntologyRepository.getVariableByIds(any())).thenReturn(
+                Arrays.asList(variableWithEnglishLanguage, variableWithFrenchLanguage, variableWithNoLanguage)
+            );
+
+            ModelAndView modelAndView = mockMvc.perform(get("/studies/{id}", study.getStudyDbId())
+                                                            .locale(Locale.CHINA))
+                                               .andReturn()
+                                               .getModelAndView();
+            StudyModel model = (StudyModel) modelAndView.getModel().get("model");
+            assertThat(model.getVariables()).containsOnly(variableWithEnglishLanguage, variableWithNoLanguage);
+        }
+
+        @Test
+        void shouldFilterVariablesByLanguageWhenRequestedLanguageIsNotFoundAndEnglishAbsent() throws Exception {
+            ObservationVariableVO variableWithSpanishLanguage = Fixtures.createVariable();
+            variableWithSpanishLanguage.setLanguage("es");
+
+            ObservationVariableVO variableWithFrenchLanguage = Fixtures.createVariable();
+            variableWithFrenchLanguage.setLanguage("FRA");
+
+            ObservationVariableVO variableWithNoLanguage = Fixtures.createVariable();
+            variableWithNoLanguage.setLanguage(null);
+
+            when(mockCropOntologyRepository.getVariableByIds(any())).thenReturn(
+                Arrays.asList(variableWithSpanishLanguage, variableWithFrenchLanguage, variableWithNoLanguage)
+            );
+
+            ModelAndView modelAndView = mockMvc.perform(get("/studies/{id}", study.getStudyDbId())
+                                                            .locale(Locale.CHINA))
+                                               .andReturn()
+                                               .getModelAndView();
+            StudyModel model = (StudyModel) modelAndView.getModel().get("model");
+            assertThat(model.getVariables()).hasSize(2).contains(variableWithNoLanguage);
+        }
+    }
+
     private void testSitemap(int index, String expectedContent) throws Exception {
         MvcResult mvcResult = mockMvc.perform(get("/faidare/studies/sitemap-" + index + ".txt")
                                                   .contextPath("/faidare"))