Skip to main content

Overview

Excalidraw supports over 30 languages out of the box. You can easily set the language for the editor interface, and Excalidraw handles all UI translations automatically.

Quick Start

Setting the Language

Use the langCode prop to set the interface language:
import { Excalidraw } from "@excalidraw/excalidraw";

function App() {
  return (
    <Excalidraw langCode="es-ES" />
  );
}

Supported Languages

Excalidraw includes 30+ languages with at least 85% translation completion:
import { Excalidraw } from "@excalidraw/excalidraw";

// English (default)
<Excalidraw langCode="en" />

// Spanish
<Excalidraw langCode="es-ES" />

// French
<Excalidraw langCode="fr-FR" />

// German
<Excalidraw langCode="de-DE" />

// Italian
<Excalidraw langCode="it-IT" />

// Portuguese (Brazil)
<Excalidraw langCode="pt-BR" />

// Portuguese (Portugal)
<Excalidraw langCode="pt-PT" />
From packages/excalidraw/i18n.ts:21-75, only languages with at least 85% completion (defined by COMPLETION_THRESHOLD) are included by default.

Available Languages List

Importing Language Data

Access the full list of supported languages:
import { Excalidraw, languages, defaultLang } from "@excalidraw/excalidraw";

function App() {
  return (
    <>
      <select onChange={(e) => setLang(e.target.value)}>
        {languages.map((lang) => (
          <option key={lang.code} value={lang.code}>
            {lang.label}
          </option>
        ))}
      </select>
      <Excalidraw langCode={lang} />
    </>
  );
}

Language Object Structure

Each language object contains:
interface Language {
  code: string;    // e.g., "en", "es-ES"
  label: string;   // e.g., "English", "Español"
  rtl?: boolean;   // true for right-to-left languages
}
Example from source (packages/excalidraw/i18n.ts:11-15):
export interface Language {
  code: string;
  label: string;
  rtl?: boolean;
}

Right-to-Left (RTL) Languages

RTL Support

Excalidraw automatically handles RTL languages:
import { Excalidraw } from "@excalidraw/excalidraw";

function App() {
  return (
    <Excalidraw
      langCode="ar-SA" // Arabic - automatically sets RTL
    />
  );
}
From packages/excalidraw/i18n.ts:92-95, when you set an RTL language, Excalidraw automatically updates document.documentElement.dir to "rtl" and sets the language attribute.

Supported RTL Languages

  • Arabic: ar-SA
  • Hebrew: he-IL
  • Persian: fa-IR

Dynamic Language Switching

User Language Selector

Let users choose their preferred language:
import { Excalidraw, languages } from "@excalidraw/excalidraw";
import { useState } from "react";

function App() {
  const [langCode, setLangCode] = useState("en");

  return (
    <div style={{ height: "100vh" }}>
      <div style={{ padding: "10px" }}>
        <label>
          Language:
          <select
            value={langCode}
            onChange={(e) => setLangCode(e.target.value)}
          >
            {languages.map((lang) => (
              <option key={lang.code} value={lang.code}>
                {lang.label}
              </option>
            ))}
          </select>
        </label>
      </div>
      <Excalidraw langCode={langCode} />
    </div>
  );
}

Browser Language Detection

Automatic language detection based on browser settings:
import { Excalidraw, languages, defaultLang } from "@excalidraw/excalidraw";
import { useState, useEffect } from "react";

function App() {
  const [langCode, setLangCode] = useState(defaultLang.code);

  useEffect(() => {
    // Get browser language
    const browserLang = navigator.language;
    
    // Find matching language in supported languages
    const matchedLang = languages.find(
      (lang) => lang.code === browserLang || lang.code.startsWith(browserLang.split("-")[0])
    );

    if (matchedLang) {
      setLangCode(matchedLang.code);
    }
  }, []);

  return <Excalidraw langCode={langCode} />;
}

Persisting Language Preference

Save and restore the user’s language choice:
import { Excalidraw, languages } from "@excalidraw/excalidraw";
import { useState, useEffect } from "react";

