diff --git a/backend/data/build.gradle b/backend/data/build.gradle
index 770dfe03a1..8933dee28b 100644
--- a/backend/data/build.gradle
+++ b/backend/data/build.gradle
@@ -168,7 +168,13 @@ dependencies {
     implementation libs.jacksonKotlin
     implementation("org.apache.commons:commons-configuration2:2.10.1")
     implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jacksonVersion"
+
+    /**
+     * Table formats
+     */
     implementation("com.opencsv:opencsv:5.9")
+    implementation 'org.apache.poi:poi:5.3.0'
+    implementation 'org.apache.poi:poi-ooxml:5.3.0'
 
     /**
      * Google translation API
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/ExportFormat.kt b/backend/data/src/main/kotlin/io/tolgee/formats/ExportFormat.kt
index f707cb5444..9cc58dfdc5 100644
--- a/backend/data/src/main/kotlin/io/tolgee/formats/ExportFormat.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/ExportFormat.kt
@@ -42,4 +42,5 @@ enum class ExportFormat(
   JSON_I18NEXT("json", "application/json"),
   CSV("csv", "text/csv"),
   RESX_ICU("resx", "text/microsoft-resx"),
+  XLSX("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
 }
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/ImportFileProcessorFactory.kt b/backend/data/src/main/kotlin/io/tolgee/formats/ImportFileProcessorFactory.kt
index cb2b75b3bd..6d8a8ac74c 100644
--- a/backend/data/src/main/kotlin/io/tolgee/formats/ImportFileProcessorFactory.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/ImportFileProcessorFactory.kt
@@ -13,6 +13,7 @@ import io.tolgee.formats.po.`in`.PoFileProcessor
 import io.tolgee.formats.properties.`in`.PropertiesFileProcessor
 import io.tolgee.formats.resx.`in`.ResxProcessor
 import io.tolgee.formats.xliff.`in`.XliffFileProcessor
+import io.tolgee.formats.xlsx.`in`.XlsxFileProcessor
 import io.tolgee.formats.xmlResources.`in`.XmlResourcesProcessor
 import io.tolgee.formats.yaml.`in`.YamlFileProcessor
 import io.tolgee.service.dataImport.processors.FileProcessorContext
@@ -64,6 +65,7 @@ class ImportFileProcessorFactory(
       ImportFileFormat.YAML -> YamlFileProcessor(context, yamlObjectMapper)
       ImportFileFormat.CSV -> CsvFileProcessor(context)
       ImportFileFormat.RESX -> ResxProcessor(context)
+      ImportFileFormat.XLSX -> XlsxFileProcessor(context)
     }
   }
 
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/csv/in/CsvFileParser.kt b/backend/data/src/main/kotlin/io/tolgee/formats/csv/in/CsvFileParser.kt
index 1a5de42bd2..c3ea360880 100644
--- a/backend/data/src/main/kotlin/io/tolgee/formats/csv/in/CsvFileParser.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/csv/in/CsvFileParser.kt
@@ -2,7 +2,8 @@ package io.tolgee.formats.csv.`in`
 
 import com.opencsv.CSVParserBuilder
 import com.opencsv.CSVReaderBuilder
-import io.tolgee.formats.csv.CsvEntry
+import io.tolgee.formats.genericTable.TableEntry
+import io.tolgee.formats.genericTable.`in`.TableParser
 import java.io.InputStream
 
 class CsvFileParser(
@@ -10,52 +11,19 @@ class CsvFileParser(
   private val delimiter: Char,
   private val languageFallback: String,
 ) {
-  val rawData: List<Array<String>> by lazy {
+  val rawData: List<List<String>> by lazy {
     val inputReader = inputStream.reader()
     val parser = CSVParserBuilder().withSeparator(delimiter).build()
     val reader = CSVReaderBuilder(inputReader).withCSVParser(parser).build()
 
-    return@lazy reader.readAll()
+    return@lazy reader.readAll().map { it.toList() }
   }
 
-  val headers: Array<String>? by lazy {
-    rawData.firstOrNull()
+  val tableParser: TableParser by lazy {
+    TableParser(rawData, languageFallback)
   }
 
-  val languages: List<String> by lazy {
-    headers?.takeIf { it.size > 1 }?.drop(1) ?: emptyList()
-  }
-
-  val languagesWithFallback: Sequence<String>
-    get() = languages.asSequence().plus(generateSequence { languageFallback })
-
-  val rows: List<Array<String>> by lazy {
-    rawData.takeIf { it.size > 1 }?.drop(1) ?: emptyList()
-  }
-
-  fun Array<String>.rowToCsvEntries(): Sequence<CsvEntry> {
-    if (isEmpty()) {
-      return emptySequence()
-    }
-    val keyName = getOrNull(0) ?: ""
-    if (size == 1) {
-      return sequenceOf(CsvEntry(keyName, languageFallback, null))
-    }
-    val translations = drop(1).asSequence()
-    return translations
-      .zip(languagesWithFallback)
-      .map { (translation, languageTag) ->
-        CsvEntry(
-          keyName,
-          languageTag,
-          translation,
-        )
-      }
-  }
-
-  fun parse(): List<CsvEntry> {
-    return rows.flatMap {
-      it.rowToCsvEntries()
-    }
+  fun parse(): List<TableEntry> {
+    return tableParser.parse()
   }
 }
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/csv/in/CsvFileProcessor.kt b/backend/data/src/main/kotlin/io/tolgee/formats/csv/in/CsvFileProcessor.kt
index 3f251a556f..b8c41a1fe3 100644
--- a/backend/data/src/main/kotlin/io/tolgee/formats/csv/in/CsvFileProcessor.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/csv/in/CsvFileProcessor.kt
@@ -1,46 +1,15 @@
 package io.tolgee.formats.csv.`in`
 
 import io.tolgee.exceptions.ImportCannotParseFileException
-import io.tolgee.formats.ImportFileProcessor
-import io.tolgee.formats.csv.CsvEntry
+import io.tolgee.formats.genericTable.TableEntry
+import io.tolgee.formats.genericTable.`in`.TableProcessor
 import io.tolgee.formats.importCommon.ImportFormat
 import io.tolgee.service.dataImport.processors.FileProcessorContext
 
 class CsvFileProcessor(
   override val context: FileProcessorContext,
-) : ImportFileProcessor() {
-  override fun process() {
-    val (data, format) = parse()
-    data.importAll(format)
-  }
-
-  fun Iterable<CsvEntry>.importAll(format: ImportFormat) {
-    forEachIndexed { idx, it -> it.import(idx, format) }
-  }
-
-  fun CsvEntry.import(
-    index: Int,
-    format: ImportFormat,
-  ) {
-    val converted =
-      format.messageConvertor.convert(
-        value,
-        language,
-        convertPlaceholders = context.importSettings.convertPlaceholdersToIcu,
-        isProjectIcuEnabled = context.projectIcuPlaceholdersEnabled,
-      )
-    context.addTranslation(
-      key,
-      language,
-      converted.message,
-      index,
-      pluralArgName = converted.pluralArgName,
-      rawData = value,
-      convertedBy = format,
-    )
-  }
-
-  private fun parse(): Pair<Iterable<CsvEntry>, ImportFormat> {
+) : TableProcessor(context) {
+  override fun parse(): Pair<Iterable<TableEntry>, ImportFormat> {
     try {
       val detector = CsvDelimiterDetector(context.file.data.inputStream())
       val parser =
@@ -50,7 +19,7 @@ class CsvFileProcessor(
           languageFallback = firstLanguageTagGuessOrUnknown,
         )
       val data = parser.parse()
-      val format = getFormat(parser.rows)
+      val format = getFormat(parser.tableParser.rows)
       return data to format
     } catch (e: Exception) {
       throw ImportCannotParseFileException(context.file.name, e.message ?: "", e)
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/csv/out/CsvFileExporter.kt b/backend/data/src/main/kotlin/io/tolgee/formats/csv/out/CsvFileExporter.kt
index 86bbd2deb9..cde1944631 100644
--- a/backend/data/src/main/kotlin/io/tolgee/formats/csv/out/CsvFileExporter.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/csv/out/CsvFileExporter.kt
@@ -1,68 +1,17 @@
 package io.tolgee.formats.csv.out
 
 import io.tolgee.dtos.IExportParams
-import io.tolgee.formats.ExportMessageFormat
-import io.tolgee.formats.csv.CsvEntry
-import io.tolgee.formats.generic.IcuToGenericFormatMessageConvertor
-import io.tolgee.service.export.ExportFilePathProvider
+import io.tolgee.formats.genericTable.TableEntry
+import io.tolgee.formats.genericTable.out.TableExporter
 import io.tolgee.service.export.dataProvider.ExportTranslationView
-import io.tolgee.service.export.exporters.FileExporter
 import java.io.InputStream
 
 class CsvFileExporter(
-  val translations: List<ExportTranslationView>,
-  val exportParams: IExportParams,
-  private val isProjectIcuPlaceholdersEnabled: Boolean = true,
-) : FileExporter {
-  private val pathProvider by lazy {
-    ExportFilePathProvider(
-      exportParams,
-      "csv",
-    )
-  }
-
-  private val messageFormat
-    get() = exportParams.messageFormat ?: ExportMessageFormat.ICU
-
-  private val placeholderConvertorFactory
-    get() = messageFormat.paramConvertorFactory
-
-  val entries =
-    translations.map {
-      val converted = convertMessage(it.text, it.key.isPlural)
-      val path =
-        pathProvider.getFilePath(it.key.namespace)
-      val entry =
-        CsvEntry(
-          key = it.key.name,
-          language = it.languageTag,
-          value = converted,
-        )
-      path to entry
-    }.groupBy({ it.first }, { it.second })
-
-  private fun convertMessage(
-    text: String?,
-    isPlural: Boolean,
-  ): String? {
-    return getMessageConvertor(text, isPlural).convert()
-  }
-
-  private fun getMessageConvertor(
-    text: String?,
-    isPlural: Boolean,
-  ) = IcuToGenericFormatMessageConvertor(
-    text,
-    isPlural,
-    isProjectIcuPlaceholdersEnabled = isProjectIcuPlaceholdersEnabled,
-    paramConvertorFactory = placeholderConvertorFactory,
-  )
-
-  override fun produceFiles(): Map<String, InputStream> {
-    return entries.mapValues { (_, entry) -> entry.toCsv() }
-  }
-
-  private fun List<CsvEntry>.toCsv(): InputStream {
+  translations: List<ExportTranslationView>,
+  exportParams: IExportParams,
+  isProjectIcuPlaceholdersEnabled: Boolean = true,
+) : TableExporter(translations, exportParams, "csv", isProjectIcuPlaceholdersEnabled) {
+  override fun List<TableEntry>.toFileContents(): InputStream {
     val languageTags =
       exportParams.languages?.sorted()?.toTypedArray()
         ?: this.map { it.language }.distinct().sorted().toTypedArray()
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/csv/out/CsvFileWriter.kt b/backend/data/src/main/kotlin/io/tolgee/formats/csv/out/CsvFileWriter.kt
index 01fce234fd..3b453f08f7 100644
--- a/backend/data/src/main/kotlin/io/tolgee/formats/csv/out/CsvFileWriter.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/csv/out/CsvFileWriter.kt
@@ -1,13 +1,13 @@
 package io.tolgee.formats.csv.out
 
 import com.opencsv.CSVWriterBuilder
-import io.tolgee.formats.csv.CsvEntry
+import io.tolgee.formats.genericTable.TableEntry
 import java.io.InputStream
 import java.io.StringWriter
 
 class CsvFileWriter(
   private val languageTags: Array<String>,
-  private val data: List<CsvEntry>,
+  private val data: List<TableEntry>,
   private val delimiter: Char,
 ) {
   val translations: Map<String, Map<String, String?>> by lazy {
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/csv/CsvModel.kt b/backend/data/src/main/kotlin/io/tolgee/formats/genericTable/TableModel.kt
similarity index 52%
rename from backend/data/src/main/kotlin/io/tolgee/formats/csv/CsvModel.kt
rename to backend/data/src/main/kotlin/io/tolgee/formats/genericTable/TableModel.kt
index 5231a56d82..b85b088948 100644
--- a/backend/data/src/main/kotlin/io/tolgee/formats/csv/CsvModel.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/genericTable/TableModel.kt
@@ -1,6 +1,6 @@
-package io.tolgee.formats.csv
+package io.tolgee.formats.genericTable
 
-data class CsvEntry(
+data class TableEntry(
   val key: String,
   val language: String,
   val value: String?,
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/genericTable/in/TableParser.kt b/backend/data/src/main/kotlin/io/tolgee/formats/genericTable/in/TableParser.kt
new file mode 100644
index 0000000000..ff5c23bb82
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/genericTable/in/TableParser.kt
@@ -0,0 +1,49 @@
+package io.tolgee.formats.genericTable.`in`
+
+import io.tolgee.formats.genericTable.TableEntry
+
+class TableParser(
+  private val rawData: List<List<String>>,
+  private val languageFallback: String,
+) {
+  val headers: List<String>? by lazy {
+    rawData.firstOrNull()
+  }
+
+  val languages: List<String> by lazy {
+    headers?.takeIf { it.size > 1 }?.drop(1) ?: emptyList()
+  }
+
+  val languagesWithFallback: Sequence<String>
+    get() = languages.asSequence().plus(generateSequence { languageFallback })
+
+  val rows: List<List<String>> by lazy {
+    rawData.takeIf { it.size > 1 }?.drop(1) ?: emptyList()
+  }
+
+  fun List<String>.rowToTableEntries(): Sequence<TableEntry> {
+    if (isEmpty()) {
+      return emptySequence()
+    }
+    val keyName = getOrNull(0) ?: ""
+    if (size == 1) {
+      return sequenceOf(TableEntry(keyName, languageFallback, null))
+    }
+    val translations = drop(1).asSequence()
+    return translations
+      .zip(languagesWithFallback)
+      .map { (translation, languageTag) ->
+        TableEntry(
+          keyName,
+          languageTag,
+          translation,
+        )
+      }
+  }
+
+  fun parse(): List<TableEntry> {
+    return rows.flatMap {
+      it.rowToTableEntries()
+    }
+  }
+}
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/genericTable/in/TableProcessor.kt b/backend/data/src/main/kotlin/io/tolgee/formats/genericTable/in/TableProcessor.kt
new file mode 100644
index 0000000000..bb707928db
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/genericTable/in/TableProcessor.kt
@@ -0,0 +1,43 @@
+package io.tolgee.formats.genericTable.`in`
+
+import io.tolgee.formats.ImportFileProcessor
+import io.tolgee.formats.genericTable.TableEntry
+import io.tolgee.formats.importCommon.ImportFormat
+import io.tolgee.service.dataImport.processors.FileProcessorContext
+
+abstract class TableProcessor(
+  override val context: FileProcessorContext,
+) : ImportFileProcessor() {
+  override fun process() {
+    val (data, format) = parse()
+    data.importAll(format)
+  }
+
+  fun Iterable<TableEntry>.importAll(format: ImportFormat) {
+    forEachIndexed { idx, it -> it.import(idx, format) }
+  }
+
+  fun TableEntry.import(
+    index: Int,
+    format: ImportFormat,
+  ) {
+    val converted =
+      format.messageConvertor.convert(
+        value,
+        language,
+        convertPlaceholders = context.importSettings.convertPlaceholdersToIcu,
+        isProjectIcuEnabled = context.projectIcuPlaceholdersEnabled,
+      )
+    context.addTranslation(
+      key,
+      language,
+      converted.message,
+      index,
+      pluralArgName = converted.pluralArgName,
+      rawData = value,
+      convertedBy = format,
+    )
+  }
+
+  protected abstract fun parse(): Pair<Iterable<TableEntry>, ImportFormat>
+}
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/genericTable/out/TableExporter.kt b/backend/data/src/main/kotlin/io/tolgee/formats/genericTable/out/TableExporter.kt
new file mode 100644
index 0000000000..f91b668fba
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/genericTable/out/TableExporter.kt
@@ -0,0 +1,67 @@
+package io.tolgee.formats.genericTable.out
+
+import io.tolgee.dtos.IExportParams
+import io.tolgee.formats.ExportMessageFormat
+import io.tolgee.formats.generic.IcuToGenericFormatMessageConvertor
+import io.tolgee.formats.genericTable.TableEntry
+import io.tolgee.service.export.ExportFilePathProvider
+import io.tolgee.service.export.dataProvider.ExportTranslationView
+import io.tolgee.service.export.exporters.FileExporter
+import java.io.InputStream
+
+abstract class TableExporter(
+  val translations: List<ExportTranslationView>,
+  val exportParams: IExportParams,
+  val fileExtension: String,
+  val isProjectIcuPlaceholdersEnabled: Boolean = true,
+) : FileExporter {
+  val pathProvider by lazy {
+    ExportFilePathProvider(
+      exportParams,
+      fileExtension,
+    )
+  }
+
+  val messageFormat
+    get() = exportParams.messageFormat ?: ExportMessageFormat.ICU
+
+  val placeholderConvertorFactory
+    get() = messageFormat.paramConvertorFactory
+
+  val entries =
+    translations.map {
+      val converted = convertMessage(it.text, it.key.isPlural)
+      val path =
+        pathProvider.getFilePath(it.key.namespace)
+      val entry =
+        TableEntry(
+          key = it.key.name,
+          language = it.languageTag,
+          value = converted,
+        )
+      path to entry
+    }.groupBy({ it.first }, { it.second })
+
+  fun convertMessage(
+    text: String?,
+    isPlural: Boolean,
+  ): String? {
+    return getMessageConvertor(text, isPlural).convert()
+  }
+
+  fun getMessageConvertor(
+    text: String?,
+    isPlural: Boolean,
+  ) = IcuToGenericFormatMessageConvertor(
+    text,
+    isPlural,
+    isProjectIcuPlaceholdersEnabled = isProjectIcuPlaceholdersEnabled,
+    paramConvertorFactory = placeholderConvertorFactory,
+  )
+
+  override fun produceFiles(): Map<String, InputStream> {
+    return entries.mapValues { (_, entry) -> entry.toFileContents() }
+  }
+
+  abstract fun List<TableEntry>.toFileContents(): InputStream
+}
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ImportFileFormat.kt b/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ImportFileFormat.kt
index f7922b305e..4e59bead4e 100644
--- a/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ImportFileFormat.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ImportFileFormat.kt
@@ -12,6 +12,7 @@ enum class ImportFileFormat(val extensions: Array<String>) {
   YAML(arrayOf("yaml", "yml")),
   CSV(arrayOf("csv")),
   RESX(arrayOf("resx")),
+  XLSX(arrayOf("xls", "xlsx")),
   ;
 
   companion object {
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ImportFormat.kt b/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ImportFormat.kt
index 9ff5fb5ff5..cf3d13d8ed 100644
--- a/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ImportFormat.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ImportFormat.kt
@@ -225,6 +225,27 @@ enum class ImportFormat(
       ),
   ),
 
+  XLSX_ICU(
+    ImportFileFormat.XLSX,
+    messageConvertorOrNull =
+      GenericMapPluralImportRawDataConvertor(
+        canContainIcu = true,
+        toIcuPlaceholderConvertorFactory = null,
+      ),
+  ),
+  XLSX_JAVA(
+    ImportFileFormat.XLSX,
+    messageConvertorOrNull = GenericMapPluralImportRawDataConvertor { JavaToIcuPlaceholderConvertor() },
+  ),
+  XLSX_PHP(
+    ImportFileFormat.XLSX,
+    messageConvertorOrNull = GenericMapPluralImportRawDataConvertor { PhpToIcuPlaceholderConvertor() },
+  ),
+  XLSX_RUBY(
+    ImportFileFormat.XLSX,
+    messageConvertorOrNull = GenericMapPluralImportRawDataConvertor { RubyToIcuPlaceholderConvertor() },
+  ),
+
   ;
 
   val messageConvertor: ImportMessageConvertor
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/in/XlsxFileParser.kt b/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/in/XlsxFileParser.kt
new file mode 100644
index 0000000000..ddc260a9fc
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/in/XlsxFileParser.kt
@@ -0,0 +1,24 @@
+package io.tolgee.formats.xlsx.`in`
+
+import io.tolgee.formats.genericTable.TableEntry
+import io.tolgee.formats.genericTable.`in`.TableParser
+import org.apache.poi.ss.usermodel.WorkbookFactory
+import java.io.InputStream
+
+class XlsxFileParser(
+  private val inputStream: InputStream,
+  private val languageFallback: String,
+) {
+  val rawData: List<List<List<String>>> by lazy {
+    val workbook = WorkbookFactory.create(inputStream)
+    return@lazy workbook.sheetIterator().asSequence().map {
+      it.map { it.map { it.stringCellValue } }
+    }.toList()
+  }
+
+  fun parse(): List<TableEntry> {
+    return rawData.flatMap {
+      TableParser(it, languageFallback).parse()
+    }
+  }
+}
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/in/XlsxFileProcessor.kt b/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/in/XlsxFileProcessor.kt
new file mode 100644
index 0000000000..c91d2a7d5e
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/in/XlsxFileProcessor.kt
@@ -0,0 +1,30 @@
+package io.tolgee.formats.xlsx.`in`
+
+import io.tolgee.exceptions.ImportCannotParseFileException
+import io.tolgee.formats.genericTable.TableEntry
+import io.tolgee.formats.genericTable.`in`.TableProcessor
+import io.tolgee.formats.importCommon.ImportFormat
+import io.tolgee.service.dataImport.processors.FileProcessorContext
+
+class XlsxFileProcessor(
+  override val context: FileProcessorContext,
+) : TableProcessor(context) {
+  override fun parse(): Pair<Iterable<TableEntry>, ImportFormat> {
+    try {
+      val parser =
+        XlsxFileParser(
+          inputStream = context.file.data.inputStream(),
+          languageFallback = firstLanguageTagGuessOrUnknown,
+        )
+      val data = parser.parse()
+      val format = getFormat(parser.rawData)
+      return data to format
+    } catch (e: Exception) {
+      throw ImportCannotParseFileException(context.file.name, e.message ?: "", e)
+    }
+  }
+
+  private fun getFormat(data: Any?): ImportFormat {
+    return context.mapping?.format ?: XlsxImportFormatDetector().detectFormat(data)
+  }
+}
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/in/XlsxImportFormatDetector.kt b/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/in/XlsxImportFormatDetector.kt
new file mode 100644
index 0000000000..5c48cd395f
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/in/XlsxImportFormatDetector.kt
@@ -0,0 +1,46 @@
+package io.tolgee.formats.xlsx.`in`
+
+import io.tolgee.formats.genericStructuredFile.`in`.FormatDetectionUtil
+import io.tolgee.formats.genericStructuredFile.`in`.FormatDetectionUtil.ICU_DETECTION_REGEX
+import io.tolgee.formats.genericStructuredFile.`in`.FormatDetectionUtil.detectFromPossibleFormats
+import io.tolgee.formats.importCommon.ImportFormat
+import io.tolgee.formats.paramConvertors.`in`.JavaToIcuPlaceholderConvertor
+import io.tolgee.formats.paramConvertors.`in`.PhpToIcuPlaceholderConvertor
+import io.tolgee.formats.paramConvertors.`in`.RubyToIcuPlaceholderConvertor
+
+class XlsxImportFormatDetector {
+  companion object {
+    private val possibleFormats =
+      mapOf(
+        ImportFormat.XLSX_ICU to
+          arrayOf(
+            FormatDetectionUtil.regexFactor(
+              ICU_DETECTION_REGEX,
+            ),
+          ),
+        ImportFormat.XLSX_PHP to
+          arrayOf(
+            FormatDetectionUtil.regexFactor(
+              PhpToIcuPlaceholderConvertor.PHP_DETECTION_REGEX,
+            ),
+          ),
+        ImportFormat.XLSX_JAVA to
+          arrayOf(
+            FormatDetectionUtil.regexFactor(
+              JavaToIcuPlaceholderConvertor.JAVA_DETECTION_REGEX,
+            ),
+          ),
+        ImportFormat.XLSX_RUBY to
+          arrayOf(
+            FormatDetectionUtil.regexFactor(
+              RubyToIcuPlaceholderConvertor.RUBY_DETECTION_REGEX,
+              0.95,
+            ),
+          ),
+      )
+  }
+
+  fun detectFormat(data: Any?): ImportFormat {
+    return detectFromPossibleFormats(possibleFormats, data) ?: ImportFormat.XLSX_ICU
+  }
+}
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/out/XlsxFileExporter.kt b/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/out/XlsxFileExporter.kt
new file mode 100644
index 0000000000..0ad1231e9c
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/out/XlsxFileExporter.kt
@@ -0,0 +1,26 @@
+package io.tolgee.formats.xlsx.out
+
+import io.tolgee.dtos.IExportParams
+import io.tolgee.formats.genericTable.TableEntry
+import io.tolgee.formats.genericTable.out.TableExporter
+import io.tolgee.service.export.dataProvider.ExportTranslationView
+import java.io.InputStream
+import java.util.Date
+
+class XlsxFileExporter(
+  val currentDate: Date,
+  translations: List<ExportTranslationView>,
+  exportParams: IExportParams,
+  isProjectIcuPlaceholdersEnabled: Boolean = true,
+) : TableExporter(translations, exportParams, "xlsx", isProjectIcuPlaceholdersEnabled) {
+  override fun List<TableEntry>.toFileContents(): InputStream {
+    val languageTags =
+      exportParams.languages?.sorted()?.toTypedArray()
+        ?: this.map { it.language }.distinct().sorted().toTypedArray()
+    return XlsxFileWriter(
+      createdDate = currentDate,
+      languageTags = languageTags,
+      data = this,
+    ).produceFiles()
+  }
+}
diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/out/XlsxFileWriter.kt b/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/out/XlsxFileWriter.kt
new file mode 100644
index 0000000000..44ee26cd88
--- /dev/null
+++ b/backend/data/src/main/kotlin/io/tolgee/formats/xlsx/out/XlsxFileWriter.kt
@@ -0,0 +1,47 @@
+package io.tolgee.formats.xlsx.out
+
+import io.tolgee.formats.genericTable.TableEntry
+import org.apache.poi.xssf.usermodel.XSSFWorkbook
+import java.io.ByteArrayOutputStream
+import java.io.InputStream
+import java.util.Date
+import java.util.Optional
+
+class XlsxFileWriter(
+  private val createdDate: Date,
+  private val languageTags: Array<String>,
+  private val data: List<TableEntry>,
+) {
+  val translations: Map<String, Map<String, String?>> by lazy {
+    data.groupBy { it.key }.mapValues { (_, values) ->
+      values.associate { it.language to it.value }
+    }
+  }
+
+  fun produceFiles(): InputStream {
+    val output = ByteArrayOutputStream()
+    val workbook = XSSFWorkbook()
+    workbook.properties.coreProperties.setCreated(Optional.of(createdDate))
+    val sheet = workbook.createSheet()
+
+    val header = sheet.createRow(0)
+    (listOf("key") + languageTags).forEachIndexed { i, v ->
+      header.createCell(i).setCellValue(v)
+    }
+
+    translations.entries.forEachIndexed { i, it ->
+      val row = sheet.createRow(i + 1)
+      (
+        listOf(it.key) +
+          languageTags.map { languageTag ->
+            it.value.getOrDefault(languageTag, null) ?: ""
+          }
+      ).forEachIndexed { i, v ->
+        row.createCell(i).setCellValue(v)
+      }
+    }
+
+    workbook.write(output)
+    return output.toByteArray().inputStream()
+  }
+}
diff --git a/backend/data/src/main/kotlin/io/tolgee/service/export/FileExporterFactory.kt b/backend/data/src/main/kotlin/io/tolgee/service/export/FileExporterFactory.kt
index 1ee64b3b2c..0411786431 100644
--- a/backend/data/src/main/kotlin/io/tolgee/service/export/FileExporterFactory.kt
+++ b/backend/data/src/main/kotlin/io/tolgee/service/export/FileExporterFactory.kt
@@ -1,6 +1,7 @@
 package io.tolgee.service.export
 
 import com.fasterxml.jackson.databind.ObjectMapper
+import io.tolgee.component.CurrentDateProvider
 import io.tolgee.dtos.IExportParams
 import io.tolgee.dtos.cacheable.LanguageDto
 import io.tolgee.formats.ExportFormat
@@ -14,6 +15,7 @@ import io.tolgee.formats.po.out.PoFileExporter
 import io.tolgee.formats.properties.out.PropertiesFileExporter
 import io.tolgee.formats.resx.out.ResxExporter
 import io.tolgee.formats.xliff.out.XliffFileExporter
+import io.tolgee.formats.xlsx.out.XlsxFileExporter
 import io.tolgee.formats.xmlResources.out.XmlResourcesExporter
 import io.tolgee.formats.yaml.out.YamlFileExporter
 import io.tolgee.service.export.dataProvider.ExportTranslationView
@@ -27,6 +29,7 @@ class FileExporterFactory(
   @Qualifier("yamlObjectMapper")
   private val yamlObjectMapper: ObjectMapper,
   private val customPrettyPrinter: CustomPrettyPrinter,
+  private val currentDateProvider: CurrentDateProvider,
 ) {
   fun create(
     data: List<ExportTranslationView>,
@@ -109,6 +112,14 @@ class FileExporterFactory(
 
       ExportFormat.RESX_ICU ->
         ResxExporter(data, exportParams, projectIcuPlaceholdersSupport)
+
+      ExportFormat.XLSX ->
+        XlsxFileExporter(
+          currentDateProvider.date,
+          data,
+          exportParams,
+          projectIcuPlaceholdersSupport,
+        )
     }
   }
 }
diff --git a/backend/data/src/test/kotlin/io/tolgee/unit/formats/csv/in/CsvImportFormatDetectorTest.kt b/backend/data/src/test/kotlin/io/tolgee/unit/formats/csv/in/CsvImportFormatDetectorTest.kt
index b83cda08e7..c31ae386a2 100644
--- a/backend/data/src/test/kotlin/io/tolgee/unit/formats/csv/in/CsvImportFormatDetectorTest.kt
+++ b/backend/data/src/test/kotlin/io/tolgee/unit/formats/csv/in/CsvImportFormatDetectorTest.kt
@@ -49,7 +49,7 @@ class CsvImportFormatDetectorTest {
         delimiter = ',',
         languageFallback = "unknown",
       )
-    return parser.rows
+    return parser.tableParser.rows
   }
 
   private fun String.assertDetected(format: ImportFormat) {
diff --git a/backend/data/src/test/kotlin/io/tolgee/unit/util/exportAssertUtil.kt b/backend/data/src/test/kotlin/io/tolgee/unit/util/exportAssertUtil.kt
index 136bcf8984..6476a3fecd 100644
--- a/backend/data/src/test/kotlin/io/tolgee/unit/util/exportAssertUtil.kt
+++ b/backend/data/src/test/kotlin/io/tolgee/unit/util/exportAssertUtil.kt
@@ -2,6 +2,7 @@ package io.tolgee.unit.util
 
 import io.tolgee.service.export.exporters.FileExporter
 import io.tolgee.testing.assert
+import java.util.zip.ZipInputStream
 
 fun Map<String, String>.assertFile(
   file: String,
@@ -15,3 +16,24 @@ fun getExported(exporter: FileExporter): Map<String, String> {
   val data = files.map { it.key to it.value.bufferedReader().readText() }.toMap()
   return data
 }
+
+fun getExportedCompressed(exporter: FileExporter): Map<String, String> {
+  val files = exporter.produceFiles()
+  val data =
+    files.map {
+      it.key to
+        buildString {
+          val stream = ZipInputStream(it.value)
+          var entry = stream.nextEntry
+          while (entry != null) {
+            appendLine("====================")
+            appendLine(entry.name)
+            appendLine("--------------------")
+            append(stream.bufferedReader().readText())
+            appendLine()
+            entry = stream.nextEntry
+          }
+        }
+    }.toMap()
+  return data
+}
diff --git a/backend/data/src/test/kotlin/io/tolgee/unit/xlsx/in/XlsxFormatProcessorTest.kt b/backend/data/src/test/kotlin/io/tolgee/unit/xlsx/in/XlsxFormatProcessorTest.kt
new file mode 100644
index 0000000000..fae6b60b02
--- /dev/null
+++ b/backend/data/src/test/kotlin/io/tolgee/unit/xlsx/in/XlsxFormatProcessorTest.kt
@@ -0,0 +1,432 @@
+package io.tolgee.unit.xlsx.`in`
+
+import io.tolgee.formats.xlsx.`in`.XlsxFileProcessor
+import io.tolgee.testing.assert
+import io.tolgee.unit.formats.PlaceholderConversionTestHelper
+import io.tolgee.util.FileProcessorContextMockUtil
+import io.tolgee.util.assertKey
+import io.tolgee.util.assertLanguagesCount
+import io.tolgee.util.assertSingle
+import io.tolgee.util.assertSinglePlural
+import io.tolgee.util.assertTranslations
+import io.tolgee.util.custom
+import io.tolgee.util.description
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+class XlsxFormatProcessorTest {
+  lateinit var mockUtil: FileProcessorContextMockUtil
+
+  @BeforeEach
+  fun setup() {
+    mockUtil = FileProcessorContextMockUtil()
+  }
+
+  // This is how to generate the test:
+  // 1. run the test in debug mode
+  // 2. copy the result of calling:
+  // io.tolgee.unit.util.generateTestsForImportResult(mockUtil.fileProcessorContext)
+  // from the debug window
+  @Test
+  fun `returns correct parsed result`() {
+    mockUtil.mockIt("example.xlsx", "src/test/resources/import/xlsx/example.xlsx")
+    processFile()
+    mockUtil.fileProcessorContext.assertLanguagesCount(2)
+    mockUtil.fileProcessorContext.assertTranslations("en", "key")
+      .assertSingle {
+        hasText("value")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "key")
+      .assertSingle {
+        hasText("hodnota")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "keyDeep.inner")
+      .assertSingle {
+        hasText("value")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "keyDeep.inner")
+      .assertSingle {
+        hasText("hodnota")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "keyInterpolate")
+      .assertSingle {
+        hasText("replace this {value}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "keyInterpolate")
+      .assertSingle {
+        hasText("nahradit toto {value}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "keyInterpolateWithFormatting")
+      .assertSingle {
+        hasText("replace this {value, number}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "keyInterpolateWithFormatting")
+      .assertSingle {
+        hasText("nahradit toto {value, number}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "keyPluralSimple")
+      .assertSinglePlural {
+        hasText(
+          """
+          {value, plural,
+          one { the singular}
+          other { the plural {value}}
+          }
+          """.trimIndent(),
+        )
+        isPluralOptimized()
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "keyPluralSimple")
+      .assertSinglePlural {
+        hasText(
+          """
+          {value, plural,
+          one { jednotné číslo}
+          other { množné číslo {value}}
+          }
+          """.trimIndent(),
+        )
+        isPluralOptimized()
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "escapedCharacters")
+      .assertSingle {
+        hasText("this is a \"quote\"")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "escapedCharacters")
+      .assertSingle {
+        hasText("toto je \"citace\"")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "escapedCharacters2")
+      .assertSingle {
+        hasText("this is a\nnew line")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "escapedCharacters2")
+      .assertSingle {
+        hasText("toto je\nnový řádek")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "escapedCharacters3")
+      .assertSingle {
+        hasText("this is a \\ backslash")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "escapedCharacters3")
+      .assertSingle {
+        hasText("toto je zpětné \\ lomítko")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "escapedCharacters4")
+      .assertSingle {
+        hasText("this is a , comma")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "escapedCharacters4")
+      .assertSingle {
+        hasText("toto je , čárka")
+      }
+    mockUtil.fileProcessorContext.assertKey("keyPluralSimple") {
+      custom.assert.isNull()
+      description.assert.isNull()
+    }
+  }
+
+  @Test
+  fun `returns correct parsed result (semicolon delimiter)`() {
+    mockUtil.mockIt("example.xlsx", "src/test/resources/import/xlsx/example_semicolon.xlsx")
+    processFile()
+    mockUtil.fileProcessorContext.assertLanguagesCount(2)
+    mockUtil.fileProcessorContext.assertTranslations("en", "key")
+      .assertSingle {
+        hasText("value")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "key")
+      .assertSingle {
+        hasText("hodnota")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "keyDeep.inner")
+      .assertSingle {
+        hasText("value")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "keyDeep.inner")
+      .assertSingle {
+        hasText("hodnota")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "keyInterpolate")
+      .assertSingle {
+        hasText("replace this {value}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "keyInterpolate")
+      .assertSingle {
+        hasText("nahradit toto {value}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "keyInterpolateWithFormatting")
+      .assertSingle {
+        hasText("replace this {value, number}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "keyInterpolateWithFormatting")
+      .assertSingle {
+        hasText("nahradit toto {value, number}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "keyPluralSimple")
+      .assertSinglePlural {
+        hasText(
+          """
+          {value, plural,
+          one { the singular}
+          other { the plural {value}}
+          }
+          """.trimIndent(),
+        )
+        isPluralOptimized()
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "keyPluralSimple")
+      .assertSinglePlural {
+        hasText(
+          """
+          {value, plural,
+          one { jednotné číslo}
+          other { množné číslo {value}}
+          }
+          """.trimIndent(),
+        )
+        isPluralOptimized()
+      }
+    mockUtil.fileProcessorContext.assertKey("keyPluralSimple") {
+      custom.assert.isNull()
+      description.assert.isNull()
+    }
+  }
+
+  @Test
+  fun `returns correct parsed result (tab delimiter)`() {
+    mockUtil.mockIt("example.xlsx", "src/test/resources/import/xlsx/example_tab.xlsx")
+    processFile()
+    mockUtil.fileProcessorContext.assertLanguagesCount(2)
+    mockUtil.fileProcessorContext.assertTranslations("en", "key")
+      .assertSingle {
+        hasText("value")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "key")
+      .assertSingle {
+        hasText("hodnota")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "keyDeep.inner")
+      .assertSingle {
+        hasText("value")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "keyDeep.inner")
+      .assertSingle {
+        hasText("hodnota")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "keyInterpolate")
+      .assertSingle {
+        hasText("replace this {value}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "keyInterpolate")
+      .assertSingle {
+        hasText("nahradit toto {value}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "keyInterpolateWithFormatting")
+      .assertSingle {
+        hasText("replace this {value, number}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "keyInterpolateWithFormatting")
+      .assertSingle {
+        hasText("nahradit toto {value, number}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "keyPluralSimple")
+      .assertSinglePlural {
+        hasText(
+          """
+          {value, plural,
+          one { the singular}
+          other { the plural {value}}
+          }
+          """.trimIndent(),
+        )
+        isPluralOptimized()
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "keyPluralSimple")
+      .assertSinglePlural {
+        hasText(
+          """
+          {value, plural,
+          one { jednotné číslo}
+          other { množné číslo {value}}
+          }
+          """.trimIndent(),
+        )
+        isPluralOptimized()
+      }
+    mockUtil.fileProcessorContext.assertKey("keyPluralSimple") {
+      custom.assert.isNull()
+      description.assert.isNull()
+    }
+  }
+
+  @Test
+  fun `import with placeholder conversion (disabled ICU)`() {
+    mockPlaceholderConversionTestFile(convertPlaceholders = false, projectIcuPlaceholdersEnabled = false)
+    processFile()
+    mockUtil.fileProcessorContext.assertLanguagesCount(2)
+    mockUtil.fileProcessorContext.assertTranslations("en", "key")
+      .assertSingle {
+        hasText("Hello {icuPara}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "key")
+      .assertSingle {
+        hasText("Ahoj {icuPara}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "plural")
+      .assertSinglePlural {
+        hasText(
+          """
+          {icuPara, plural,
+          one {Hello one '#' '{'icuParam'}'}
+          other {Hello other '{'icuParam'}'}
+          }
+          """.trimIndent(),
+        )
+        isPluralOptimized()
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "plural")
+      .assertSinglePlural {
+        hasText(
+          """
+          {icuPara, plural,
+          one {Ahoj jedno '#' '{'icuParam'}'}
+          other {Ahoj jiné '{'icuParam'}'}
+          }
+          """.trimIndent(),
+        )
+        isPluralOptimized()
+      }
+    mockUtil.fileProcessorContext.assertKey("plural") {
+      custom.assert.isNull()
+      description.assert.isNull()
+    }
+  }
+
+  @Test
+  fun `import with placeholder conversion (no conversion)`() {
+    mockPlaceholderConversionTestFile(convertPlaceholders = false, projectIcuPlaceholdersEnabled = true)
+    processFile()
+    mockUtil.fileProcessorContext.assertLanguagesCount(2)
+    mockUtil.fileProcessorContext.assertTranslations("en", "key")
+      .assertSingle {
+        hasText("Hello {icuPara}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "key")
+      .assertSingle {
+        hasText("Ahoj {icuPara}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "plural")
+      .assertSinglePlural {
+        hasText(
+          """
+          {icuPara, plural,
+          one {Hello one # {icuParam}}
+          other {Hello other {icuParam}}
+          }
+          """.trimIndent(),
+        )
+        isPluralOptimized()
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "plural")
+      .assertSinglePlural {
+        hasText(
+          """
+          {icuPara, plural,
+          one {Ahoj jedno # {icuParam}}
+          other {Ahoj jiné {icuParam}}
+          }
+          """.trimIndent(),
+        )
+        isPluralOptimized()
+      }
+    mockUtil.fileProcessorContext.assertKey("plural") {
+      custom.assert.isNull()
+      description.assert.isNull()
+    }
+  }
+
+  @Test
+  fun `import with placeholder conversion (with conversion)`() {
+    mockPlaceholderConversionTestFile(convertPlaceholders = true, projectIcuPlaceholdersEnabled = true)
+    processFile()
+    mockUtil.fileProcessorContext.assertLanguagesCount(2)
+    mockUtil.fileProcessorContext.assertTranslations("en", "key")
+      .assertSingle {
+        hasText("Hello {icuPara}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "key")
+      .assertSingle {
+        hasText("Ahoj {icuPara}")
+      }
+    mockUtil.fileProcessorContext.assertTranslations("en", "plural")
+      .assertSinglePlural {
+        hasText(
+          """
+          {icuPara, plural,
+          one {Hello one # {icuParam}}
+          other {Hello other {icuParam}}
+          }
+          """.trimIndent(),
+        )
+        isPluralOptimized()
+      }
+    mockUtil.fileProcessorContext.assertTranslations("cs", "plural")
+      .assertSinglePlural {
+        hasText(
+          """
+          {icuPara, plural,
+          one {Ahoj jedno # {icuParam}}
+          other {Ahoj jiné {icuParam}}
+          }
+          """.trimIndent(),
+        )
+        isPluralOptimized()
+      }
+    mockUtil.fileProcessorContext.assertKey("plural") {
+      custom.assert.isNull()
+      description.assert.isNull()
+    }
+  }
+
+  @Test
+  fun `placeholder conversion setting application works`() {
+    PlaceholderConversionTestHelper.testFile(
+      "import.xlsx",
+      "src/test/resources/import/xlsx/placeholder_conversion.xlsx",
+      assertBeforeSettingsApplication =
+        listOf(
+          "this is xlsx {0, number}",
+          "this is xlsx",
+          "toto je xlsx {0, number}",
+          "toto je xlsx",
+        ),
+      assertAfterDisablingConversion =
+        listOf(
+          "this is xlsx %d",
+          "toto je xlsx %d",
+        ),
+      assertAfterReEnablingConversion =
+        listOf(
+          "this is xlsx {0, number}",
+          "toto je xlsx {0, number}",
+        ),
+    )
+  }
+
+  private fun mockPlaceholderConversionTestFile(
+    convertPlaceholders: Boolean,
+    projectIcuPlaceholdersEnabled: Boolean,
+  ) {
+    mockUtil.mockIt(
+      "import.xlsx",
+      "src/test/resources/import/xlsx/example_params.xlsx",
+      convertPlaceholders,
+      projectIcuPlaceholdersEnabled,
+    )
+  }
+
+  private fun processFile() {
+    XlsxFileProcessor(mockUtil.fileProcessorContext).process()
+  }
+}
diff --git a/backend/data/src/test/kotlin/io/tolgee/unit/xlsx/in/XlsxImportFormatDetectorTest.kt b/backend/data/src/test/kotlin/io/tolgee/unit/xlsx/in/XlsxImportFormatDetectorTest.kt
new file mode 100644
index 0000000000..5765e76c10
--- /dev/null
+++ b/backend/data/src/test/kotlin/io/tolgee/unit/xlsx/in/XlsxImportFormatDetectorTest.kt
@@ -0,0 +1,57 @@
+package io.tolgee.unit.xlsx.`in`
+
+import io.tolgee.formats.importCommon.ImportFormat
+import io.tolgee.formats.xlsx.`in`.XlsxFileParser
+import io.tolgee.formats.xlsx.`in`.XlsxImportFormatDetector
+import io.tolgee.testing.assert
+import io.tolgee.util.FileProcessorContextMockUtil
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import java.io.File
+
+class XlsxImportFormatDetectorTest {
+  lateinit var mockUtil: FileProcessorContextMockUtil
+
+  @BeforeEach
+  fun setup() {
+    mockUtil = FileProcessorContextMockUtil()
+  }
+
+  @Test
+  fun `detected i18next`() {
+    "src/test/resources/import/xlsx/example.xlsx".assertDetected(ImportFormat.XLSX_ICU)
+  }
+
+  @Test
+  fun `detected icu`() {
+    "src/test/resources/import/xlsx/icu.xlsx".assertDetected(ImportFormat.XLSX_ICU)
+  }
+
+  @Test
+  fun `detected java`() {
+    "src/test/resources/import/xlsx/java.xlsx".assertDetected(ImportFormat.XLSX_JAVA)
+  }
+
+  @Test
+  fun `detected php`() {
+    "src/test/resources/import/xlsx/php.xlsx".assertDetected(ImportFormat.XLSX_PHP)
+  }
+
+  @Test
+  fun `fallbacks to icu`() {
+    "src/test/resources/import/xlsx/unknown.xlsx".assertDetected(ImportFormat.XLSX_ICU)
+  }
+
+  private fun parseFile(path: String): Any? {
+    val parser =
+      XlsxFileParser(
+        inputStream = File(path).inputStream(),
+        languageFallback = "unknown",
+      )
+    return parser.rawData
+  }
+
+  private fun String.assertDetected(format: ImportFormat) {
+    XlsxImportFormatDetector().detectFormat(parseFile(this)).assert.isEqualTo(format)
+  }
+}
diff --git a/backend/data/src/test/kotlin/io/tolgee/unit/xlsx/out/XlsxFileExporterTest.kt b/backend/data/src/test/kotlin/io/tolgee/unit/xlsx/out/XlsxFileExporterTest.kt
new file mode 100644
index 0000000000..c5df14dd5e
--- /dev/null
+++ b/backend/data/src/test/kotlin/io/tolgee/unit/xlsx/out/XlsxFileExporterTest.kt
@@ -0,0 +1,258 @@
+package io.tolgee.unit.xlsx.out
+
+import io.tolgee.component.CurrentDateProvider
+import io.tolgee.dtos.request.export.ExportParams
+import io.tolgee.formats.xlsx.out.XlsxFileExporter
+import io.tolgee.service.export.dataProvider.ExportTranslationView
+import io.tolgee.unit.util.assertFile
+import io.tolgee.unit.util.getExportedCompressed
+import io.tolgee.util.buildExportTranslationList
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.mockito.Mockito
+import java.util.Calendar
+import java.util.Date
+
+class XlsxFileExporterTest {
+  private val currentDateProvider = Mockito.mock(CurrentDateProvider::class.java)
+
+  @BeforeEach
+  fun setup() {
+    val now = Date(Date.UTC(2025 - 1900, Calendar.JANUARY, 10, 0, 0, 0))
+    Mockito.`when`(currentDateProvider.date).thenReturn(now)
+  }
+
+  @AfterEach
+  fun teardown() {
+    Mockito.reset(currentDateProvider)
+  }
+
+  @Test
+  fun `exports with placeholders (ICU placeholders disabled)`() {
+    val exporter = getIcuPlaceholdersDisabledExporter()
+    val data = getExportedCompressed(exporter)
+    data.assertFile(
+      "all.xlsx",
+      """
+    |====================
+    |[Content_Types].xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default ContentType="application/vnd.openxmlformats-package.relationships+xml" Extension="rels"/><Default ContentType="application/xml" Extension="xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" PartName="/docProps/app.xml"/><Override ContentType="application/vnd.openxmlformats-package.core-properties+xml" PartName="/docProps/core.xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" PartName="/xl/sharedStrings.xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" PartName="/xl/styles.xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" PartName="/xl/workbook.xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" PartName="/xl/worksheets/sheet1.xml"/></Types>
+    |====================
+    |_rels/.rels
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Target="xl/workbook.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"/><Relationship Id="rId2" Target="docProps/app.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"/><Relationship Id="rId3" Target="docProps/core.xml" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"/></Relationships>
+    |====================
+    |docProps/app.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"><Application>Apache POI</Application></Properties>
+    |====================
+    |docProps/core.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8" standalone="yes"?><cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dcterms:created xsi:type="dcterms:W3CDTF">2025-01-10T00:00:00Z</dcterms:created><dc:creator>Apache POI</dc:creator></cp:coreProperties>
+    |====================
+    |xl/sharedStrings.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<sst count="8" uniqueCount="7" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><si><t>key</t></si><si><t>cs</t></si><si><t>key3</t></si><si><t>{count, plural, one {# den {icuParam}} few {# dny} other {# dní}}</t></si><si><t>item</t></si><si><t>I will be first {icuParam, number}</t></si><si><t xml:space="preserve">Text with multiple lines
+    |and , commas and "quotes" </t></si></sst>
+    |====================
+    |xl/styles.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><numFmts count="0"/><fonts count="1"><font><sz val="11.0"/><color indexed="8"/><name val="Calibri"/><family val="2"/><scheme val="minor"/></font></fonts><fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="darkGray"/></fill></fills><borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs></styleSheet>
+    |====================
+    |xl/workbook.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><workbookPr date1904="false"/><bookViews><workbookView activeTab="0"/></bookViews><sheets><sheet name="Sheet0" r:id="rId3" sheetId="1"/></sheets></workbook>
+    |====================
+    |xl/_rels/workbook.xml.rels
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Target="sharedStrings.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"/><Relationship Id="rId2" Target="styles.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"/><Relationship Id="rId3" Target="worksheets/sheet1.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"/></Relationships>
+    |====================
+    |xl/worksheets/sheet1.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><dimension ref="A1:B4"/><sheetViews><sheetView workbookViewId="0" tabSelected="true"/></sheetViews><sheetFormatPr defaultRowHeight="15.0"/><sheetData><row r="1"><c r="A1" t="s" s="0"><v>0</v></c><c r="B1" t="s" s="0"><v>1</v></c></row><row r="2"><c r="A2" t="s" s="0"><v>2</v></c><c r="B2" t="s" s="0"><v>3</v></c></row><row r="3"><c r="A3" t="s" s="0"><v>4</v></c><c r="B3" t="s" s="0"><v>5</v></c></row><row r="4"><c r="A4" t="s" s="0"><v>0</v></c><c r="B4" t="s" s="0"><v>6</v></c></row></sheetData><pageMargins bottom="0.75" footer="0.3" header="0.3" left="0.7" right="0.7" top="0.75"/></worksheet>
+    |
+      """.trimMargin(),
+    )
+  }
+
+  private fun getIcuPlaceholdersDisabledExporter(): XlsxFileExporter {
+    val built =
+      buildExportTranslationList {
+        add(
+          languageTag = "cs",
+          keyName = "key3",
+          text = "{count, plural, one {'#' den '{'icuParam'}'} few {'#' dny} other {'#' dní}}",
+        ) {
+          key.isPlural = true
+        }
+        add(
+          languageTag = "cs",
+          keyName = "item",
+          text = "I will be first {icuParam, number}",
+        )
+        add(
+          languageTag = "cs",
+          keyName = "key",
+          text = "Text with multiple lines\nand , commas and \"quotes\" ",
+        )
+      }
+    return getExporter(built.translations, false)
+  }
+
+  @Test
+  fun `exports with placeholders (ICU placeholders enabled)`() {
+    val exporter = getIcuPlaceholdersEnabledExporter()
+    val data = getExportedCompressed(exporter)
+    data.assertFile(
+      "all.xlsx",
+      """
+    |====================
+    |[Content_Types].xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default ContentType="application/vnd.openxmlformats-package.relationships+xml" Extension="rels"/><Default ContentType="application/xml" Extension="xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" PartName="/docProps/app.xml"/><Override ContentType="application/vnd.openxmlformats-package.core-properties+xml" PartName="/docProps/core.xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" PartName="/xl/sharedStrings.xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" PartName="/xl/styles.xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" PartName="/xl/workbook.xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" PartName="/xl/worksheets/sheet1.xml"/></Types>
+    |====================
+    |_rels/.rels
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Target="xl/workbook.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"/><Relationship Id="rId2" Target="docProps/app.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"/><Relationship Id="rId3" Target="docProps/core.xml" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"/></Relationships>
+    |====================
+    |docProps/app.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"><Application>Apache POI</Application></Properties>
+    |====================
+    |docProps/core.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8" standalone="yes"?><cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dcterms:created xsi:type="dcterms:W3CDTF">2025-01-10T00:00:00Z</dcterms:created><dc:creator>Apache POI</dc:creator></cp:coreProperties>
+    |====================
+    |xl/sharedStrings.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<sst count="6" uniqueCount="6" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><si><t>key</t></si><si><t>cs</t></si><si><t>key3</t></si><si><t>{count, plural, one {# den {icuParam, number}} few {# dny} other {# dní}}</t></si><si><t>item</t></si><si><t>I will be first '{'icuParam'}' {hello, number}</t></si></sst>
+    |====================
+    |xl/styles.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><numFmts count="0"/><fonts count="1"><font><sz val="11.0"/><color indexed="8"/><name val="Calibri"/><family val="2"/><scheme val="minor"/></font></fonts><fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="darkGray"/></fill></fills><borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs></styleSheet>
+    |====================
+    |xl/workbook.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><workbookPr date1904="false"/><bookViews><workbookView activeTab="0"/></bookViews><sheets><sheet name="Sheet0" r:id="rId3" sheetId="1"/></sheets></workbook>
+    |====================
+    |xl/_rels/workbook.xml.rels
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Target="sharedStrings.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"/><Relationship Id="rId2" Target="styles.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"/><Relationship Id="rId3" Target="worksheets/sheet1.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"/></Relationships>
+    |====================
+    |xl/worksheets/sheet1.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><dimension ref="A1:B3"/><sheetViews><sheetView workbookViewId="0" tabSelected="true"/></sheetViews><sheetFormatPr defaultRowHeight="15.0"/><sheetData><row r="1"><c r="A1" t="s" s="0"><v>0</v></c><c r="B1" t="s" s="0"><v>1</v></c></row><row r="2"><c r="A2" t="s" s="0"><v>2</v></c><c r="B2" t="s" s="0"><v>3</v></c></row><row r="3"><c r="A3" t="s" s="0"><v>4</v></c><c r="B3" t="s" s="0"><v>5</v></c></row></sheetData><pageMargins bottom="0.75" footer="0.3" header="0.3" left="0.7" right="0.7" top="0.75"/></worksheet>
+    |
+      """.trimMargin(),
+    )
+  }
+
+  @Test
+  fun `correct exports translation with colon`() {
+    val exporter = getExporter(getTranslationWithColon())
+    val data = getExportedCompressed(exporter)
+    data.assertFile(
+      "all.xlsx",
+      """
+    |====================
+    |[Content_Types].xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default ContentType="application/vnd.openxmlformats-package.relationships+xml" Extension="rels"/><Default ContentType="application/xml" Extension="xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" PartName="/docProps/app.xml"/><Override ContentType="application/vnd.openxmlformats-package.core-properties+xml" PartName="/docProps/core.xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" PartName="/xl/sharedStrings.xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" PartName="/xl/styles.xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" PartName="/xl/workbook.xml"/><Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" PartName="/xl/worksheets/sheet1.xml"/></Types>
+    |====================
+    |_rels/.rels
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Target="xl/workbook.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"/><Relationship Id="rId2" Target="docProps/app.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"/><Relationship Id="rId3" Target="docProps/core.xml" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"/></Relationships>
+    |====================
+    |docProps/app.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"><Application>Apache POI</Application></Properties>
+    |====================
+    |docProps/core.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8" standalone="yes"?><cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dcterms:created xsi:type="dcterms:W3CDTF">2025-01-10T00:00:00Z</dcterms:created><dc:creator>Apache POI</dc:creator></cp:coreProperties>
+    |====================
+    |xl/sharedStrings.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<sst count="4" uniqueCount="4" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><si><t>key</t></si><si><t>cs</t></si><si><t>item</t></si><si><t>name : {name}</t></si></sst>
+    |====================
+    |xl/styles.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><numFmts count="0"/><fonts count="1"><font><sz val="11.0"/><color indexed="8"/><name val="Calibri"/><family val="2"/><scheme val="minor"/></font></fonts><fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="darkGray"/></fill></fills><borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs></styleSheet>
+    |====================
+    |xl/workbook.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><workbookPr date1904="false"/><bookViews><workbookView activeTab="0"/></bookViews><sheets><sheet name="Sheet0" r:id="rId3" sheetId="1"/></sheets></workbook>
+    |====================
+    |xl/_rels/workbook.xml.rels
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Target="sharedStrings.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"/><Relationship Id="rId2" Target="styles.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"/><Relationship Id="rId3" Target="worksheets/sheet1.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"/></Relationships>
+    |====================
+    |xl/worksheets/sheet1.xml
+    |--------------------
+    |<?xml version="1.0" encoding="UTF-8"?>
+    |<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><dimension ref="A1:B2"/><sheetViews><sheetView workbookViewId="0" tabSelected="true"/></sheetViews><sheetFormatPr defaultRowHeight="15.0"/><sheetData><row r="1"><c r="A1" t="s" s="0"><v>0</v></c><c r="B1" t="s" s="0"><v>1</v></c></row><row r="2"><c r="A2" t="s" s="0"><v>2</v></c><c r="B2" t="s" s="0"><v>3</v></c></row></sheetData><pageMargins bottom="0.75" footer="0.3" header="0.3" left="0.7" right="0.7" top="0.75"/></worksheet>
+    |
+      """.trimMargin(),
+    )
+  }
+
+  private fun getTranslationWithColon(): MutableList<ExportTranslationView> {
+    val built =
+      buildExportTranslationList {
+        add(
+          languageTag = "cs",
+          keyName = "item",
+          text = "name : {name}",
+        )
+      }
+    return built.translations
+  }
+
+  private fun getIcuPlaceholdersEnabledExporter(): XlsxFileExporter {
+    val built =
+      buildExportTranslationList {
+        add(
+          languageTag = "cs",
+          keyName = "key3",
+          text = "{count, plural, one {# den {icuParam, number}} few {# dny} other {# dní}}",
+        ) {
+          key.isPlural = true
+        }
+        add(
+          languageTag = "cs",
+          keyName = "item",
+          text = "I will be first '{'icuParam'}' {hello, number}",
+        )
+      }
+    return getExporter(built.translations, true)
+  }
+
+  private fun getExporter(
+    translations: List<ExportTranslationView>,
+    isProjectIcuPlaceholdersEnabled: Boolean = true,
+    exportParams: ExportParams = ExportParams(),
+  ): XlsxFileExporter {
+    return XlsxFileExporter(
+      currentDateProvider.date,
+      translations = translations,
+      exportParams = exportParams,
+      isProjectIcuPlaceholdersEnabled = isProjectIcuPlaceholdersEnabled,
+    )
+  }
+}
diff --git a/backend/data/src/test/resources/import/xlsx/example.xlsx b/backend/data/src/test/resources/import/xlsx/example.xlsx
new file mode 100644
index 0000000000..71b6b1ca03
Binary files /dev/null and b/backend/data/src/test/resources/import/xlsx/example.xlsx differ
diff --git a/backend/data/src/test/resources/import/xlsx/example_params.xlsx b/backend/data/src/test/resources/import/xlsx/example_params.xlsx
new file mode 100644
index 0000000000..4b1cd479dd
Binary files /dev/null and b/backend/data/src/test/resources/import/xlsx/example_params.xlsx differ
diff --git a/backend/data/src/test/resources/import/xlsx/example_semicolon.xlsx b/backend/data/src/test/resources/import/xlsx/example_semicolon.xlsx
new file mode 100644
index 0000000000..c84ceaf57a
Binary files /dev/null and b/backend/data/src/test/resources/import/xlsx/example_semicolon.xlsx differ
diff --git a/backend/data/src/test/resources/import/xlsx/example_tab.xlsx b/backend/data/src/test/resources/import/xlsx/example_tab.xlsx
new file mode 100644
index 0000000000..274578729c
Binary files /dev/null and b/backend/data/src/test/resources/import/xlsx/example_tab.xlsx differ
diff --git a/backend/data/src/test/resources/import/xlsx/icu.xlsx b/backend/data/src/test/resources/import/xlsx/icu.xlsx
new file mode 100644
index 0000000000..2501ccdd4a
Binary files /dev/null and b/backend/data/src/test/resources/import/xlsx/icu.xlsx differ
diff --git a/backend/data/src/test/resources/import/xlsx/java.xlsx b/backend/data/src/test/resources/import/xlsx/java.xlsx
new file mode 100644
index 0000000000..ebe4227d7e
Binary files /dev/null and b/backend/data/src/test/resources/import/xlsx/java.xlsx differ
diff --git a/backend/data/src/test/resources/import/xlsx/php.xlsx b/backend/data/src/test/resources/import/xlsx/php.xlsx
new file mode 100644
index 0000000000..3f807f2430
Binary files /dev/null and b/backend/data/src/test/resources/import/xlsx/php.xlsx differ
diff --git a/backend/data/src/test/resources/import/xlsx/placeholder_conversion.xlsx b/backend/data/src/test/resources/import/xlsx/placeholder_conversion.xlsx
new file mode 100644
index 0000000000..4624b35167
Binary files /dev/null and b/backend/data/src/test/resources/import/xlsx/placeholder_conversion.xlsx differ
diff --git a/backend/data/src/test/resources/import/xlsx/simple.xlsx b/backend/data/src/test/resources/import/xlsx/simple.xlsx
new file mode 100644
index 0000000000..5184105537
Binary files /dev/null and b/backend/data/src/test/resources/import/xlsx/simple.xlsx differ
diff --git a/backend/data/src/test/resources/import/xlsx/unknown.xlsx b/backend/data/src/test/resources/import/xlsx/unknown.xlsx
new file mode 100644
index 0000000000..27146f379b
Binary files /dev/null and b/backend/data/src/test/resources/import/xlsx/unknown.xlsx differ
diff --git a/e2e/cypress/common/export.ts b/e2e/cypress/common/export.ts
index 8365099bb3..22f9755cf4 100644
--- a/e2e/cypress/common/export.ts
+++ b/e2e/cypress/common/export.ts
@@ -241,6 +241,16 @@ export const testExportFormats = (
       },
     }
   );
+
+  testFormatWithMessageFormats(
+    ['ICU', 'PHP Sprintf', 'C Sprintf', 'Ruby Sprintf', 'Java String.format'],
+    {
+      format: 'XLSX',
+      expectedParams: {
+        format: 'XLSX',
+      },
+    }
+  );
 };
 
 const testFormat = (
diff --git a/webapp/src/service/apiSchema.generated.ts b/webapp/src/service/apiSchema.generated.ts
index 3e80d71cbc..92dd27fe93 100644
--- a/webapp/src/service/apiSchema.generated.ts
+++ b/webapp/src/service/apiSchema.generated.ts
@@ -343,6 +343,12 @@ export interface paths {
     /** Pairs user account with slack account. */
     post: operations["userLogin"];
   };
