summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHeinenen <37484430+Heinenen@users.noreply.github.com>2024-04-19 16:26:49 +0200
committerGitHub <noreply@github.com>2024-04-19 14:26:49 +0000
commitd65d9d0fe648415bb61e90042c3a98d865391b95 (patch)
tree7effc8d9d86f4f62f86e6d065100931e081ff908
parenta6a331bd2dbcebbf88901e583cc734b5c6437bcb (diff)
Localization with custom key-value format (#3728)
-rw-r--r--crates/typst/src/math/equation.rs41
-rw-r--r--crates/typst/src/model/bibliography.rs38
-rw-r--r--crates/typst/src/model/heading.rs41
-rw-r--r--crates/typst/src/model/outline.rs41
-rw-r--r--crates/typst/src/model/table.rs37
-rw-r--r--crates/typst/src/text/lang.rs109
-rw-r--r--crates/typst/src/text/raw.rs40
-rw-r--r--crates/typst/src/visualize/image/mod.rs40
-rw-r--r--crates/typst/translations/ar.txt7
-rw-r--r--crates/typst/translations/cs.txt7
-rw-r--r--crates/typst/translations/da.txt7
-rw-r--r--crates/typst/translations/de.txt7
-rw-r--r--crates/typst/translations/en.txt7
-rw-r--r--crates/typst/translations/es.txt7
-rw-r--r--crates/typst/translations/et.txt7
-rw-r--r--crates/typst/translations/fi.txt7
-rw-r--r--crates/typst/translations/fr.txt7
-rw-r--r--crates/typst/translations/gr.txt7
-rw-r--r--crates/typst/translations/hu.txt7
-rw-r--r--crates/typst/translations/it.txt7
-rw-r--r--crates/typst/translations/ja.txt7
-rw-r--r--crates/typst/translations/nb.txt7
-rw-r--r--crates/typst/translations/nl.txt7
-rw-r--r--crates/typst/translations/nn.txt7
-rw-r--r--crates/typst/translations/pl.txt7
-rw-r--r--crates/typst/translations/pt-PT.txt7
-rw-r--r--crates/typst/translations/pt.txt7
-rw-r--r--crates/typst/translations/ro.txt8
-rw-r--r--crates/typst/translations/ru.txt7
-rw-r--r--crates/typst/translations/sl.txt7
-rw-r--r--crates/typst/translations/sq.txt7
-rw-r--r--crates/typst/translations/sr.txt7
-rw-r--r--crates/typst/translations/sv.txt7
-rw-r--r--crates/typst/translations/tl.txt7
-rw-r--r--crates/typst/translations/tr.txt7
-rw-r--r--crates/typst/translations/ua.txt7
-rw-r--r--crates/typst/translations/vi.txt8
-rw-r--r--crates/typst/translations/zh-TW.txt7
-rw-r--r--crates/typst/translations/zh.txt7
-rw-r--r--tests/ref/text-language-fallback-english.pngbin0 -> 922 bytes
-rw-r--r--tests/suite/text/lang.typ6
41 files changed, 351 insertions, 261 deletions
diff --git a/crates/typst/src/math/equation.rs b/crates/typst/src/math/equation.rs
index c5cb9132..4e844d07 100644
--- a/crates/typst/src/math/equation.rs
+++ b/crates/typst/src/math/equation.rs
@@ -19,10 +19,9 @@ use crate::math::{
use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement};
use crate::syntax::Span;
use crate::text::{
- families, variant, Font, FontFamily, FontList, FontWeight, Lang, LocalName, Region,
- TextElem,
+ families, variant, Font, FontFamily, FontList, FontWeight, LocalName, TextElem,
};
-use crate::util::{option_eq, NonZeroExt, Numeric};
+use crate::util::{NonZeroExt, Numeric};
use crate::World;
/// A mathematical equation.
@@ -302,41 +301,7 @@ impl Count for Packed<EquationElem> {
}
impl LocalName for Packed<EquationElem> {
- fn local_name(lang: Lang, region: Option<Region>) -> &'static str {
- match lang {
- Lang::ALBANIAN => "Ekuacion",
- Lang::ARABIC => "معادلة",
- Lang::BOKMÅL => "Ligning",
- Lang::CATALAN => "Equació",
- Lang::CHINESE if option_eq(region, "TW") => "方程式",
- Lang::CHINESE => "公式",
- Lang::CZECH => "Rovnice",
- Lang::DANISH => "Ligning",
- Lang::DUTCH => "Vergelijking",
- Lang::ESTONIAN => "Valem",
- Lang::FILIPINO => "Ekwasyon",
- Lang::FINNISH => "Yhtälö",
- Lang::FRENCH => "Équation",
- Lang::GERMAN => "Gleichung",
- Lang::GREEK => "Εξίσωση",
- Lang::HUNGARIAN => "Egyenlet",
- Lang::ITALIAN => "Equazione",
- Lang::NYNORSK => "Likning",
- Lang::POLISH => "Równanie",
- Lang::PORTUGUESE => "Equação",
- Lang::ROMANIAN => "Ecuația",
- Lang::RUSSIAN => "Уравнение",
- Lang::SERBIAN => "Једначина",
- Lang::SLOVENIAN => "Enačba",
- Lang::SPANISH => "Ecuación",
- Lang::SWEDISH => "Ekvation",
- Lang::TURKISH => "Denklem",
- Lang::UKRAINIAN => "Рівняння",
- Lang::VIETNAMESE => "Phương trình",
- Lang::JAPANESE => "式",
- Lang::ENGLISH | _ => "Equation",
- }
- }
+ const KEY: &'static str = "equation";
}
impl Refable for Packed<EquationElem> {
diff --git a/crates/typst/src/model/bibliography.rs b/crates/typst/src/model/bibliography.rs
index 576d1171..b7a10b92 100644
--- a/crates/typst/src/model/bibliography.rs
+++ b/crates/typst/src/model/bibliography.rs
@@ -40,7 +40,7 @@ use crate::syntax::{Span, Spanned};
use crate::text::{
FontStyle, Lang, LocalName, Region, SubElem, SuperElem, TextElem, WeightDelta,
};
-use crate::util::{option_eq, LazyHash, NonZeroExt, PicoStr};
+use crate::util::{LazyHash, NonZeroExt, PicoStr};
use crate::World;
/// A bibliography / reference listing.
@@ -283,41 +283,7 @@ impl ShowSet for Packed<BibliographyElem> {
}
impl LocalName for Packed<BibliographyElem> {
- fn local_name(lang: Lang, region: Option<Region>) -> &'static str {
- match lang {
- Lang::ALBANIAN => "Bibliografi",
- Lang::ARABIC => "المراجع",
- Lang::BOKMÅL => "Bibliografi",
- Lang::CATALAN => "Bibliografia",
- Lang::CHINESE if option_eq(region, "TW") => "書目",
- Lang::CHINESE => "参考文献",
- Lang::CZECH => "Bibliografie",
- Lang::DANISH => "Bibliografi",
- Lang::DUTCH => "Bibliografie",
- Lang::ESTONIAN => "Viited",
- Lang::FILIPINO => "Bibliograpiya",
- Lang::FINNISH => "Viitteet",
- Lang::FRENCH => "Bibliographie",
- Lang::GERMAN => "Bibliographie",
- Lang::GREEK => "Βιβλιογραφία",
- Lang::HUNGARIAN => "Irodalomjegyzék",
- Lang::ITALIAN => "Bibliografia",
- Lang::NYNORSK => "Bibliografi",
- Lang::POLISH => "Bibliografia",
- Lang::PORTUGUESE => "Bibliografia",
- Lang::ROMANIAN => "Bibliografie",
- Lang::RUSSIAN => "Библиография",
- Lang::SERBIAN => "Литература",
- Lang::SLOVENIAN => "Literatura",
- Lang::SPANISH => "Bibliografía",
- Lang::SWEDISH => "Bibliografi",
- Lang::TURKISH => "Kaynakça",
- Lang::UKRAINIAN => "Бібліографія",
- Lang::VIETNAMESE => "Tài liệu tham khảo",
- Lang::JAPANESE => "参考文献",
- Lang::ENGLISH | _ => "Bibliography",
- }
- }
+ const KEY: &'static str = "bibliography";
}
/// A loaded bibliography.
diff --git a/crates/typst/src/model/heading.rs b/crates/typst/src/model/heading.rs
index 314f5482..d3976948 100644
--- a/crates/typst/src/model/heading.rs
+++ b/crates/typst/src/model/heading.rs
@@ -9,8 +9,8 @@ use crate::foundations::{
use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
use crate::layout::{BlockElem, Em, HElem, VElem};
use crate::model::{Numbering, Outlinable, Refable, Supplement};
-use crate::text::{FontWeight, Lang, LocalName, Region, SpaceElem, TextElem, TextSize};
-use crate::util::{option_eq, NonZeroExt};
+use crate::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize};
+use crate::util::NonZeroExt;
/// A section heading.
///
@@ -294,40 +294,5 @@ impl Outlinable for Packed<HeadingElem> {
}
impl LocalName for Packed<HeadingElem> {
- fn local_name(lang: Lang, region: Option<Region>) -> &'static str {
- match lang {
- Lang::ALBANIAN => "Kapitull",
- Lang::ARABIC => "الفصل",
- Lang::BOKMÅL => "Kapittel",
- Lang::CATALAN => "Secció",
- Lang::CHINESE if option_eq(region, "TW") => "小節",
- Lang::CHINESE => "小节",
- Lang::CZECH => "Kapitola",
- Lang::DANISH => "Afsnit",
- Lang::DUTCH => "Hoofdstuk",
- Lang::ESTONIAN => "Peatükk",
- Lang::FILIPINO => "Seksyon",
- Lang::FINNISH => "Osio",
- Lang::FRENCH => "Chapitre",
- Lang::GERMAN => "Abschnitt",
- Lang::GREEK => "Κεφάλαιο",
- Lang::HUNGARIAN => "Fejezet",
- Lang::ITALIAN => "Sezione",
- Lang::NYNORSK => "Kapittel",
- Lang::POLISH => "Sekcja",
- Lang::PORTUGUESE if option_eq(region, "PT") => "Secção",
- Lang::PORTUGUESE => "Seção",
- Lang::ROMANIAN => "Secțiunea",
- Lang::RUSSIAN => "Раздел",
- Lang::SERBIAN => "Поглавље",
- Lang::SLOVENIAN => "Poglavje",
- Lang::SPANISH => "Sección",
- Lang::SWEDISH => "Kapitel",
- Lang::TURKISH => "Bölüm",
- Lang::UKRAINIAN => "Розділ",
- Lang::VIETNAMESE => "Phần", // TODO: This may be wrong.
- Lang::JAPANESE => "節",
- Lang::ENGLISH | _ => "Section",
- }
- }
+ const KEY: &'static str = "heading";
}
diff --git a/crates/typst/src/model/outline.rs b/crates/typst/src/model/outline.rs
index a85d1cdf..e9230656 100644
--- a/crates/typst/src/model/outline.rs
+++ b/crates/typst/src/model/outline.rs
@@ -15,8 +15,8 @@ use crate::model::{
Destination, HeadingElem, NumberingPattern, ParElem, ParbreakElem, Refable,
};
use crate::syntax::Span;
-use crate::text::{Lang, LinebreakElem, LocalName, Region, SpaceElem, TextElem};
-use crate::util::{option_eq, NonZeroExt};
+use crate::text::{LinebreakElem, LocalName, SpaceElem, TextElem};
+use crate::util::NonZeroExt;
/// A table of contents, figures, or other elements.
///
@@ -272,42 +272,7 @@ impl ShowSet for Packed<OutlineElem> {
}
impl LocalName for Packed<OutlineElem> {
- fn local_name(lang: Lang, region: Option<Region>) -> &'static str {
- match lang {
- Lang::ALBANIAN => "Përmbajtja",
- Lang::ARABIC => "المحتويات",
- Lang::BOKMÅL => "Innhold",
- Lang::CATALAN => "Índex",
- Lang::CHINESE if option_eq(region, "TW") => "目錄",
- Lang::CHINESE => "目录",
- Lang::CZECH => "Obsah",
- Lang::DANISH => "Indhold",
- Lang::DUTCH => "Inhoudsopgave",
- Lang::ESTONIAN => "Sisukord",
- Lang::FILIPINO => "Talaan ng mga Nilalaman",
- Lang::FINNISH => "Sisällys",
- Lang::FRENCH => "Table des matières",
- Lang::GERMAN => "Inhaltsverzeichnis",
- Lang::GREEK => "Περιεχόμενα",
- Lang::HUNGARIAN => "Tartalomjegyzék",
- Lang::ITALIAN => "Indice",
- Lang::NYNORSK => "Innhald",
- Lang::POLISH => "Spis treści",
- Lang::PORTUGUESE if option_eq(region, "PT") => "Índice",
- Lang::PORTUGUESE => "Sumário",
- Lang::ROMANIAN => "Cuprins",
- Lang::RUSSIAN => "Содержание",
- Lang::SERBIAN => "Садржај",
- Lang::SLOVENIAN => "Kazalo",
- Lang::SPANISH => "Índice",
- Lang::SWEDISH => "Innehåll",
- Lang::TURKISH => "İçindekiler",
- Lang::UKRAINIAN => "Зміст",
- Lang::VIETNAMESE => "Mục lục",
- Lang::JAPANESE => "目次",
- Lang::ENGLISH | _ => "Contents",
- }
- }
+ const KEY: &'static str = "outline";
}
/// Marks an element as being able to be outlined. This is used to implement the
diff --git a/crates/typst/src/model/table.rs b/crates/typst/src/model/table.rs
index 50578780..3a0c47c1 100644
--- a/crates/typst/src/model/table.rs
+++ b/crates/typst/src/model/table.rs
@@ -16,7 +16,7 @@ use crate::layout::{
};
use crate::model::Figurable;
use crate::syntax::Span;
-use crate::text::{Lang, LocalName, Region, TextElem};
+use crate::text::{LocalName, TextElem};
use crate::util::NonZeroExt;
use crate::visualize::{Paint, Stroke};
@@ -317,40 +317,7 @@ impl LayoutMultiple for Packed<TableElem> {
}
impl LocalName for Packed<TableElem> {
- fn local_name(lang: Lang, _: Option<Region>) -> &'static str {
- match lang {
- Lang::ALBANIAN => "Tabel",
- Lang::ARABIC => "جدول",
- Lang::BOKMÅL => "Tabell",
- Lang::CATALAN => "Taula",
- Lang::CHINESE => "表",
- Lang::CZECH => "Tabulka",
- Lang::DANISH => "Tabel",
- Lang::DUTCH => "Tabel",
- Lang::ESTONIAN => "Tabel",
- Lang::FILIPINO => "Talaan",
- Lang::FINNISH => "Taulukko",
- Lang::FRENCH => "Tableau",
- Lang::GERMAN => "Tabelle",
- Lang::GREEK => "Πίνακας",
- Lang::HUNGARIAN => "Táblázat",
- Lang::ITALIAN => "Tabella",
- Lang::NYNORSK => "Tabell",
- Lang::POLISH => "Tabela",
- Lang::PORTUGUESE => "Tabela",
- Lang::ROMANIAN => "Tabelul",
- Lang::RUSSIAN => "Таблица",
- Lang::SERBIAN => "Табела",
- Lang::SLOVENIAN => "Tabela",
- Lang::SPANISH => "Tabla",
- Lang::SWEDISH => "Tabell",
- Lang::TURKISH => "Tablo",
- Lang::UKRAINIAN => "Таблиця",
- Lang::VIETNAMESE => "Bảng",
- Lang::JAPANESE => "表",
- Lang::ENGLISH | _ => "Table",
- }
- }
+ const KEY: &'static str = "table";
}
impl Figurable for Packed<TableElem> {}
diff --git a/crates/typst/src/text/lang.rs b/crates/typst/src/text/lang.rs
index 111c929b..a67fa33a 100644
--- a/crates/typst/src/text/lang.rs
+++ b/crates/typst/src/text/lang.rs
@@ -1,3 +1,4 @@
+use std::collections::HashMap;
use std::str::FromStr;
use ecow::EcoString;
@@ -6,6 +7,46 @@ use crate::foundations::{cast, StyleChain};
use crate::layout::Dir;
use crate::text::TextElem;
+macro_rules! translation {
+ ($lang:literal) => {
+ ($lang, include_str!(concat!("../../translations/", $lang, ".txt")))
+ };
+}
+
+const TRANSLATIONS: [(&str, &str); 31] = [
+ translation!("ar"),
+ translation!("cs"),
+ translation!("da"),
+ translation!("de"),
+ translation!("en"),
+ translation!("es"),
+ translation!("et"),
+ translation!("fi"),
+ translation!("fr"),
+ translation!("gr"),
+ translation!("hu"),
+ translation!("it"),
+ translation!("ja"),
+ translation!("nb"),
+ translation!("nl"),
+ translation!("nn"),
+ translation!("pl"),
+ translation!("pt-PT"),
+ translation!("pt"),
+ translation!("ro"),
+ translation!("ru"),
+ translation!("sl"),
+ translation!("sq"),
+ translation!("sr"),
+ translation!("sv"),
+ translation!("tl"),
+ translation!("tr"),
+ translation!("ua"),
+ translation!("vi"),
+ translation!("zh-TW"),
+ translation!("zh"),
+];
+
/// An identifier for a natural language.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Lang([u8; 3], u8);
@@ -26,11 +67,13 @@ impl Lang {
pub const FRENCH: Self = Self(*b"fr ", 2);
pub const GERMAN: Self = Self(*b"de ", 2);
pub const GREEK: Self = Self(*b"gr ", 2);
+ pub const HUNGARIAN: Self = Self(*b"hu ", 2);
pub const ITALIAN: Self = Self(*b"it ", 2);
pub const JAPANESE: Self = Self(*b"ja ", 2);
pub const NYNORSK: Self = Self(*b"nn ", 2);
pub const POLISH: Self = Self(*b"pl ", 2);
pub const PORTUGUESE: Self = Self(*b"pt ", 2);
+ pub const ROMANIAN: Self = Self(*b"ro ", 2);
pub const RUSSIAN: Self = Self(*b"ru ", 2);
pub const SERBIAN: Self = Self(*b"sr ", 2);
pub const SLOVENIAN: Self = Self(*b"sl ", 2);
@@ -39,8 +82,6 @@ impl Lang {
pub const TURKISH: Self = Self(*b"tr ", 2);
pub const UKRAINIAN: Self = Self(*b"ua ", 2);
pub const VIETNAMESE: Self = Self(*b"vi ", 2);
- pub const HUNGARIAN: Self = Self(*b"hu ", 2);
- pub const ROMANIAN: Self = Self(*b"ro ", 2);
/// Return the language code as an all lowercase string slice.
pub fn as_str(&self) -> &str {
@@ -159,8 +200,13 @@ cast! {
/// The name with which an element is referenced.
pub trait LocalName {
+ /// The key of an element in order to get its localized name.
+ const KEY: &'static str;
+
/// Get the name in the given language and (optionally) region.
- fn local_name(lang: Lang, region: Option<Region>) -> &'static str;
+ fn local_name(lang: Lang, region: Option<Region>) -> &'static str {
+ localized_str(lang, region, Self::KEY)
+ }
/// Gets the local name from the style chain.
fn local_name_in(styles: StyleChain) -> &'static str
@@ -171,6 +217,63 @@ pub trait LocalName {
}
}
+/// Retrieves the localized string for a given language and region.
+/// Silently falls back to English if no fitting string exists for
+/// the given language + region. Panics if no fitting string exists
+/// in both given language + region and English.
+#[comemo::memoize]
+pub fn localized_str(lang: Lang, region: Option<Region>, key: &str) -> &'static str {
+ let lang_region_bundle = parse_language_bundle(lang, region).unwrap();
+ if let Some(str) = lang_region_bundle.get(key) {
+ return str;
+ }
+ let lang_bundle = parse_language_bundle(lang, None).unwrap();
+ if let Some(str) = lang_bundle.get(key) {
+ return str;
+ }
+ let english_bundle = parse_language_bundle(Lang::ENGLISH, None).unwrap();
+ english_bundle.get(key).unwrap()
+}
+
+/// Parses the translation file for a given language and region.
+/// Only returns an error if the language file is malformed.
+#[comemo::memoize]
+fn parse_language_bundle(
+ lang: Lang,
+ region: Option<Region>,
+) -> Result<HashMap<&'static str, &'static str>, &'static str> {
+ let language_tuple = TRANSLATIONS.iter().find(|it| it.0 == lang_str(lang, region));
+ let Some((_lang_name, language_file)) = language_tuple else {
+ return Ok(HashMap::new());
+ };
+
+ let mut bundle = HashMap::new();
+ let lines = language_file.trim().lines();
+ for line in lines {
+ if line.trim().starts_with('#') {
+ continue;
+ }
+ let (key, val) = line
+ .split_once('=')
+ .ok_or("malformed translation file: line without \"=\"")?;
+ let (key, val) = (key.trim(), val.trim());
+ if val.is_empty() {
+ return Err("malformed translation file: empty translation value");
+ }
+ let duplicate = bundle.insert(key.trim(), val.trim());
+ if duplicate.is_some() {
+ return Err("malformed translation file: duplicate key");
+ }
+ }
+ Ok(bundle)
+}
+
+/// Convert language + region to a string to be able to get a file name.
+fn lang_str(lang: Lang, region: Option<Region>) -> EcoString {
+ EcoString::from(lang.as_str())
+ + region.map_or_else(EcoString::new, |r| EcoString::from("-") + r.as_str())
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/crates/typst/src/text/raw.rs b/crates/typst/src/text/raw.rs
index dc288a66..ae7525be 100644
--- a/crates/typst/src/text/raw.rs
+++ b/crates/typst/src/text/raw.rs
@@ -9,6 +9,7 @@ use syntect::highlighting as synt;
use syntect::parsing::{SyntaxDefinition, SyntaxSet, SyntaxSetBuilder};
use unicode_segmentation::UnicodeSegmentation;
+use super::Lang;
use crate::diag::{At, FileError, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
@@ -19,10 +20,9 @@ use crate::layout::{BlockElem, Em, HAlignment};
use crate::model::{Figurable, ParElem};
use crate::syntax::{split_newlines, LinkedNode, Span, Spanned};
use crate::text::{
- FontFamily, FontList, Hyphenate, Lang, LinebreakElem, LocalName, Region,
- SmartQuoteElem, TextElem, TextSize,
+ FontFamily, FontList, Hyphenate, LinebreakElem, LocalName, SmartQuoteElem, TextElem,
+ TextSize,
};
-use crate::util::option_eq;
use crate::visualize::Color;
use crate::{syntax, World};
@@ -456,39 +456,7 @@ impl ShowSet for Packed<RawElem> {
}
impl LocalName for Packed<RawElem> {
- fn local_name(lang: Lang, region: Option<Region>) -> &'static str {
- match lang {
- Lang::ALBANIAN => "List",
- Lang::ARABIC => "قائمة",
- Lang::BOKMÅL => "Utskrift",
- Lang::CATALAN => "Llistat",
- Lang::CHINESE if option_eq(region, "TW") => "程式",
- Lang::CHINESE => "代码",
- Lang::CZECH => "Seznam",
- Lang::DANISH => "Liste",
- Lang::DUTCH => "Listing",
- Lang::ESTONIAN => "List",
- Lang::FILIPINO => "Listahan",
- Lang::FINNISH => "Listaus",
- Lang::FRENCH => "Liste",
- Lang::GERMAN => "Listing",
- Lang::GREEK => "Παράθεση",
- Lang::ITALIAN => "Codice",
- Lang::NYNORSK => "Utskrift",
- Lang::POLISH => "Program",
- Lang::ROMANIAN => "Listă", // TODO: I dunno
- Lang::RUSSIAN => "Листинг",
- Lang::SERBIAN => "Програм",
- Lang::SLOVENIAN => "Program",
- Lang::SPANISH => "Listado",
- Lang::SWEDISH => "Listing",
- Lang::TURKISH => "Liste",
- Lang::UKRAINIAN => "Лістинг",
- Lang::VIETNAMESE => "Chương trình", // TODO: This may be wrong.
- Lang::JAPANESE => "リスト",
- Lang::ENGLISH | _ => "Listing",
- }
- }
+ const KEY: &'static str = "raw";
}
impl Figurable for Packed<RawElem> {}
diff --git a/crates/typst/src/visualize/image/mod.rs b/crates/typst/src/visualize/image/mod.rs
index 2b80567d..c1715f7a 100644
--- a/crates/typst/src/visualize/image/mod.rs
+++ b/crates/typst/src/visualize/image/mod.rs
@@ -26,8 +26,8 @@ use crate::layout::{
use crate::loading::Readable;
use crate::model::Figurable;
use crate::syntax::{Span, Spanned};
-use crate::text::{families, Lang, LocalName, Region};
-use crate::util::{option_eq, LazyHash};
+use crate::text::{families, LocalName};
+use crate::util::LazyHash;
use crate::visualize::Path;
use crate::World;
@@ -264,41 +264,7 @@ impl LayoutSingle for Packed<ImageElem> {
}
impl LocalName for Packed<ImageElem> {
- fn local_name(lang: Lang, region: Option<Region>) -> &'static str {
- match lang {
- Lang::ALBANIAN => "Figurë",
- Lang::ARABIC => "شكل",
- Lang::BOKMÅL => "Figur",
- Lang::CATALAN => "Figura",
- Lang::CHINESE if option_eq(region, "TW") => "圖",
- Lang::CHINESE => "图",
- Lang::CZECH => "Obrázek",
- Lang::DANISH => "Figur",
- Lang::DUTCH => "Figuur",
- Lang::ESTONIAN => "Joonis",
- Lang::FILIPINO => "Pigura",
- Lang::FINNISH => "Kuva",
- Lang::FRENCH => "Fig.",
- Lang::GERMAN => "Abbildung",
- Lang::GREEK => "Σχήμα",
- Lang::HUNGARIAN => "Ábra",
- Lang::ITALIAN => "Figura",
- Lang::NYNORSK => "Figur",
- Lang::POLISH => "Rysunek",
- Lang::PORTUGUESE => "Figura",
- Lang::ROMANIAN => "Figura",
- Lang::RUSSIAN => "Рис.",
- Lang::SERBIAN => "Слика",
- Lang::SLOVENIAN => "Slika",
- Lang::SPANISH => "Figura",
- Lang::SWEDISH => "Figur",
- Lang::TURKISH => "Şekil",
- Lang::UKRAINIAN => "Рисунок",
- Lang::VIETNAMESE => "Hình",
- Lang::JAPANESE => "図",
- Lang::ENGLISH | _ => "Figure",
- }
- }
+ const KEY: &'static str = "figure";
}
impl Figurable for Packed<ImageElem> {}
diff --git a/crates/typst/translations/ar.txt b/crates/typst/translations/ar.txt
new file mode 100644
index 00000000..5c17d379
--- /dev/null
+++ b/crates/typst/translations/ar.txt
@@ -0,0 +1,7 @@
+figure = شكل
+table = جدول
+equation = معادلة
+bibliography = المراجع
+heading = الفصل
+outline = المحتويات
+raw = قائمة \ No newline at end of file
diff --git a/crates/typst/translations/cs.txt b/crates/typst/translations/cs.txt
new file mode 100644
index 00000000..13670586
--- /dev/null
+++ b/crates/typst/translations/cs.txt
@@ -0,0 +1,7 @@
+figure = Obrázek
+table = Tabulka
+equation = Rovnice
+bibliography = Bibliografie
+heading = Kapitola
+outline = Obsah
+raw = Seznam \ No newline at end of file
diff --git a/crates/typst/translations/da.txt b/crates/typst/translations/da.txt
new file mode 100644
index 00000000..9664c3b0
--- /dev/null
+++ b/crates/typst/translations/da.txt
@@ -0,0 +1,7 @@
+figure = Figur
+table = Tabel
+equation = Ligning
+bibliography = Bibliografi
+heading = Afsnit
+outline = Indhold
+raw = Liste \ No newline at end of file
diff --git a/crates/typst/translations/de.txt b/crates/typst/translations/de.txt
new file mode 100644
index 00000000..8ba72adb
--- /dev/null
+++ b/crates/typst/translations/de.txt
@@ -0,0 +1,7 @@
+figure = Abbildung
+table = Tabelle
+equation = Gleichung
+bibliography = Bibliographie
+heading = Abschnitt
+outline = Inhaltsverzeichnis
+raw = Listing \ No newline at end of file
diff --git a/crates/typst/translations/en.txt b/crates/typst/translations/en.txt
new file mode 100644
index 00000000..02e7accd
--- /dev/null
+++ b/crates/typst/translations/en.txt
@@ -0,0 +1,7 @@
+figure = Figure
+table = Table
+equation = Equation
+bibliography = Bibliography
+heading = Section
+outline = Contents
+raw = Listing \ No newline at end of file
diff --git a/crates/typst/translations/es.txt b/crates/typst/translations/es.txt
new file mode 100644
index 00000000..b0e56642
--- /dev/null
+++ b/crates/typst/translations/es.txt
@@ -0,0 +1,7 @@
+figure = Figura
+table = Tabla
+equation = Ecuación
+bibliography = Bibliografía
+heading = Sección
+outline = Índice
+raw = Listado \ No newline at end of file
diff --git a/crates/typst/translations/et.txt b/crates/typst/translations/et.txt
new file mode 100644
index 00000000..01f23b4b
--- /dev/null
+++ b/crates/typst/translations/et.txt
@@ -0,0 +1,7 @@
+figure = Joonis
+table = Tabel
+equation = Valem
+bibliography = Viited
+heading = Peatükk
+outline = Sisukord
+raw = List \ No newline at end of file
diff --git a/crates/typst/translations/fi.txt b/crates/typst/translations/fi.txt
new file mode 100644
index 00000000..da516d24
--- /dev/null
+++ b/crates/typst/translations/fi.txt
@@ -0,0 +1,7 @@
+figure = Kuva
+table = Taulukko
+equation = Yhtälö
+bibliography = Viitteet
+heading = Osio
+outline = Sisällys
+raw = Esimerkki \ No newline at end of file
diff --git a/crates/typst/translations/fr.txt b/crates/typst/translations/fr.txt
new file mode 100644
index 00000000..6c5b60f6
--- /dev/null
+++ b/crates/typst/translations/fr.txt
@@ -0,0 +1,7 @@
+figure = Fig.
+table = Tableau
+equation = Équation
+bibliography = Bibliographie
+heading = Chapitre
+outline = Table des matières
+raw = Liste \ No newline at end of file
diff --git a/crates/typst/translations/gr.txt b/crates/typst/translations/gr.txt
new file mode 100644
index 00000000..34aa3c44
--- /dev/null
+++ b/crates/typst/translations/gr.txt
@@ -0,0 +1,7 @@
+figure = Σχήμα
+table = Πίνακας
+equation = Εξίσωση
+bibliography = Βιβλιογραφία
+heading = Κεφάλαιο
+outline = Περιεχόμενα
+raw = Παράθεση \ No newline at end of file
diff --git a/crates/typst/translations/hu.txt b/crates/typst/translations/hu.txt
new file mode 100644
index 00000000..f1b616b7
--- /dev/null
+++ b/crates/typst/translations/hu.txt
@@ -0,0 +1,7 @@
+figure = Ábra
+table = Táblázat
+equation = Egyenlet
+bibliography = Irodalomjegyzék
+heading = Fejezet
+outline = Tartalomjegyzék
+# raw = \ No newline at end of file
diff --git a/crates/typst/translations/it.txt b/crates/typst/translations/it.txt
new file mode 100644
index 00000000..4c1cd90c
--- /dev/null
+++ b/crates/typst/translations/it.txt
@@ -0,0 +1,7 @@
+figure = Figura
+table = Tabella
+equation = Equazione
+bibliography = Bibliografia
+heading = Sezione
+outline = Indice
+raw = Codice \ No newline at end of file
diff --git a/crates/typst/translations/ja.txt b/crates/typst/translations/ja.txt
new file mode 100644
index 00000000..6c1f6f9c
--- /dev/null
+++ b/crates/typst/translations/ja.txt
@@ -0,0 +1,7 @@
+figure = 図
+table = 表
+equation = 式
+bibliography = 参考文献
+heading = 節
+outline = 目次
+raw = リスト \ No newline at end of file
diff --git a/crates/typst/translations/nb.txt b/crates/typst/translations/nb.txt
new file mode 100644
index 00000000..f51bcd75
--- /dev/null
+++ b/crates/typst/translations/nb.txt
@@ -0,0 +1,7 @@
+figure = Figur
+table = Tabell
+equation = Ligning
+bibliography = Bibliografi
+heading = Kapittel
+outline = Innhold
+raw = Utskrift \ No newline at end of file
diff --git a/crates/typst/translations/nl.txt b/crates/typst/translations/nl.txt
new file mode 100644
index 00000000..ecd464b3
--- /dev/null
+++ b/crates/typst/translations/nl.txt
@@ -0,0 +1,7 @@
+figure = Figuur
+table = Tabel
+equation = Vergelijking
+bibliography = Bibliografie
+heading = Hoofdstuk
+outline = Inhoudsopgave
+raw = Listing \ No newline at end of file
diff --git a/crates/typst/translations/nn.txt b/crates/typst/translations/nn.txt
new file mode 100644
index 00000000..b067bc56
--- /dev/null
+++ b/crates/typst/translations/nn.txt
@@ -0,0 +1,7 @@
+figure = Figur
+table = Tabell
+equation = Likning
+bibliography = Bibliografi
+heading = Kapittel
+outline = Innhald
+raw = Utskrift \ No newline at end of file
diff --git a/crates/typst/translations/pl.txt b/crates/typst/translations/pl.txt
new file mode 100644
index 00000000..c4934257
--- /dev/null
+++ b/crates/typst/translations/pl.txt
@@ -0,0 +1,7 @@
+figure = Rysunek
+table = Tabela
+equation = Równanie
+bibliography = Bibliografia
+heading = Sekcja
+outline = Spis treści
+raw = Program \ No newline at end of file
diff --git a/crates/typst/translations/pt-PT.txt b/crates/typst/translations/pt-PT.txt
new file mode 100644
index 00000000..6fbb586a
--- /dev/null
+++ b/crates/typst/translations/pt-PT.txt
@@ -0,0 +1,7 @@
+# figure =
+# table =
+# equation =
+# bibliography =
+heading = Secção
+outline = Índice
+# raw = \ No newline at end of file
diff --git a/crates/typst/translations/pt.txt b/crates/typst/translations/pt.txt
new file mode 100644
index 00000000..a8372e6d
--- /dev/null
+++ b/crates/typst/translations/pt.txt
@@ -0,0 +1,7 @@
+figure = Figura
+table = Tabela
+equation = Equação
+bibliography = Bibliografia
+heading = Seção
+outline = Sumário
+raw = Listagem \ No newline at end of file
diff --git a/crates/typst/translations/ro.txt b/crates/typst/translations/ro.txt
new file mode 100644
index 00000000..44d6bc8b
--- /dev/null
+++ b/crates/typst/translations/ro.txt
@@ -0,0 +1,8 @@
+figure = Figura
+table = Tabelul
+equation = Ecuația
+bibliography = Bibliografie
+heading = Secțiunea
+outline = Cuprins
+# may be wrong
+raw = Listă \ No newline at end of file
diff --git a/crates/typst/translations/ru.txt b/crates/typst/translations/ru.txt
new file mode 100644
index 00000000..fccf7bc9
--- /dev/null
+++ b/crates/typst/translations/ru.txt
@@ -0,0 +1,7 @@
+figure = Рис.
+table = Таблица
+equation = Уравнение
+bibliography = Библиография
+heading = Раздел
+outline = Содержание
+raw = Листинг \ No newline at end of file
diff --git a/crates/typst/translations/sl.txt b/crates/typst/translations/sl.txt
new file mode 100644
index 00000000..1375c4dd
--- /dev/null
+++ b/crates/typst/translations/sl.txt
@@ -0,0 +1,7 @@
+figure = Slika
+table = Tabela
+equation = Enačba
+bibliography = Literatura
+heading = Poglavje
+outline = Kazalo
+raw = Program \ No newline at end of file
diff --git a/crates/typst/translations/sq.txt b/crates/typst/translations/sq.txt
new file mode 100644
index 00000000..3b429545
--- /dev/null
+++ b/crates/typst/translations/sq.txt
@@ -0,0 +1,7 @@
+figure = Figurë
+table = Tabel
+equation = Ekuacion
+bibliography = Bibliografi
+heading = Kapitull
+outline = Përmbajtja
+raw = List \ No newline at end of file
diff --git a/crates/typst/translations/sr.txt b/crates/typst/translations/sr.txt
new file mode 100644
index 00000000..3a2f92b9
--- /dev/null
+++ b/crates/typst/translations/sr.txt
@@ -0,0 +1,7 @@
+figure = Слика
+table = Табела
+equation = Једначина
+bibliography = Литература
+heading = Поглавље
+outline = Садржај
+raw = Програм \ No newline at end of file
diff --git a/crates/typst/translations/sv.txt b/crates/typst/translations/sv.txt
new file mode 100644
index 00000000..61a39403
--- /dev/null
+++ b/crates/typst/translations/sv.txt
@@ -0,0 +1,7 @@
+figure = Figur
+table = Tabell
+equation = Ekvation
+bibliography = Bibliografi
+heading = Kapitel
+outline = Innehåll
+raw = Listing \ No newline at end of file
diff --git a/crates/typst/translations/tl.txt b/crates/typst/translations/tl.txt
new file mode 100644
index 00000000..a7b6b571
--- /dev/null
+++ b/crates/typst/translations/tl.txt
@@ -0,0 +1,7 @@
+figure = Pigura
+table = Talaan
+equation = Ekwasyon
+bibliography = Bibliograpiya
+heading = Seksyon
+outline = Talaan ng mga Nilalaman
+raw = Listahan \ No newline at end of file
diff --git a/crates/typst/translations/tr.txt b/crates/typst/translations/tr.txt
new file mode 100644
index 00000000..a7068a4f
--- /dev/null
+++ b/crates/typst/translations/tr.txt
@@ -0,0 +1,7 @@
+figure = Şekil
+table = Tablo
+equation = Denklem
+bibliography = Kaynakça
+heading = Bölüm
+outline = İçindekiler
+raw = Liste \ No newline at end of file
diff --git a/crates/typst/translations/ua.txt b/crates/typst/translations/ua.txt
new file mode 100644
index 00000000..30b409a6
--- /dev/null
+++ b/crates/typst/translations/ua.txt
@@ -0,0 +1,7 @@
+figure = Рисунок
+table = Таблиця
+equation = Рівняння
+bibliography = Бібліографія
+heading = Розділ
+outline = Зміст
+raw = Лістинг \ No newline at end of file
diff --git a/crates/typst/translations/vi.txt b/crates/typst/translations/vi.txt
new file mode 100644
index 00000000..a65ac09a
--- /dev/null
+++ b/crates/typst/translations/vi.txt
@@ -0,0 +1,8 @@
+figure = Hình
+table = Bảng
+equation = Phương trình
+bibliography = Tài liệu tham khảo
+heading = Phần
+outline = Mục lục
+# may be wrong
+raw = Chương trình \ No newline at end of file
diff --git a/crates/typst/translations/zh-TW.txt b/crates/typst/translations/zh-TW.txt
new file mode 100644
index 00000000..88035d65
--- /dev/null
+++ b/crates/typst/translations/zh-TW.txt
@@ -0,0 +1,7 @@
+figure = 圖
+# table =
+equation = 方程式
+bibliography = 書目
+heading = 小節
+outline = 目錄
+raw = 程式 \ No newline at end of file
diff --git a/crates/typst/translations/zh.txt b/crates/typst/translations/zh.txt
new file mode 100644
index 00000000..f862fff4
--- /dev/null
+++ b/crates/typst/translations/zh.txt
@@ -0,0 +1,7 @@
+figure = 图
+table = 表
+equation = 公式
+bibliography = 参考文献
+heading = 小节
+outline = 目录
+raw = 代码 \ No newline at end of file
diff --git a/tests/ref/text-language-fallback-english.png b/tests/ref/text-language-fallback-english.png
new file mode 100644
index 00000000..1f4bf74d
--- /dev/null
+++ b/tests/ref/text-language-fallback-english.png
Binary files differ
diff --git a/tests/suite/text/lang.typ b/tests/suite/text/lang.typ
index 74f70140..9bce5c3c 100644
--- a/tests/suite/text/lang.typ
+++ b/tests/suite/text/lang.typ
@@ -72,3 +72,9 @@
--- text-region-bad-value ---
// Error: 19-24 expected two letter region code (ISO 3166-1 alpha-2)
#set text(region: "hey")
+
+--- text-language-fallback-english ---
+#set text(lang: "qaa")
+#outline()
+#set text(lang: "qaa", region: "aa")
+#outline() \ No newline at end of file