diff --git a/AGENTS.md b/AGENTS.md
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -95,7 +95,8 @@ means adding its row to every catalog marked `complete_menus` (the `*.tsv` files
 the English text, never through another catalog. Product and app names (ArtCraft Launcher, Craft
 App Suite, PhotoCraft…) stay in English.
 A CJK language is drawn with the system's own CJK font (`crates/ui-egui/src/cjk_font.rs`, desktop
-only; English where there is none): never bundle a CJK font, they are 10 MB and more.
+only; English where there is none): never bundle a CJK font, they are 10 MB and more. Georgian
+borrows the system's Georgian font the same way.
 
 ### Other rules
 
diff --git a/crates/ui-egui/src/cjk_font.rs b/crates/ui-egui/src/cjk_font.rs
--- a/crates/ui-egui/src/cjk_font.rs
+++ b/crates/ui-egui/src/cjk_font.rs
@@ -1,6 +1,8 @@
 //! A CJK font from the operating system, for the CJK interface languages. The bundled faces have
 //! no CJK glyphs, and a CJK font is too large to ship (10 MB or more), so the desktop build
 //! borrows the one every system that can show them already has. Desktop only.
+//!
+//! Georgian, which the bundled faces don't draw either, borrows its system face the same way.
 
 use std::path::{Path, PathBuf};
 
@@ -40,6 +42,7 @@ fn script_name(script: Script) -> &'static str {
     match script {
         Script::Chinese => "Chinese",
         Script::Japanese => "Japanese",
+        Script::Georgian => "Georgian",
     }
 }
 
@@ -48,6 +51,7 @@ fn script_families(script: Script) -> &'static str {
     match script {
         Script::Chinese => "PingFang SC, Hiragino Sans GB, Heiti SC, Microsoft YaHei, SimSun, Noto Sans CJK SC, WenQuanYi and MiSans",
         Script::Japanese => "Hiragino Sans, Yu Gothic, Meiryo, MS Gothic, Noto Sans CJK JP, Noto Sans JP, IPAGothic and TakaoGothic",
+        Script::Georgian => "SF Georgian, Arial Unicode MS, Sylfaen, Noto Sans Georgian and DejaVu Sans",
     }
 }
 
@@ -76,6 +80,11 @@ pub(crate) fn load(script: Script) -> Option<FontData> {
             y_offset_factor: (body_ascent - face_ascent) + 0.5 * (face_height - body_height),
             ..Default::default()
         };
+        if matches!(script, Script::Georgian) {
+            // Upper-case labels use Mtavruli only where this face draws it.
+            let caps = FontRef::from_index(&bytes, index).is_ok_and(|f| ('\u{1c90}'..='\u{1cba}').all(|c| f.charmap().map(c).is_some()));
+            crate::widgets::set_georgian_caps(caps);
+        }
         let mut font = FontData::from_owned(bytes).tweak(tweak);
         font.index = index;
         return Some(font);
@@ -123,6 +132,7 @@ fn candidates(script: Script) -> Vec<Candidate> {
     match script {
         Script::Chinese => chinese_candidates(),
         Script::Japanese => japanese_candidates(),
+        Script::Georgian => georgian_candidates(),
     }
 }
 
@@ -305,6 +315,50 @@ fn japanese_candidates() -> Vec<Candidate> {
     out
 }
 