+  "/v2/public/translator/translate": {
+    post: operations["translate"];
+  };
+  "/v2/public/telemetry/report": {
+    post: operations["report"];
+  };
   "/v2/public/slack": {
     post: operations["slackCommand"];
   };
@@ -358,8 +364,26 @@ export interface paths {
      */
     post: operations["fetchBotEvent"];
   };
+  "/v2/public/licensing/subscription": {
+    post: operations["getMySubscription"];
+  };
+  "/v2/public/licensing/set-key": {
+    post: operations["onLicenceSetKey"];
+  };
+  "/v2/public/licensing/report-usage": {
+    post: operations["reportUsage"];
+  };
+  "/v2/public/licensing/report-error": {
+    post: operations["reportError"];
+  };
+  "/v2/public/licensing/release-key": {
+    post: operations["releaseKey"];
+  };
+  "/v2/public/licensing/prepare-set-key": {
+    post: operations["prepareSetLicenseKey"];
+  };
   "/v2/public/business-events/report": {
-    post: operations["report"];
+    post: operations["report_1"];
   };
   "/v2/public/business-events/identify": {
     post: operations["identify"];
@@ -438,7 +462,7 @@ export interface paths {
   };
   "/v2/projects/{projectId}/start-batch-job/pre-translate-by-tm": {
     /** Pre-translate provided keys to provided languages by TM. */
-    post: operations["translate"];
+    post: operations["translate_1"];
   };
   "/v2/projects/{projectId}/start-batch-job/machine-translate": {
     /** Translate provided keys to provided languages through primary MT provider. */
@@ -524,7 +548,7 @@ export interface paths {
   };
   "/v2/ee-license/prepare-set-license-key": {
     /** Get info about the upcoming EE subscription. This will show, how much the subscription will cost when key is applied. */
-    post: operations["prepareSetLicenseKey"];
+    post: operations["prepareSetLicenseKey_1"];
   };
   "/v2/api-keys": {
     get: operations["allByUser"];
@@ -1206,6 +1230,14 @@ export interface components {
         | "SERVER_ADMIN";
       /** @description The user's permission type. This field is null if uses granular permissions */
       type?: "NONE" | "VIEW" | "TRANSLATE" | "REVIEW" | "EDIT" | "MANAGE";
+      /**
+       * @deprecated
+       * @description Deprecated (use translateLanguageIds).
+       *
+       * List of languages current user has TRANSLATE permission to. If null, all languages edition is permitted.
+       * @example 200001,200004
+       */
+      permittedLanguageIds?: number[];
       /**
        * @description List of languages user can translate to. If null, all languages editing is permitted.
        * @example 200001,200004
@@ -1216,14 +1248,6 @@ export interface components {
        * @example 200001,200004
        */
       stateChangeLanguageIds?: number[];
-      /**
-       * @deprecated
-       * @description Deprecated (use translateLanguageIds).
-       *
-       * List of languages current user has TRANSLATE permission to. If null, all languages edition is permitted.
-       * @example 200001,200004
-       */
-      permittedLanguageIds?: number[];
       /**
        * @description List of languages user can view. If null, all languages view is permitted.
        * @example 200001,200004
@@ -1994,7 +2018,8 @@ export interface components {
         | "YAML"
         | "JSON_I18NEXT"
         | "CSV"
-        | "RESX_ICU";
+        | "RESX_ICU"
+        | "XLSX";
       /**
        * @description Delimiter to structure file content.
        *
@@ -2097,7 +2122,8 @@ export interface components {
         | "YAML"
         | "JSON_I18NEXT"
         | "CSV"
-        | "RESX_ICU";
+        | "RESX_ICU"
+        | "XLSX";
       /**
        * @description Delimiter to structure file content.
        *
@@ -2209,10 +2235,10 @@ export interface components {
       createNewKeys: boolean;
     };
     ImportSettingsModel: {
-      /** @description If true, placeholders from other formats will be converted to ICU when possible */
-      convertPlaceholdersToIcu: boolean;
       /** @description If true, key descriptions will be overridden by the import */
       overrideKeyDescriptions: boolean;
+      /** @description If true, placeholders from other formats will be converted to ICU when possible */
+      convertPlaceholdersToIcu: boolean;
       /** @description If false, only updates keys, skipping the creation of new keys */
       createNewKeys: boolean;
     };
@@ -2377,11 +2403,11 @@ export interface components {
       expiresAt?: number;
       /** Format: int64 */
       lastUsedAt?: number;
+      description: string;
       /** Format: int64 */
       createdAt: number;
       /** Format: int64 */
       updatedAt: number;
-      description: string;
     };
     SetOrganizationRoleDto: {
       roleType: "MEMBER" | "OWNER";
@@ -2542,16 +2568,16 @@ export interface components {
       /** Format: int64 */
       id: number;
       userFullName?: string;
+      projectName: string;
+      scopes: string[];
+      /** Format: int64 */
+      projectId: number;
       /** Format: int64 */
       expiresAt?: number;
       /** Format: int64 */
       lastUsedAt?: number;
-      scopes: string[];
-      /** Format: int64 */
-      projectId: number;
-      username?: string;
       description: string;
-      projectName: string;
+      username?: string;
     };
     SuperTokenRequest: {
       /** @description Has to be provided when TOTP enabled */
@@ -2563,6 +2589,49 @@ export interface components {
       name: string;
       oldSlug?: string;
     };
+    ExampleItem: {
+      source: string;
+      target: string;
+      key: string;
+      keyNamespace?: string;
+    };
+    Metadata: {
+      examples: components["schemas"]["ExampleItem"][];
+      closeItems: components["schemas"]["ExampleItem"][];
+      keyDescription?: string;
+      projectDescription?: string;
+      languageDescription?: string;
+    };
+    TolgeeTranslateParams: {
+      text: string;
+      keyName?: string;
+      sourceTag: string;
+      targetTag: string;
+      metadata?: components["schemas"]["Metadata"];
+      formality?: "FORMAL" | "INFORMAL" | "DEFAULT";
+      isBatch: boolean;
+      pluralForms?: { [key: string]: string };
+      pluralFormExamples?: { [key: string]: string };
+    };
+    MtResult: {
+      translated?: string;
+      /** Format: int32 */
+      price: number;
+      contextDescription?: string;
+    };
+    TelemetryReportRequest: {
+      instanceId: string;
+      /** Format: int64 */
+      projectsCount: number;
+      /** Format: int64 */
+      translationsCount: number;
+      /** Format: int64 */
+      languagesCount: number;
+      /** Format: int64 */
+      distinctLanguagesCount: number;
+      /** Format: int64 */
+      usersCount: number;
+    };
     SlackCommandDto: {
       token?: string;
       team_id: string;
@@ -2575,6 +2644,130 @@ export interface components {
       trigger_id?: string;
       team_domain: string;
     };
+    GetMySubscriptionDto: {
+      licenseKey: string;
+      instanceId: string;
+    };
+    PlanIncludedUsageModel: {
+      /** Format: int64 */
+      seats: number;
+      /** Format: int64 */
+      translationSlots: number;
+      /** Format: int64 */
+      translations: number;
+      /** Format: int64 */
+      mtCredits: number;
+    };
+    PlanPricesModel: {
+      perSeat: number;
+      perThousandTranslations?: number;
+      perThousandMtCredits?: number;
+      subscriptionMonthly: number;
+      subscriptionYearly: number;
+    };
+    SelfHostedEePlanModel: {
+      /** Format: int64 */
+      id: number;
+      name: string;
+      public: boolean;
+      enabledFeatures: (
+        | "GRANULAR_PERMISSIONS"
+        | "PRIORITIZED_FEATURE_REQUESTS"
+        | "PREMIUM_SUPPORT"
+        | "DEDICATED_SLACK_CHANNEL"
+        | "ASSISTED_UPDATES"
+        | "DEPLOYMENT_ASSISTANCE"
+        | "BACKUP_CONFIGURATION"
+        | "TEAM_TRAINING"
+        | "ACCOUNT_MANAGER"
+        | "STANDARD_SUPPORT"
+        | "PROJECT_LEVEL_CONTENT_STORAGES"
+        | "WEBHOOKS"
+        | "MULTIPLE_CONTENT_DELIVERY_CONFIGS"
+        | "AI_PROMPT_CUSTOMIZATION"
+        | "SLACK_INTEGRATION"
+        | "TASKS"
+        | "SSO"
+        | "ORDER_TRANSLATION"
+      )[];
+      prices: components["schemas"]["PlanPricesModel"];
+      includedUsage: components["schemas"]["PlanIncludedUsageModel"];
+      hasYearlyPrice: boolean;
+      free: boolean;
+      nonCommercial: boolean;
+    };
+    SelfHostedEeSubscriptionModel: {
+      /** Format: int64 */
+      id: number;
+      /** Format: int64 */
+      currentPeriodStart?: number;
+      /** Format: int64 */
+      currentPeriodEnd?: number;
+      currentBillingPeriod: "MONTHLY" | "YEARLY";
+      /** Format: int64 */
+      createdAt: number;
+      plan: components["schemas"]["SelfHostedEePlanModel"];
+      status:
+        | "ACTIVE"
+        | "CANCELED"
+        | "PAST_DUE"
+        | "UNPAID"
+        | "ERROR"
+        | "KEY_USED_BY_ANOTHER_INSTANCE";
+      licenseKey?: string;
+      estimatedCosts?: number;
+    };
+    SetLicenseKeyLicensingDto: {
+      licenseKey: string;
+      /** Format: int64 */
+      seats: number;
+      instanceId: string;
+    };
+    ReportUsageDto: {
+      licenseKey: string;
+      /** Format: int64 */
+      seats: number;
+    };
+    ReportErrorDto: {
+      stackTrace: string;
+      licenseKey: string;
+    };
+    ReleaseKeyDto: {
+      licenseKey: string;
+    };
+    PrepareSetLicenseKeyDto: {
+      licenseKey: string;
+      /** Format: int64 */
+      seats: number;
+    };
+    AverageProportionalUsageItemModel: {
+      total: number;
+      unusedQuantity: number;
+      usedQuantity: number;
+      usedQuantityOverPlan: number;
+    };
+    PrepareSetEeLicenceKeyModel: {
+      plan: components["schemas"]["SelfHostedEePlanModel"];
+      usage: components["schemas"]["UsageModel"];
+    };
+    SumUsageItemModel: {
+      total: number;
+      /** Format: int64 */
+      unusedQuantity: number;
+      /** Format: int64 */
+      usedQuantity: number;
+      /** Format: int64 */
+      usedQuantityOverPlan: number;
+    };
+    UsageModel: {
+      subscriptionPrice?: number;
+      /** @description Relevant for invoices only. When there are applied stripe credits, we need to reduce the total price by this amount. */
+      appliedStripeCredits?: number;
+      seats: components["schemas"]["AverageProportionalUsageItemModel"];
+      translations: components["schemas"]["AverageProportionalUsageItemModel"];
+      credits?: components["schemas"]["SumUsageItemModel"];
+      total: number;
+    };
     BusinessEventReportRequest: {
       eventName: string;
       anonymousUserId?: string;
@@ -3143,7 +3336,11 @@ export interface components {
         | "XLIFF_JAVA"
         | "XLIFF_PHP"
         | "XLIFF_RUBY"
-        | "RESX_ICU";
+        | "RESX_ICU"
+        | "XLSX_ICU"
+        | "XLSX_JAVA"
+        | "XLSX_PHP"
+        | "XLSX_RUBY";
       /**
        * @description The existing language tag in the Tolgee platform to which the imported language should be mapped.
        *
@@ -3280,7 +3477,8 @@ export interface components {
         | "YAML"
         | "JSON_I18NEXT"
         | "CSV"
-        | "RESX_ICU";
+        | "RESX_ICU"
+        | "XLSX";
       /**
        * @description Delimiter to structure file content.
        *
@@ -3452,82 +3650,6 @@ export interface components {
       createdAt: string;
       location?: string;
     };
-    AverageProportionalUsageItemModel: {
-      total: number;
-      unusedQuantity: number;
-      usedQuantity: number;
-      usedQuantityOverPlan: number;
-    };
-    PlanIncludedUsageModel: {
-      /** Format: int64 */
-      seats: number;
-      /** Format: int64 */
-      translationSlots: number;
-      /** Format: int64 */
-      translations: number;
-      /** Format: int64 */
-      mtCredits: number;
-    };
-    PlanPricesModel: {
-      perSeat: number;
-      perThousandTranslations?: number;
-      perThousandMtCredits?: number;
-      subscriptionMonthly: number;
-      subscriptionYearly: number;
-    };
-    PrepareSetEeLicenceKeyModel: {
-      plan: components["schemas"]["SelfHostedEePlanModel"];
-      usage: components["schemas"]["UsageModel"];
-    };
-    SelfHostedEePlanModel: {
-      /** Format: int64 */
-      id: number;
-      name: string;
-      public: boolean;
-      enabledFeatures: (
-        | "GRANULAR_PERMISSIONS"
-        | "PRIORITIZED_FEATURE_REQUESTS"
-        | "PREMIUM_SUPPORT"
-        | "DEDICATED_SLACK_CHANNEL"
-        | "ASSISTED_UPDATES"
-        | "DEPLOYMENT_ASSISTANCE"
-        | "BACKUP_CONFIGURATION"
-        | "TEAM_TRAINING"
-        | "ACCOUNT_MANAGER"
-        | "STANDARD_SUPPORT"
-        | "PROJECT_LEVEL_CONTENT_STORAGES"
-        | "WEBHOOKS"
-        | "MULTIPLE_CONTENT_DELIVERY_CONFIGS"
-        | "AI_PROMPT_CUSTOMIZATION"
-        | "SLACK_INTEGRATION"
-        | "TASKS"
-        | "SSO"
-        | "ORDER_TRANSLATION"
-      )[];
-      prices: components["schemas"]["PlanPricesModel"];
-      includedUsage: components["schemas"]["PlanIncludedUsageModel"];
-      hasYearlyPrice: boolean;
-      free: boolean;
-      nonCommercial: boolean;
-    };
-    SumUsageItemModel: {
-      total: number;
-      /** Format: int64 */
-      unusedQuantity: number;
-      /** Format: int64 */
-      usedQuantity: number;
-      /** Format: int64 */
-      usedQuantityOverPlan: number;
-    };
-    UsageModel: {
-      subscriptionPrice?: number;
-      /** @description Relevant for invoices only. When there are applied stripe credits, we need to reduce the total price by this amount. */
-      appliedStripeCredits?: number;
-      seats: components["schemas"]["AverageProportionalUsageItemModel"];
-      translations: components["schemas"]["AverageProportionalUsageItemModel"];
-      credits?: components["schemas"]["SumUsageItemModel"];
-      total: number;
-    };
     CreateApiKeyDto: {
       /** Format: int64 */
       projectId: number;
@@ -3759,13 +3881,13 @@ export interface components {
       name: string;
       /** Format: int64 */
       id: number;
+      basePermissions: components["schemas"]["PermissionModel"];
       /**
        * @description The role of currently authorized user.
        *
        * Can be null when user has direct access to one of the projects owned by the organization.
        */
       currentUserRole?: "MEMBER" | "OWNER";
-      basePermissions: components["schemas"]["PermissionModel"];
       /** @example btforg */
       slug: string;
       avatar?: components["schemas"]["Avatar"];
@@ -3841,7 +3963,8 @@ export interface components {
         | "YAML"
         | "JSON_I18NEXT"
         | "CSV"
-        | "RESX_ICU";
+        | "RESX_ICU"
+        | "XLSX";
       extension: string;
       mediaType: string;
       defaultFileStructureTemplate: string;
@@ -4565,11 +4688,11 @@ export interface components {
       expiresAt?: number;
       /** Format: int64 */
       lastUsedAt?: number;
+      description: string;
       /** Format: int64 */
       createdAt: number;
       /** Format: int64 */
       updatedAt: number;
-      description: string;
     };
     PagedModelOrganizationModel: {
       _embedded?: {
@@ -4689,16 +4812,16 @@ export interface components {
       /** Format: int64 */
       id: number;
       userFullName?: string;
+      projectName: string;
+      scopes: string[];
       /** Format: int64 */
-      expiresAt?: number;
+      projectId: number;
       /** Format: int64 */
-      lastUsedAt?: number;
-      scopes: string[];
+      expiresAt?: number;
       /** Format: int64 */
-      projectId: number;
-      username?: string;
+      lastUsedAt?: number;
       description: string;
-      projectName: string;
+      username?: string;
     };
     PagedModelUserAccountModel: {
       _embedded?: {
@@ -10508,6 +10631,96 @@ export interface operations {
       };
     };
   };
+  translate: {
+    responses: {
+      /** OK */
+      200: {
+        content: {
+          "application/json": components["schemas"]["MtResult"];
+        };
+      };
+      /** Bad Request */
+      400: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Unauthorized */
+      401: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Forbidden */
+      403: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Not Found */
+      404: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+    };
+    requestBody: {
+      content: {
+        "application/json": components["schemas"]["TolgeeTranslateParams"];
+      };
+    };
+  };
+  report: {
+    responses: {
+      /** OK */
+      200: unknown;
+      /** Bad Request */
+      400: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Unauthorized */
+      401: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Forbidden */
+      403: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Not Found */
+      404: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+    };
+    requestBody: {
+      content: {
+        "application/json": components["schemas"]["TelemetryReportRequest"];
+      };
+    };
+  };
   slackCommand: {
     parameters: {
       header: {
@@ -10672,7 +10885,277 @@ export interface operations {
       };
     };
   };
-  report: {
+  getMySubscription: {
+    responses: {
+      /** OK */
+      200: {
+        content: {
+          "application/json": components["schemas"]["SelfHostedEeSubscriptionModel"];
+        };
+      };
+      /** Bad Request */
+      400: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Unauthorized */
+      401: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Forbidden */
+      403: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Not Found */
+      404: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+    };
+    requestBody: {
+      content: {
+        "application/json": components["schemas"]["GetMySubscriptionDto"];
+      };
+    };
+  };
+  onLicenceSetKey: {
+    responses: {
+      /** OK */
+      200: {
+        content: {
+          "application/json": components["schemas"]["SelfHostedEeSubscriptionModel"];
+        };
+      };
+      /** Bad Request */
+      400: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Unauthorized */
+      401: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Forbidden */
+      403: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Not Found */
+      404: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+    };
+    requestBody: {
+      content: {
+        "application/json": components["schemas"]["SetLicenseKeyLicensingDto"];
+      };
+    };
+  };
+  reportUsage: {
+    responses: {
+      /** OK */
+      200: unknown;
+      /** Bad Request */
+      400: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Unauthorized */
+      401: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Forbidden */
+      403: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Not Found */
+      404: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+    };
+    requestBody: {
+      content: {
+        "application/json": components["schemas"]["ReportUsageDto"];
+      };
+    };
+  };
+  reportError: {
+    responses: {
+      /** OK */
+      200: unknown;
+      /** Bad Request */
+      400: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Unauthorized */
+      401: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Forbidden */
+      403: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Not Found */
+      404: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+    };
+    requestBody: {
+      content: {
+        "application/json": components["schemas"]["ReportErrorDto"];
+      };
+    };
+  };
+  releaseKey: {
+    responses: {
+      /** OK */
+      200: unknown;
+      /** Bad Request */
+      400: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Unauthorized */
+      401: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Forbidden */
+      403: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Not Found */
+      404: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+    };
+    requestBody: {
+      content: {
+        "application/json": components["schemas"]["ReleaseKeyDto"];
+      };
+    };
+  };
+  prepareSetLicenseKey: {
+    responses: {
+      /** OK */
+      200: {
+        content: {
+          "application/json": components["schemas"]["PrepareSetEeLicenceKeyModel"];
+        };
+      };
+      /** Bad Request */
+      400: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Unauthorized */
+      401: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Forbidden */
+      403: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+      /** Not Found */
+      404: {
+        content: {
+          "application/json":
+            | components["schemas"]["ErrorResponseTyped"]
+            | components["schemas"]["ErrorResponseBody"];
+        };
+      };
+    };
+    requestBody: {
+      content: {
+        "application/json": components["schemas"]["PrepareSetLicenseKeyDto"];
+      };
+    };
+  };
+  report_1: {
     responses: {
       /** OK */
       200: unknown;
@@ -12173,7 +12656,7 @@ export interface operations {
     };
   };
   /** Pre-translate provided keys to provided languages by TM. */
-  translate: {
+  translate_1: {
     parameters: {
       path: {
         projectId: number;
@@ -12616,7 +13099,8 @@ export interface operations {
           | "YAML"
           | "JSON_I18NEXT"
           | "CSV"
-          | "RESX_ICU";
+          | "RESX_ICU"
+          | "XLSX";
         /**
          * Delimiter to structure file content.
          *
@@ -13693,7 +14177,7 @@ export interface operations {
     };
   };
   /** Get info about the upcoming EE subscription. This will show, how much the subscription will cost when key is applied. */
-  prepareSetLicenseKey: {
+  prepareSetLicenseKey_1: {
     responses: {
       /** OK */
       200: {
diff --git a/webapp/src/svgs/logos/xlsx.svg b/webapp/src/svgs/logos/xlsx.svg
new file mode 100644
index 0000000000..64559c916b
--- /dev/null
+++ b/webapp/src/svgs/logos/xlsx.svg
@@ -0,0 +1,3 @@
+<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path fill-rule="evenodd" clip-rule="evenodd" d="M18 2.00488H6C4.89543 2.00488 4 2.90031 4 4.00488V6.00488H20V4.00488C20 2.90031 19.1046 2.00488 18 2.00488ZM0 10.0049C0 8.52432 0.804397 7.23163 2 6.54001V4.00488C2 1.79574 3.79086 0.00488281 6 0.00488281H18C20.2091 0.00488281 22 1.79574 22 4.00488V6.54001C23.1956 7.23163 24 8.52432 24 10.0049V16.0049C24 17.4854 23.1956 18.7781 22 19.4698V20.0049C22 22.214 20.2091 24.0049 18 24.0049H6C3.79086 24.0049 2 22.214 2 20.0049V19.4698C0.804397 18.7781 0 17.4854 0 16.0049V10.0049ZM4 20.0049H20C20 21.1095 19.1046 22.0049 18 22.0049H6C4.89543 22.0049 4 21.1095 4 20.0049ZM18.168 14.5264C18.193 14.623 18.2056 14.7323 18.2056 14.854C18.2056 14.9937 18.1877 15.1226 18.1519 15.2407C18.116 15.3553 18.0516 15.4484 17.9585 15.52C17.869 15.5881 17.7419 15.6221 17.5771 15.6221C17.3981 15.6221 17.2477 15.6024 17.126 15.563C17.0042 15.52 16.904 15.452 16.8252 15.3589C16.75 15.2622 16.6945 15.1351 16.6587 14.9775C16.6229 14.82 16.605 14.6266 16.605 14.3975H14.7573C14.7573 14.8594 14.8379 15.2586 14.999 15.5952C15.1602 15.9282 15.375 16.2021 15.6436 16.417C15.9157 16.6283 16.2183 16.7858 16.5513 16.8896C16.8879 16.9899 17.2298 17.04 17.5771 17.04C17.9567 17.04 18.2969 16.9917 18.5977 16.895C18.902 16.7983 19.1616 16.6569 19.3765 16.4707C19.5913 16.2809 19.7542 16.05 19.8652 15.7778C19.9798 15.5057 20.0371 15.1942 20.0371 14.8433C20.0371 14.5138 19.9888 14.2184 19.8921 13.957C19.799 13.6956 19.6593 13.4629 19.4731 13.2588C19.2869 13.0511 19.056 12.8667 18.7803 12.7056C18.5081 12.5409 18.1948 12.3923 17.8403 12.2598C17.6935 12.1882 17.5557 12.1147 17.4268 12.0396C17.3014 11.9644 17.1904 11.8856 17.0938 11.8032C17.0007 11.7209 16.9272 11.6331 16.8735 11.54C16.8198 11.4434 16.793 11.3359 16.793 11.2178C16.793 11.0745 16.8198 10.9438 16.8735 10.8257C16.9272 10.7039 17.006 10.6073 17.1099 10.5356C17.2173 10.464 17.348 10.4282 17.502 10.4282C17.6738 10.4282 17.8117 10.4712 17.9155 10.5571C18.0229 10.6431 18.1017 10.7612 18.1519 10.9116C18.202 11.062 18.2271 11.2357 18.2271 11.4326H20.0586C20.0586 10.9385 19.9512 10.5106 19.7363 10.1489C19.5251 9.78369 19.2297 9.5026 18.8501 9.30566C18.4741 9.10514 18.0391 9.00488 17.5449 9.00488C17.1725 9.00488 16.8288 9.0568 16.5137 9.16064C16.1986 9.26091 15.9246 9.40771 15.6919 9.60107C15.4627 9.79085 15.2819 10.0218 15.1494 10.2939C15.0205 10.5625 14.9561 10.8651 14.9561 11.2017C14.9561 11.5526 15.0205 11.8605 15.1494 12.1255C15.2783 12.3869 15.4484 12.616 15.6597 12.813C15.8745 13.0063 16.1162 13.1764 16.3848 13.3232C16.6533 13.4665 16.9272 13.5954 17.2065 13.71C17.4285 13.8066 17.6058 13.8997 17.7383 13.9893C17.8708 14.0752 17.9692 14.1611 18.0337 14.2471C18.1017 14.333 18.1465 14.4261 18.168 14.5264ZM14.1289 16.9326V15.4824H11.4863V9.1123H9.64941V16.9326H10.8418H11.4863H14.1289ZM5.1377 9.1123L6.01855 11.7119L6.88867 9.1123H8.97803L7.23242 12.9902L9.021 16.9326H6.91016L6.01855 14.2847L5.12158 16.9326H3L4.79932 12.9902L3.04297 9.1123H5.1377Z" fill="currentColor" fill-opacity="0.87"/>
+</svg>
diff --git a/webapp/src/views/projects/export/components/formatGroups.tsx b/webapp/src/views/projects/export/components/formatGroups.tsx
index 9cc271f854..a5dbe5699e 100644
--- a/webapp/src/views/projects/export/components/formatGroups.tsx
+++ b/webapp/src/views/projects/export/components/formatGroups.tsx
@@ -156,6 +156,19 @@ export const formatGroups: FormatGroup[] = [
           'RUBY_SPRINTF',
         ],
       },
+      {
+        id: 'generic_xlsx',
+        extension: 'xlsx',
+        name: <T keyName="export-format-xlsx" />,
+        format: 'XLSX',
+        supportedMessageFormats: [
+          'ICU',
+          'JAVA_STRING_FORMAT',
+          'PHP_SPRINTF',
+          'C_SPRINTF',
+          'RUBY_SPRINTF',
+        ],
+      },
     ],
   },
   {
diff --git a/webapp/src/views/projects/import/component/ImportSupportedFormats.tsx b/webapp/src/views/projects/import/component/ImportSupportedFormats.tsx
index 8aa0c9fcd2..e398c68acb 100644
--- a/webapp/src/views/projects/import/component/ImportSupportedFormats.tsx
+++ b/webapp/src/views/projects/import/component/ImportSupportedFormats.tsx
@@ -14,6 +14,7 @@ import RailsLogo from 'tg.svgs/logos/rails.svg?react';
 import I18nextLogo from 'tg.svgs/logos/i18next.svg?react';
 import CsvLogo from 'tg.svgs/logos/csv.svg?react';
 import DotnetLogo from 'tg.svgs/logos/dotnet.svg?react';
+import XlsxLogo from 'tg.svgs/logos/xlsx.svg?react';
 
 const TechLogo = ({
   svg,
@@ -61,6 +62,7 @@ const FORMATS = [
   { name: 'i18next', logo: <I18nextLogo /> },
   { name: 'CSV', logo: <CsvLogo /> },
   { name: '.NET RESX', logo: <DotnetLogo /> },
+  { name: 'XLSX', logo: <XlsxLogo /> },
 ];
 
 export const ImportSupportedFormats = () => {