diff --git a/lib/matplotlib/_text_helpers.py b/lib/matplotlib/_text_helpers.py
index b9603b114bc2..cb4fa7693eae 100644
--- a/lib/matplotlib/_text_helpers.py
+++ b/lib/matplotlib/_text_helpers.py
@@ -43,7 +43,7 @@ def warn_on_missing_glyph(codepoint, fontnames):
             f"Matplotlib currently does not support {block} natively.")
 
 
-def layout(string, font, *, kern_mode=Kerning.DEFAULT):
+def layout(string, font, language, *, kern_mode=Kerning.DEFAULT):
     """
     Render *string* with *font*.
 
@@ -65,7 +65,7 @@ def layout(string, font, *, kern_mode=Kerning.DEFAULT):
     """
     x = 0
     prev_glyph_idx = None
-    char_to_font = font._get_fontmap(string)
+    char_to_font = font._get_fontmap(string)  # TODO: Pass in language.
     base_font = font
     for char in string:
         # This has done the fallback logic
diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py
index b435ae565ce4..b9bf48094f91 100644
--- a/lib/matplotlib/backends/backend_agg.py
+++ b/lib/matplotlib/backends/backend_agg.py
@@ -189,7 +189,8 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
         font = self._prepare_font(prop)
         # We pass '0' for angle here, since it will be rotated (in raster
         # space) in the following call to draw_text_image).
-        font.set_text(s, 0, flags=get_hinting_flag())
+        font.set_text(s, 0, flags=get_hinting_flag(),
+                      language=mtext.get_language() if mtext is not None else None)
         font.draw_glyphs_to_bitmap(
             antialiased=gc.get_antialiased())
         d = font.get_descent() / 64.0
diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py
index 4c6bb266e09e..b44d1b7f04d1 100644
--- a/lib/matplotlib/backends/backend_pdf.py
+++ b/lib/matplotlib/backends/backend_pdf.py
@@ -2345,6 +2345,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
             return self.draw_mathtext(gc, x, y, s, prop, angle)
 
         fontsize = prop.get_size_in_points()
+        language = mtext.get_language() if mtext is not None else None
 
         if mpl.rcParams['pdf.use14corefonts']:
             font = self._get_font_afm(prop)
@@ -2355,7 +2356,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
             fonttype = mpl.rcParams['pdf.fonttype']
 
         if gc.get_url() is not None:
-            font.set_text(s)
+            font.set_text(s, language=language)
             width, height = font.get_width_height()
             self.file._annotations[-1][1].append(_get_link_annotation(
                 gc, x, y, width / 64, height / 64, angle))
@@ -2389,7 +2390,8 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
             multibyte_glyphs = []
             prev_was_multibyte = True
             prev_font = font
-            for item in _text_helpers.layout(s, font, kern_mode=Kerning.UNFITTED):
+            for item in _text_helpers.layout(s, font, language,
+                                             kern_mode=Kerning.UNFITTED):
                 if _font_supports_glyph(fonttype, ord(item.char)):
                     if prev_was_multibyte or item.ft_object != prev_font:
                         singlebyte_chunks.append((item.ft_object, item.x, []))
diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py
index 62952caa32e1..2126d9c94824 100644
--- a/lib/matplotlib/backends/backend_ps.py
+++ b/lib/matplotlib/backends/backend_ps.py
@@ -795,9 +795,10 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
                 thisx += width * scale
 
         else:
+            language = mtext.get_language() if mtext is not None else None
             font = self._get_font_ttf(prop)
             self._character_tracker.track(font, s)
-            for item in _text_helpers.layout(s, font):
+            for item in _text_helpers.layout(s, font, language):
                 ps_name = (item.ft_object.postscript_name
                            .encode("ascii", "replace").decode("ascii"))
                 glyph_name = item.ft_object.get_glyph_name(item.glyph_idx)
diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py
index 3b0de58814d9..5801e101050f 100644
--- a/lib/matplotlib/text.py
+++ b/lib/matplotlib/text.py
@@ -136,6 +136,7 @@ def __init__(self,
         super().__init__()
         self._x, self._y = x, y
         self._text = ''
+        self._language = None
         self._reset_visual_defaults(
             text=text,
             color=color,
@@ -1422,6 +1423,36 @@ def _va_for_angle(self, angle):
             return 'baseline' if anchor_at_left else 'top'
         return 'top' if anchor_at_left else 'baseline'
 
+    def get_language(self):
+        """Return the language this Text is in."""
+        return self._language
+
+    def set_language(self, language):
+        """
+        Set the language of the text.
+
+        Parameters
+        ----------
+        language : str or list[tuple[str, int, int]]
+
+        """
+        _api.check_isinstance((list, str, None), language=language)
+        if isinstance(language, list):
+            for val in language:
+                if not isinstance(val, tuple) or len(val) != 3:
+                    raise ValueError('language must be list of tuple, not {language!r}')
+                sublang, start, end = val
+                if not isinstance(sublang, str):
+                    raise ValueError(
+                        'sub-language specification must be str, not {sublang!r}')
+                if not isinstance(start, int):
+                    raise ValueError('start location must be int, not {start!r}')
+                if not isinstance(end, int):
+                    raise ValueError('end location must be int, not {end!r}')
+
+        self._language = language
+        self.stale = True
+
 
 class OffsetFrom:
     """Callable helper class for working with `Annotation`."""
diff --git a/lib/matplotlib/text.pyi b/lib/matplotlib/text.pyi
index 9cdfd9596a7d..0081986254b0 100644
--- a/lib/matplotlib/text.pyi
+++ b/lib/matplotlib/text.pyi
@@ -108,6 +108,8 @@ class Text(Artist):
     def set_antialiased(self, antialiased: bool) -> None: ...
     def _ha_for_angle(self, angle: Any) -> Literal['center', 'right', 'left'] | None: ...
     def _va_for_angle(self, angle: Any) -> Literal['center', 'top', 'baseline'] | None: ...
+    def get_language(self) -> str | list[tuple[str, int, int]] | None: ...
+    def set_language(self, language: str | list[tuple[str, int, int]] | None) -> None: ...
 
 class OffsetFrom:
     def __init__(
diff --git a/lib/matplotlib/textpath.py b/lib/matplotlib/textpath.py
index 35adfdd77899..9d6f8a69184c 100644
--- a/lib/matplotlib/textpath.py
+++ b/lib/matplotlib/textpath.py
@@ -69,7 +69,7 @@ def get_text_width_height_descent(self, s, prop, ismath):
         d /= 64.0
         return w * scale, h * scale, d * scale
 
-    def get_text_path(self, prop, s, ismath=False):
+    def get_text_path(self, prop, s, ismath=False, language=None):
         """
         Convert text *s* to path (a tuple of vertices and codes for
         matplotlib.path.Path).
@@ -109,7 +109,8 @@ def get_text_path(self, prop, s, ismath=False):
             glyph_info, glyph_map, rects = self.get_glyphs_tex(prop, s)
         elif not ismath:
             font = self._get_font(prop)
-            glyph_info, glyph_map, rects = self.get_glyphs_with_font(font, s)
+            glyph_info, glyph_map, rects = self.get_glyphs_with_font(font, s,
+                                                                     language=language)
         else:
             glyph_info, glyph_map, rects = self.get_glyphs_mathtext(prop, s)
 
@@ -130,7 +131,7 @@ def get_text_path(self, prop, s, ismath=False):
         return verts, codes
 
     def get_glyphs_with_font(self, font, s, glyph_map=None,
-                             return_new_glyphs_only=False):
+                             return_new_glyphs_only=False, language=None):
         """
         Convert string *s* to vertices and codes using the provided ttf font.
         """
@@ -145,7 +146,7 @@ def get_glyphs_with_font(self, font, s, glyph_map=None,
 
         xpositions = []
         glyph_ids = []
-        for item in _text_helpers.layout(s, font):
+        for item in _text_helpers.layout(s, font, language):
             char_id = self._get_char_id(item.ft_object, ord(item.char))
             glyph_ids.append(char_id)
             xpositions.append(item.x)
diff --git a/lib/matplotlib/textpath.pyi b/lib/matplotlib/textpath.pyi
index 34d4e92ac47e..5d66159da14e 100644
--- a/lib/matplotlib/textpath.pyi
+++ b/lib/matplotlib/textpath.pyi
@@ -16,7 +16,8 @@ class TextToPath:
         self, s: str, prop: FontProperties, ismath: bool | Literal["TeX"]
     ) -> tuple[float, float, float]: ...
     def get_text_path(
-        self, prop: FontProperties, s: str, ismath: bool | Literal["TeX"] = ...
+        self, prop: FontProperties, s: str, ismath: bool | Literal["TeX"] = ...,
+        language: str | list[tuple[str, int, int]] | None = ...,
     ) -> list[np.ndarray]: ...
     def get_glyphs_with_font(
         self,
@@ -24,6 +25,7 @@ class TextToPath:
         s: str,
         glyph_map: dict[str, tuple[np.ndarray, np.ndarray]] | None = ...,
         return_new_glyphs_only: bool = ...,
+        language: str | list[tuple[str, int, int]] | None = ...,
     ) -> tuple[
         list[tuple[str, float, float, float]],
         dict[str, tuple[np.ndarray, np.ndarray]],
diff --git a/src/ft2font.cpp b/src/ft2font.cpp
index 56767ef5235f..0aad01d4a14b 100644
--- a/src/ft2font.cpp
+++ b/src/ft2font.cpp
@@ -399,7 +399,8 @@ void FT2Font::set_kerning_factor(int factor)
 }
 
 void FT2Font::set_text(
-    std::u32string_view text, double angle, FT_Int32 flags, std::vector<double> &xys)
+    std::u32string_view text, double angle, FT_Int32 flags, LanguageType languages,
+    std::vector<double> &xys)
 {
     FT_Matrix matrix; /* transformation matrix */
 
diff --git a/src/ft2font.h b/src/ft2font.h
index 5524930d5ad0..e955066726e3 100644
--- a/src/ft2font.h
+++ b/src/ft2font.h
@@ -6,6 +6,7 @@
 #ifndef MPL_FT2FONT_H
 #define MPL_FT2FONT_H
 
+#include <optional>
 #include <set>
 #include <string>
 #include <string_view>
@@ -70,6 +71,9 @@ class FT2Font
     typedef void (*WarnFunc)(FT_ULong charcode, std::set<FT_String*> family_names);
 
   public:
+    using LanguageRange = std::tuple<std::string, int, int>;
+    using LanguageType = std::optional<std::vector<LanguageRange>>;
+
     FT2Font(FT_Open_Args &open_args, long hinting_factor,
             std::vector<FT2Font *> &fallback_list, WarnFunc warn);
     virtual ~FT2Font();
@@ -78,7 +82,7 @@ class FT2Font
     void set_charmap(int i);
     void select_charmap(unsigned long i);
     void set_text(std::u32string_view codepoints, double angle, FT_Int32 flags,
-                  std::vector<double> &xys);
+                  LanguageType languages, std::vector<double> &xys);
     int get_kerning(FT_UInt left, FT_UInt right, FT_Kerning_Mode mode, bool fallback);
     int get_kerning(FT_UInt left, FT_UInt right, FT_Kerning_Mode mode, FT_Vector &delta);
     void set_kerning_factor(int factor);
diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp
index 4f3c2fd00d52..6ddb06a97f4b 100644
--- a/src/ft2font_wrapper.cpp
+++ b/src/ft2font_wrapper.cpp
@@ -705,7 +705,8 @@ const char *PyFT2Font_set_text__doc__ = R"""(
 
 static py::array_t<double>
 PyFT2Font_set_text(PyFT2Font *self, std::u32string_view text, double angle = 0.0,
-                   std::variant<LoadFlags, FT_Int32> flags_or_int = LoadFlags::FORCE_AUTOHINT)
+                   std::variant<LoadFlags, FT_Int32> flags_or_int = LoadFlags::FORCE_AUTOHINT,
+                   py::object language_obj = py::none())
 {
     std::vector<double> xys;
     LoadFlags flags;
@@ -725,7 +726,29 @@ PyFT2Font_set_text(PyFT2Font *self, std::u32string_view text, double angle = 0.0
         throw py::type_error("flags must be LoadFlags or int");
     }
 
-    self->x->set_text(text, angle, static_cast<FT_Int32>(flags), xys);
+    FT2Font::LanguageType languages;
+    if (py::isinstance<std::string>(language_obj)) {
+        languages = std::vector<FT2Font::LanguageRange>{
+            FT2Font::LanguageRange{language_obj.cast<std::string>(), 0, text.size()}
+        };
+    } else if (py::isinstance<py::list>(language_obj)) {
+        languages = std::vector<FT2Font::LanguageRange>{};
+
+        for (py::handle lang_range_obj : language_obj.cast<py::list>()) {
+            if (!py::isinstance<py::tuple>(lang_range_obj)) {
+                throw py::type_error("languages must be str or list of tuple");
+            }
+
+            auto lang_range = lang_range_obj.cast<py::tuple>();
+            auto lang_str = lang_range[0].cast<std::string>();
+            auto start = lang_range[1].cast<size_t>();
+            auto end = lang_range[2].cast<size_t>();
+
+            languages->emplace_back(lang_str, start, end);
+        }
+    }
+
+    self->x->set_text(text, angle, static_cast<FT_Int32>(flags), languages, xys);
 
     py::ssize_t dims[] = { static_cast<py::ssize_t>(xys.size()) / 2, 2 };
     py::array_t<double> result(dims);
@@ -1752,6 +1775,7 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
              PyFT2Font_get_kerning__doc__)
         .def("set_text", &PyFT2Font_set_text,
              "string"_a, "angle"_a=0.0, "flags"_a=LoadFlags::FORCE_AUTOHINT,
+             "language"_a=py::none(),
              PyFT2Font_set_text__doc__)
         .def("_get_fontmap", &PyFT2Font_get_fontmap, "string"_a,
              PyFT2Font_get_fontmap__doc__)