+/// Where each system keeps a Georgian font, best first. These faces are small (about 0.1–1 MB),
+/// except Arial Unicode MS, which macOS ships too.
+fn georgian_candidates() -> Vec<Candidate> {
+    let mut out = Vec::new();
+    let mut add = |path: PathBuf, family: &'static str| out.push(Candidate { path, family });
+    #[cfg(target_os = "macos")]
+    {
+        // SF Georgian is the system UI face for Georgian; its family name starts with a dot, as
+        // macOS's hidden UI faces do.
+        add("/System/Library/Fonts/SFGeorgian.ttf".into(), ".SF Georgian");
+        add("/System/Library/Fonts/Supplemental/Arial Unicode.ttf".into(), "Arial Unicode MS");
+        add("/Library/Fonts/Arial Unicode.ttf".into(), "Arial Unicode MS");
+    }
+    #[cfg(target_os = "windows")]
+    {
+        let root = std::env::var_os("SystemRoot").map_or_else(|| PathBuf::from(r"C:\Windows"), PathBuf::from);
+        add(root.join("Fonts").join("sylfaen.ttf"), "Sylfaen");
+    }
+    #[cfg(all(unix, not(target_os = "macos")))]
+    {
+        // Debian/Ubuntu, Arch, Fedora (static and variable) and FreeBSD's ports.
+        for path in [
+            "/usr/share/fonts/truetype/noto/NotoSansGeorgian-Regular.ttf",
+            "/usr/share/fonts/noto/NotoSansGeorgian-Regular.ttf",
+            "/usr/share/fonts/google-noto/NotoSansGeorgian-Regular.ttf",
+            "/usr/share/fonts/google-noto-vf/NotoSansGeorgian[wght].ttf",
+            "/usr/local/share/fonts/noto/NotoSansGeorgian-Regular.ttf",
+        ] {
+            add(path.into(), "Noto Sans Georgian");
+        }
+        // DejaVu Sans draws Georgian too, and most desktops have it.
+        for path in [
+            "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
+            "/usr/share/fonts/TTF/DejaVuSans.ttf",
+            "/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf",
+            "/usr/share/fonts/dejavu/DejaVuSans.ttf",
+            "/usr/local/share/fonts/dejavu/DejaVuSans.ttf",
+        ] {
+            add(path.into(), "DejaVu Sans");
+        }
+    }
+    out
+}
+
 /// `file` in macOS's downloaded font assets:
 /// `/System/Library/AssetsV2/com_apple_MobileAsset_Font*/*.asset/AssetData/<file>`.
 #[cfg(target_os = "macos")]
diff --git a/crates/ui-egui/src/i18n/lang.rs b/crates/ui-egui/src/i18n/lang.rs
--- a/crates/ui-egui/src/i18n/lang.rs
+++ b/crates/ui-egui/src/i18n/lang.rs
@@ -7,17 +7,18 @@ use super::detect::system_lang;
 
 /// The writing system a language is drawn in, and so the system font it needs. Languages that
 /// share one can share a face; Simplified and Traditional Chinese do not (they want different
-/// faces), so each script is its own. The bundled faces have no CJK glyphs, so a language with a
-/// script borrows a system font for it (`crate::theme::cjk_font`).
+/// faces), so each script is its own. The bundled faces have no CJK or Georgian glyphs, so a
+/// language with a script borrows a system font for it (`crate::theme::cjk_font`).
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum Script {
     Chinese,
     Japanese,
+    Georgian,
 }
 
 impl Script {
     /// Every script a language can be written in.
-    pub const ALL: [Script; 2] = [Script::Chinese, Script::Japanese];
+    pub const ALL: [Script; 3] = [Script::Chinese, Script::Japanese, Script::Georgian];
 }
 
 /// One supported UI language.
