diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts
index 3fde960f..3e553fee 100644
--- a/mobile/build.gradle.kts
+++ b/mobile/build.gradle.kts
@@ -29,8 +29,8 @@ android {
// WICHTIG: Bei jedem neuen Debug-Build erhöhen. Android erkennt eine APK mit
// unverändertem versionCode (+ gleicher Signatur) auf manchen Geräten nicht
// zuverlässig als Update, wodurch eine Neuinstallation still fehlschlagen kann.
- versionCode = 41
- versionName = "3.1.0"
+ versionCode = 44
+ versionName = "3.3.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -92,6 +92,8 @@ dependencies {
androidTestImplementation(composeBom)
implementation("androidx.activity:activity-compose:1.9.2")
+ // Lint prüft beim Release-Build die Fragment-Version für registerForActivityResult (mindestens 1.3.0)
+ implementation("androidx.fragment:fragment-ktx:1.8.9")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
@@ -124,8 +126,8 @@ val verifyBranding by tasks.registering {
.file("src/main/res/values/strings.xml")
.asFile
.readText()
- check("deskhub" in strings) {
- "app_name muss exakt deskhub lauten."
+ check("DeskHub" in strings) {
+ "app_name muss exakt DeskHub lauten (Groß- und Kleinschreibung beachten)."
}
val readme = layout.projectDirectory.file("README.md").asFile.readText()
check("Ersteller: M_Viper" in readme) {
diff --git a/mobile/src/test/java/de/deskhub/staff/ui/I18nTest.kt b/mobile/src/test/java/de/deskhub/staff/ui/I18nTest.kt
new file mode 100644
index 00000000..1f211b4a
--- /dev/null
+++ b/mobile/src/test/java/de/deskhub/staff/ui/I18nTest.kt
@@ -0,0 +1,57 @@
+package de.deskhub.staff.ui
+
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import java.io.File
+
+/** Prüft die Sprachschicht: Wörterbuch, Muster mit {} und {#}, Reihenfolge, Deutsch bleibt unverändert, das mitgelieferte Wörterbuch. */
+class I18nTest {
+ @Before
+ fun setUp() {
+ I18n.load(
+ """{"Neues Ticket":"New ticket","Willkommen, {}":"Welcome, {}","{#} Treffer":"{} hits","{} · erstellt am {}":"{1} · created {2}","_info":"x","Leer":""}""",
+ )
+ I18n.useLanguage("en")
+ }
+
+ @After
+ fun tearDown() = I18n.useLanguage("de")
+
+ @Test
+ fun exactAndWhitespace() {
+ assertEquals("New ticket", I18n.tr("Neues Ticket"))
+ assertEquals(" New ticket\n", I18n.tr(" Neues Ticket\n"))
+ assertEquals("Unbekannt", I18n.tr("Unbekannt"))
+ assertEquals("", I18n.tr(""))
+ assertEquals("Leer", I18n.tr("Leer"))
+ }
+
+ @Test
+ fun patterns() {
+ assertEquals("Welcome, Erika", I18n.tr("Willkommen, Erika"))
+ assertEquals("12 hits", I18n.tr("12 Treffer"))
+ assertEquals("viele Treffer", I18n.tr("viele Treffer"))
+ assertEquals("Technik · created 24.10.2026", I18n.tr("Technik · erstellt am 24.10.2026"))
+ }
+
+ @Test
+ fun germanStaysGerman() {
+ I18n.useLanguage("de")
+ assertEquals("Neues Ticket", I18n.tr("Neues Ticket"))
+ assertEquals("Willkommen, Erika", tr("Willkommen, Erika"))
+ }
+
+ @Test
+ fun bundledDictionaryCoversTheApp() {
+ val file = listOf("src/main/assets/lang_en.json", "mobile/src/main/assets/lang_en.json").map(::File).first { it.exists() }
+ I18n.load(file.readText(Charsets.UTF_8))
+ assertEquals("Cancel", I18n.tr("Abbrechen"))
+ assertEquals("Sign out", I18n.tr("Abmelden"))
+ assertEquals("Welcome, Lukas", I18n.tr("Willkommen, Lukas"))
+ assertEquals("Resolution due: in 2h 5m", I18n.tr("Lösung fällig: in 2 Std 5 Min"))
+ assertEquals("Page 3 · 120 tickets", I18n.tr("Seite 3 · 120 Tickets"))
+ assertEquals("Customer satisfaction (30 days)", I18n.tr("Kundenzufriedenheit (30 Tage)"))
+ }
+}