diff --git a/Cargo.lock b/Cargo.lock
index 4982051..b4bc05f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2422,6 +2422,7 @@ dependencies = [
  "tempfile",
  "thiserror 2.0.11",
  "toml",
+ "xml-rs",
 ]
 
 [[package]]
@@ -5435,6 +5436,12 @@ version = "0.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
 
+[[package]]
+name = "xml-rs"
+version = "0.8.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4"
+
 [[package]]
 name = "yansi-term"
 version = "0.1.2"
diff --git a/l10n-db/Cargo.toml b/l10n-db/Cargo.toml
index 8016440..5a1f64a 100644
--- a/l10n-db/Cargo.toml
+++ b/l10n-db/Cargo.toml
@@ -12,6 +12,7 @@ serde_json = "1.0.139"
 tempfile = "3.17.1"
 thiserror = "2.0.11"
 toml = "0.8.20"
+xml-rs = "0.8.25"
 
 # [lib]
 # name = "l10n_db"
diff --git a/l10n-db/output.json b/l10n-db/output.json
deleted file mode 100644
index fe23c55..0000000
--- a/l10n-db/output.json
+++ /dev/null
@@ -1 +0,0 @@
-{"SaveSettings":"Save Settings","Welcome":"Welcome to FitnessTrax"}
\ No newline at end of file
diff --git a/l10n-db/src/bin/main.rs b/l10n-db/src/bin/main.rs
index a7880f8..d1b304e 100644
--- a/l10n-db/src/bin/main.rs
+++ b/l10n-db/src/bin/main.rs
@@ -7,7 +7,7 @@ use std::{
 use clap::{Parser, Subcommand};
 
 use icu_locid::{langid, LanguageIdentifier};
-use l10n_db::{self, export_file, read_file, Bundle, Editor, ReadError};
+use l10n_db::{self, js, read_file, xliff, Bundle, Editor, ReadError};
 use serde::Deserialize;
 
 #[derive(Parser)]
@@ -76,7 +76,12 @@ fn main() {
         }
         Some(Commands::Export { format, locale }) => {
             let locale = locale.as_ref().map(|l| l.clone().parse::<LanguageIdentifier>().unwrap()).unwrap_or(langid!("en"));
-            export_file(&bundle, locale, &PathBuf::from("output.json")).unwrap();
+
+            match format.as_ref() {
+                "js" => js::export_file(&bundle, locale, &PathBuf::from("output.json")).unwrap(),
+                "xliff" => xliff::export_file(&bundle, locale, &PathBuf::from("output.xliff")).unwrap(),
+                _ => todo!(),
+            }
         },
         None => {}
     }
diff --git a/l10n-db/src/js_format.rs b/l10n-db/src/formats/js.rs
similarity index 100%
rename from l10n-db/src/js_format.rs
rename to l10n-db/src/formats/js.rs
diff --git a/l10n-db/src/formats/mod.rs b/l10n-db/src/formats/mod.rs
new file mode 100644
index 0000000..6fbd8ce
--- /dev/null
+++ b/l10n-db/src/formats/mod.rs
@@ -0,0 +1,4 @@
+pub mod js;
+pub mod xliff;
+
+
diff --git a/l10n-db/src/formats/xliff.rs b/l10n-db/src/formats/xliff.rs
new file mode 100644
index 0000000..9563e52
--- /dev/null
+++ b/l10n-db/src/formats/xliff.rs
@@ -0,0 +1,48 @@
+use std::{fs::File, io, path::Path};
+
+use icu_locid::LanguageIdentifier;
+use xml::{writer::XmlEvent, EmitterConfig, EventWriter};
+
+use crate::{Bundle, Message, WriteError};
+
+pub fn export_file(
+    bundle: &Bundle,
+    locale: LanguageIdentifier,
+    path: &Path,
+) -> Result<(), WriteError> {
+    let mut file = File::create(path).unwrap();
+    export_fh(bundle, locale, &mut file)
+}
+
+pub fn export_fh(
+    bundle: &Bundle,
+    locale: LanguageIdentifier,
+    fh: &mut File,
+) -> Result<(), WriteError> {
+    let output = io::stdout();
+    let mut writer = EmitterConfig::new()
+        .perform_indent(true)
+        .create_writer(output);
+
+    writer.write(XmlEvent::start_element("xliff")).unwrap();
+    writer.write(XmlEvent::start_element("file")).unwrap();
+
+    for (key, message) in bundle.message_iter() {
+        write_message(&mut writer, message, &locale);
+    }
+
+    writer.write(XmlEvent::end_element()).unwrap();
+    writer.write(XmlEvent::end_element()).unwrap();
+    Ok(())
+}
+
+fn write_message<T>(writer: &mut EventWriter<T>, message: &Message, locale: &LanguageIdentifier)
+where
+    T: std::io::Write,
+{
+    writer.write(XmlEvent::start_element("unit")).unwrap();
+    writer.write(XmlEvent::start_element("segment")).unwrap();
+    writer.write(XmlEvent::characters(message.variant(locale).unwrap().content())).unwrap();
+    writer.write(XmlEvent::end_element()).unwrap();
+    writer.write(XmlEvent::end_element()).unwrap();
+}
diff --git a/l10n-db/src/lib.rs b/l10n-db/src/lib.rs
index a5286f6..d826d47 100644
--- a/l10n-db/src/lib.rs
+++ b/l10n-db/src/lib.rs
@@ -4,8 +4,8 @@ pub use bundle::Bundle;
 mod editor;
 pub use editor::Editor;
 
-mod js_format;
-pub use js_format::{export_file, export_fh};
+mod formats;
+pub use formats::{js, xliff};
 
 mod types;
 pub use types::{Message, Variant};
@@ -13,17 +13,3 @@ pub use types::{Message, Variant};
 mod utils;
 pub use utils::*;
 
-/*
-#[cfg(test)]
-mod test {
-    #[test]
-    fn it_can_represent_an_untranslated_message() {
-        todo!()
-    }
-
-    #[test]
-    fn it_can_represent_a_partially_translated_message() {
-        todo!()
-    }
-}
-*/