@@ -29,7 +30,8 @@ pub struct LangInfo {
     pub name: &'static str,
     /// The language's name in English, shown instead where its own script can't be drawn.
     pub english_name: &'static str,
-    /// The writing system this language is drawn in, or `None` if it needs no system CJK font.
+    /// The writing system this language is drawn in, or `None` if it needs no system font
+    /// (CJK, Georgian).
     pub script: Option<Script>,
     /// Catalog file contents (empty for the built-in English).
     pub source: &'static str,
@@ -295,12 +297,12 @@ impl Lang {
         self.0.english_name
     }
 
-    /// Does drawing this language need a CJK font from the system?
+    /// Does drawing this language need a font from the system (CJK, Georgian)?
     pub fn needs_cjk_font(self) -> bool {
         self.0.script.is_some()
     }
 
-    /// The writing system this language is drawn in, if it needs a system CJK font.
+    /// The writing system this language is drawn in, if it needs a system font.
     pub fn script(self) -> Option<Script> {
         self.0.script
     }
diff --git a/crates/ui-egui/src/settings_dialog.rs b/crates/ui-egui/src/settings_dialog.rs
--- a/crates/ui-egui/src/settings_dialog.rs
+++ b/crates/ui-egui/src/settings_dialog.rs
@@ -159,7 +159,7 @@ impl LauncherUi {
         if pref.needs_cjk_font() && !theme::cjk_font_available(pref) {
             // In English on purpose: the language asked for can't be drawn.
             let text = "No font for this language was found on this system, so the interface stays in English. \
-                        Installing one (for example Noto Sans CJK) and restarting fixes it.";
+                        Installing one (for example Noto Sans CJK or Noto Sans Georgian) and restarting fixes it.";
             ui.add(egui::Label::new(RichText::new(text).size(12.0).color(t.warn)).wrap());
             ui.add_space(14.0);
         }
diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs
--- a/crates/ui-egui/src/widgets.rs
+++ b/crates/ui-egui/src/widgets.rs
@@ -4,6 +4,7 @@
 //! Upper-case text is drawn upper case but announced to screen readers as written.
 
 use std::sync::Arc;
+use std::sync::atomic::{AtomicBool, Ordering};
 
 use egui::{Color32, CornerRadius, Galley, Rect, Response, RichText, Sense, Stroke, Ui, vec2};
 
@@ -20,10 +21,37 @@ pub fn tone_color(t: &Tokens, tone: Tone) -> Color32 {
     }
 }
 
+/// Whether the Georgian face in use draws Mtavruli (U+1C90–U+1CBF), the capitals Unicode maps
+/// Georgian letters to. Set when the face loads; until then, and without one, Georgian stays as
+/// written, since a face without them (DejaVu Sans, older Sylfaen) would draw boxes.
+static GEORGIAN_CAPS: AtomicBool = AtomicBool::new(false);
+
+#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
+pub(crate) fn set_georgian_caps(on: bool) {
+    GEORGIAN_CAPS.store(on, Ordering::Relaxed);
+}
+
+/// `text` in upper case. Georgian goes to Mtavruli only when the face draws it.
+fn upper(text: &str) -> String {
+    upper_with(text, GEORGIAN_CAPS.load(Ordering::Relaxed))
+}
+
+/// [`upper`] with the face's Georgian capitals given rather than read from [`GEORGIAN_CAPS`].
+fn upper_with(text: &str, georgian_caps: bool) -> String {
+    text.chars().fold(String::with_capacity(text.len()), |mut out, c| {
+        if !georgian_caps && ('\u{10d0}'..='\u{10ff}').contains(&c) {
+            out.push(c);
+        } else {
+            out.extend(c.to_uppercase());
+        }
+        out
+    })
+}
+
 /// `text` laid out upper case in `family`, `spacing` points between letters.
 pub fn caps(ui: &Ui, text: &str, size: f32, family: egui::FontFamily, spacing: f32, color: Color32) -> Arc<Galley> {
     let format = egui::TextFormat { font_id: egui::FontId::new(size, family), color, extra_letter_spacing: spacing, ..Default::default() };
-    ui.painter().layout_job(egui::text::LayoutJob::single_section(text.to_uppercase(), format))
+    ui.painter().layout_job(egui::text::LayoutJob::single_section(upper(text), format))
 }
 
 /// A `.hud-label`: 11 px Geist Mono, upper case, widely spaced.
@@ -60,7 +88,7 @@ pub fn hud_elided(ui: &mut Ui, text: &str, color: Color32, max_width: f32) -> Re
         extra_letter_spacing: theme::HUD_SPACING,
         ..Default::default()
     };
-    let mut job = egui::text::LayoutJob::single_section(text.to_uppercase(), format);
+    let mut job = egui::text::LayoutJob::single_section(upper(text), format);
     job.wrap = egui::text::TextWrapping::truncate_at_width(max_width.min(ui.available_width()).max(0.0));
     let galley = ui.painter().layout_job(job);
     let elided = galley.elided;
@@ -764,4 +792,12 @@ mod tests {
             }
         }
     }
+
+    #[test]
+    fn upper_keeps_georgian_unless_the_face_has_capitals() {
+        // The capability is passed in: `GEORGIAN_CAPS` is shared with tests that load a face.
+        assert_eq!(super::upper_with("Update all (2)", false), "UPDATE ALL (2)");
+        assert_eq!(super::upper_with("ყველას განახლება (2) · ArtCraft", false), "ყველას განახლება (2) · ARTCRAFT");
+        assert_eq!(super::upper_with("ყველას განახლება · ArtCraft", true), "ᲧᲕᲔᲚᲐᲡ ᲒᲐᲜᲐᲮᲚᲔᲑᲐ · ARTCRAFT");
+    }
 }