function App() {
  const [langCode, setLangCode] = useState(() => {
    // Load from localStorage
    const saved = localStorage.getItem("excalidraw-lang");
    return saved || "en";
  });

  useEffect(() => {
    // Save to localStorage when language changes
    localStorage.setItem("excalidraw-lang", langCode);
  }, [langCode]);

  return (
    <>
      <select value={langCode} onChange={(e) => setLangCode(e.target.value)}>
        {languages.map((lang) => (
          <option key={lang.code} value={lang.code}>
            {lang.label}
          </option>
        ))}
      </select>
      <Excalidraw langCode={langCode} />
    </>
  );
}

Using the i18n Hook

useI18n Hook

For custom components that need translations:
import { useI18n } from "@excalidraw/excalidraw";

function CustomComponent() {
  const { t, langCode } = useI18n();

  return (
    <div>
      <p>Current language: {langCode}</p>
      {/* Use t() function for translations if you have access to translation keys */}
    </div>
  );
}
From packages/excalidraw/i18n.ts:165-172, the useI18n hook should be used in components rendered as Excalidraw children or memoized internal components.

Translation Function

Using the t() Function

The translation function from the source code (packages/excalidraw/i18n.ts:127-160):
export const t = (
  path: string,
  replacement?: { [key: string]: string | number } | null,
  fallback?: string,
) => {
  // Returns translated string based on current language
}
Example usage with replacements:
// With variable replacement
t("labels.exportedElements", { count: 5 })
// Returns: "5 elements exported" (translated)

// With fallback
t("labels.customLabel", null, "Default text")
// Returns: "Default text" if translation not found

Advanced Configuration

Language Setting Function

Programmatically set the language:
import { setLanguage, languages } from "@excalidraw/excalidraw";

// Set language (typically handled internally by Excalidraw)
const spanish = languages.find(lang => lang.code === "es-ES");
if (spanish) {
  await setLanguage(spanish);
}
The setLanguage function is typically called internally by Excalidraw. Use the langCode prop instead for standard use cases.

Default Language

Get the default language:
import { defaultLang } from "@excalidraw/excalidraw";

console.log(defaultLang);
// Output: { code: "en", label: "English" }

Initial Language in AppState

Setting Initial Language

You can also set the language through initialData:
import { Excalidraw } from "@excalidraw/excalidraw";

function App() {
  return (
    <Excalidraw
      langCode="fr-FR"
      initialData={{
        appState: {
          // Other app state properties
        },
      }}
    />
  );
}

Best Practices

1

Detect browser language

Use navigator.language to detect and set the initial language.
2

Provide language selector

Give users an easy way to change the language.
3

Persist preference

Save the user’s language choice to localStorage.
4

Test RTL languages

Ensure your custom UI components work correctly with RTL languages.
5

Fallback to English

Always have English as a fallback option.

Complete Example

Full i18n Implementation

import { Excalidraw, languages, defaultLang } from "@excalidraw/excalidraw";
import { useState, useEffect } from "react";

function App() {
  const [langCode, setLangCode] = useState(() => {
    // Try to load from localStorage
    const saved = localStorage.getItem("excalidraw-lang");
    if (saved) return saved;

    // Try to detect browser language
    const browserLang = navigator.language;
    const matched = languages.find(
      (lang) => lang.code === browserLang || lang.code.startsWith(browserLang.split("-")[0])
    );

    return matched?.code || defaultLang.code;
  });

  useEffect(() => {
    localStorage.setItem("excalidraw-lang", langCode);
  }, [langCode]);

  const currentLang = languages.find((lang) => lang.code === langCode);

  return (
    <div style={{ height: "100vh", display: "flex", flexDirection: "column" }}>
      <header style={{ padding: "10px", background: "#f0f0f0" }}>
        <label>
          {currentLang?.rtl ? "اللغة:" : "Language:"}
          <select
            value={langCode}
            onChange={(e) => setLangCode(e.target.value)}
            style={{ marginLeft: currentLang?.rtl ? 0 : "10px", marginRight: currentLang?.rtl ? "10px" : 0 }}
          >
            {languages.map((lang) => (
              <option key={lang.code} value={lang.code}>
                {lang.label}
              </option>
            ))}
          </select>
        </label>
      </header>
      <div style={{ flex: 1 }}>
        <Excalidraw langCode={langCode} />
      </div>
    </div>
  );
}

export default App;

Next Steps