From 67ed6379ffe06cd799319753b3a954e1972c810b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 19:34:04 +0000 Subject: [PATCH] fix: re-escape the strings copied back to Android res, port the catalog filter test aapt and compose-resources do not share escaping rules, so a key moved back to res/values needs its apostrophes escaped again or the resource merge fails with "Invalid unicode escape sequence in string". This is the inverse of what tools/strings-migrate/fix_escapes.py does on the way out; a value Android wrapped in quotes to protect whitespace, and a bare @string/ alias, are left alone. SettingsCatalogFilterTest used bare Ints as stand-in resource handles; it now uses real catalog entries and still supplies their text through the injected lookup, which is the point of the filter taking one. :quartz, :commons, :commonsUI and :amethyst unit tests all pass. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AXvKXakvup4inNFfAhhr4L --- .../src/main/res/values-fr-rCA/strings.xml | 4 +- .../src/main/res/values-fr-rFR/strings.xml | 4 +- amethyst/src/main/res/values-fr/strings.xml | 4 +- .../src/main/res/values-it-rIT/strings.xml | 4 +- .../src/main/res/values-tr-rTR/strings.xml | 14 ++--- amethyst/src/main/res/values-tr/strings.xml | 14 ++--- .../src/main/res/values-uk-rUA/strings.xml | 2 +- amethyst/src/main/res/values-uk/strings.xml | 2 +- .../src/main/res/values-uz-rUZ/strings.xml | 24 ++++---- .../settings/SettingsCatalogFilterTest.kt | 57 ++++++++++++------- 10 files changed, 73 insertions(+), 56 deletions(-) diff --git a/amethyst/src/main/res/values-fr-rCA/strings.xml b/amethyst/src/main/res/values-fr-rCA/strings.xml index cace072690..af9eb8fd96 100644 --- a/amethyst/src/main/res/values-fr-rCA/strings.xml +++ b/amethyst/src/main/res/values-fr-rCA/strings.xml @@ -169,7 +169,7 @@ Connexion aux relais de boîte de réception… Notifications Amethyst actives Appels - Notification d'appel en cours + Notification d\'appel en cours Appel avec %1$s Raccrocher Réaction @@ -178,7 +178,7 @@ Réponse vocale Message privé Nest — En direct - Maintient l'audio actif pendant qu'une salle est ouverte. + Maintient l\'audio actif pendant qu\'une salle est ouverte. Nest connecté Arrêter Appuyer pour revenir. diff --git a/amethyst/src/main/res/values-fr-rFR/strings.xml b/amethyst/src/main/res/values-fr-rFR/strings.xml index a335ec1d4f..0d6509ba5c 100644 --- a/amethyst/src/main/res/values-fr-rFR/strings.xml +++ b/amethyst/src/main/res/values-fr-rFR/strings.xml @@ -185,7 +185,7 @@ Connexion aux relais de boîte de réception… Notifications Amethyst actives Appels - Notification d'appel en cours + Notification d\'appel en cours Appel avec %1$s Raccrocher à tout moment maintenant @@ -205,7 +205,7 @@ Signaler Message privé Nest — En direct - Maintient l'audio actif pendant qu'une salle est ouverte. + Maintient l\'audio actif pendant qu\'une salle est ouverte. Nest connecté Arrêter Appuyer pour revenir. diff --git a/amethyst/src/main/res/values-fr/strings.xml b/amethyst/src/main/res/values-fr/strings.xml index 6bc4c1f316..634295f979 100644 --- a/amethyst/src/main/res/values-fr/strings.xml +++ b/amethyst/src/main/res/values-fr/strings.xml @@ -2316,7 +2316,7 @@ Connexion aux relais de boîte de réception… Notifications Amethyst actives Appels - Notification d'appel en cours + Notification d\'appel en cours Appel avec %1$s Raccrocher Réaction @@ -2327,7 +2327,7 @@ Message privé Nest — En direct Nests - Maintient l'audio actif pendant qu'une salle est ouverte. + Maintient l\'audio actif pendant qu\'une salle est ouverte. Nest connecté Arrêter Appuyer pour revenir. diff --git a/amethyst/src/main/res/values-it-rIT/strings.xml b/amethyst/src/main/res/values-it-rIT/strings.xml index 5fdfa841d7..3b5cda62bf 100644 --- a/amethyst/src/main/res/values-it-rIT/strings.xml +++ b/amethyst/src/main/res/values-it-rIT/strings.xml @@ -176,7 +176,7 @@ Post vocale Risposta vocale Messaggio privato - Mantiene l'audio attivo mentre una stanza è aperta. + Mantiene l\'audio attivo mentre una stanza è aperta. Nest connesso Ferma Tocca per tornare. @@ -195,7 +195,7 @@ Tema Notifiche Messaggistica - Mutato. Clicca per riattivare l'audio + Mutato. Clicca per riattivare l\'audio Audio acceso. Clicca per disattivarlo Riproduci Pausa diff --git a/amethyst/src/main/res/values-tr-rTR/strings.xml b/amethyst/src/main/res/values-tr-rTR/strings.xml index 88d56cb8b7..6336ea7bab 100644 --- a/amethyst/src/main/res/values-tr-rTR/strings.xml +++ b/amethyst/src/main/res/values-tr-rTR/strings.xml @@ -158,16 +158,16 @@ because android:label on the manifest entry can only reference @string/. --> - Gerçek zamanlı bildirimler için gelen kutusu relay'lerinize bağlantıları etkin tutar + Gerçek zamanlı bildirimler için gelen kutusu relay\'lerinize bağlantıları etkin tutar - %1$d gelen kutusu relay'ine bağlandı - %1$d gelen kutusu relay'ine bağlandı + %1$d gelen kutusu relay\'ine bağlandı + %1$d gelen kutusu relay\'ine bağlandı - %1$d relay'e bağlandı - %1$d relay'e bağlandı + %1$d relay\'e bağlandı + %1$d relay\'e bağlandı - Gelen kutusu relay'lerine bağlanılıyor… + Gelen kutusu relay\'lerine bağlanılıyor… Amethyst Bildirimleri Etkin Aramalar Devam eden arama bildirimi @@ -191,7 +191,7 @@ Sesi Aç Kimlik Klavye eylemleri - relay'ler + relay\'ler Depolama Ödemeler Ağ diff --git a/amethyst/src/main/res/values-tr/strings.xml b/amethyst/src/main/res/values-tr/strings.xml index 7bba943a26..50bc0f12e2 100644 --- a/amethyst/src/main/res/values-tr/strings.xml +++ b/amethyst/src/main/res/values-tr/strings.xml @@ -2881,16 +2881,16 @@ - Gerçek zamanlı bildirimler için gelen kutusu relay'lerinize bağlantıları etkin tutar + Gerçek zamanlı bildirimler için gelen kutusu relay\'lerinize bağlantıları etkin tutar - %1$d gelen kutusu relay'ine bağlandı - %1$d gelen kutusu relay'ine bağlandı + %1$d gelen kutusu relay\'ine bağlandı + %1$d gelen kutusu relay\'ine bağlandı - %1$d relay'e bağlandı - %1$d relay'e bağlandı + %1$d relay\'e bağlandı + %1$d relay\'e bağlandı - Gelen kutusu relay'lerine bağlanılıyor… + Gelen kutusu relay\'lerine bağlanılıyor… Amethyst Bildirimleri Etkin Aramalar Devam eden arama bildirimi @@ -2915,7 +2915,7 @@ Sesi Aç Kimlik Klavye eylemleri - relay'ler + relay\'ler Depolama Ödemeler Ağ diff --git a/amethyst/src/main/res/values-uk-rUA/strings.xml b/amethyst/src/main/res/values-uk-rUA/strings.xml index 6ad6888cd5..0b186c5a3c 100644 --- a/amethyst/src/main/res/values-uk-rUA/strings.xml +++ b/amethyst/src/main/res/values-uk-rUA/strings.xml @@ -158,7 +158,7 @@ because android:label on the manifest entry can only reference @string/. --> - Підтримує активні з'єднання з вашими inbox relay для сповіщень у реальному часі + Підтримує активні з\'єднання з вашими inbox relay для сповіщень у реальному часі Підключено до %1$d inbox relay Підключено до %1$d inbox relay diff --git a/amethyst/src/main/res/values-uk/strings.xml b/amethyst/src/main/res/values-uk/strings.xml index 267dcd627d..c173b1af5c 100644 --- a/amethyst/src/main/res/values-uk/strings.xml +++ b/amethyst/src/main/res/values-uk/strings.xml @@ -2640,7 +2640,7 @@ - Підтримує активні з'єднання з вашими inbox relay для сповіщень у реальному часі + Підтримує активні з\'єднання з вашими inbox relay для сповіщень у реальному часі Підключено до %1$d inbox relay Підключено до %1$d inbox relay diff --git a/amethyst/src/main/res/values-uz-rUZ/strings.xml b/amethyst/src/main/res/values-uz-rUZ/strings.xml index a7821157bf..9f0bb3da38 100644 --- a/amethyst/src/main/res/values-uz-rUZ/strings.xml +++ b/amethyst/src/main/res/values-uz-rUZ/strings.xml @@ -160,18 +160,18 @@ NavBarItemDef labels are R.string ids. --> Real vaqtli bildirishnomalar uchun kirish relaylaringizga ulanishni faol saqlaydi - %1$d kirish relay'iga ulangan - %1$d kirish relay'lariga ulangan + %1$d kirish relay\'iga ulangan + %1$d kirish relay\'lariga ulangan - %1$d relay'ga ulangan - %1$d relay'larga ulangan + %1$d relay\'ga ulangan + %1$d relay\'larga ulangan Kirish relaylariga ulanilmoqda… Amethyst Bildirishnomalari Faol - Qo'ng'iroqlar - Davom etayotgan qo'ng'iroq bildirishномаsi - %1$s bilan qo'ng'iroq + Qo\'ng\'iroqlar + Davom etayotgan qo\'ng\'iroq bildirishномаsi + %1$s bilan qo\'ng\'iroq Tugatish Reaksiya Targʻib qilish @@ -180,20 +180,20 @@ Shaxsiy Xabar Nest — Jonli Nestlar - Xona ochiq bo'lganda audio ijrosini davom ettiradi. + Xona ochiq bo\'lganda audio ijrosini davom ettiradi. Nest ulandi - To'xtatish + To\'xtatish Qaytish uchun bosing. Chiqish - Ovozni o'chirish + Ovozni o\'chirish Ovozni yoqish - Ovozni o'chirish + Ovozni o\'chirish Ovozni yoqish Shaxsiyat Klaviatura amallari Relaylar Saqlash - To'lovlar + To\'lovlar Tarmoq Yuklamalar Mavzu diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsCatalogFilterTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsCatalogFilterTest.kt index 2e8b61a13a..42b0e69dd7 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsCatalogFilterTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsCatalogFilterTest.kt @@ -20,25 +20,42 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.account_settings +import com.vitorpamplona.amethyst.commons.resources.backup_keys +import com.vitorpamplona.amethyst.commons.resources.danger_zone +import com.vitorpamplona.amethyst.commons.resources.relays +import com.vitorpamplona.amethyst.commons.resources.settings_section_appearance +import com.vitorpamplona.amethyst.commons.resources.theme import org.jetbrains.compose.resources.StringResource import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test class SettingsCatalogFilterTest { + // Real catalog entries stand in for the rows, but their text comes from this map: + // filterSettings takes its lookup as a parameter precisely so the filter itself + // stays Compose-free, and resolving a StringResource for real would need a runtime. + private val accountCategory = Res.string.account_settings + private val dangerCategory = Res.string.danger_zone + private val relayEntry = Res.string.relays + private val uiEntry = Res.string.settings_section_appearance + private val backupEntry = Res.string.backup_keys + private val uiKeywords = Res.string.theme + private val strings = mapOf( - 100 to "Account Settings", - 200 to "Danger Zone", - 1 to "Relay Setup", - 2 to "UI Preferences", - 3 to "Backup Keys", - 20 to "dark mode, theme, font size", + accountCategory to "Account Settings", + dangerCategory to "Danger Zone", + relayEntry to "Relay Setup", + uiEntry to "UI Preferences", + backupEntry to "Backup Keys", + uiKeywords to "dark mode, theme, font size", ) private fun entry( titleRes: StringResource, - keywordsRes: Int? = null, + keywordsRes: StringResource? = null, isDanger: Boolean = false, ) = SettingsEntry( titleRes = titleRes, @@ -51,17 +68,17 @@ class SettingsCatalogFilterTest { private val catalog = listOf( SettingsCategory( - titleRes = 100, + titleRes = accountCategory, entries = listOf( - entry(1), - entry(2, keywordsRes = 20), + entry(relayEntry), + entry(uiEntry, keywordsRes = uiKeywords), ), ), SettingsCategory( - titleRes = 200, + titleRes = dangerCategory, isDanger = true, - entries = listOf(entry(3, isDanger = true)), + entries = listOf(entry(backupEntry, isDanger = true)), ), ) @@ -89,30 +106,30 @@ class SettingsCatalogFilterTest { fun titleMatchIsCaseInsensitive() { val result = run("relay") assertEquals(1, result.size) - assertEquals(100, result[0].titleRes) + assertEquals(accountCategory, result[0].titleRes) assertEquals(1, result[0].entries.size) - assertEquals(1, result[0].entries[0].titleRes) + assertEquals(relayEntry, result[0].entries[0].titleRes) } @Test fun keywordMatchSurfacesEntryWhoseTitleDoesNotMatch() { val result = run("dark mode") assertEquals(1, result.size) - assertEquals(2, result[0].entries[0].titleRes) // UI Preferences, matched via keywords + assertEquals(uiEntry, result[0].entries[0].titleRes) // UI Preferences, matched via keywords } @Test fun categoryTitleMatchSurfacesWholeCategory() { val result = run("account") assertEquals(1, result.size) - assertEquals(100, result[0].titleRes) + assertEquals(accountCategory, result[0].titleRes) assertEquals(2, result[0].entries.size) // both rows shown because the category name matched } @Test fun categoryWithNoMatchesIsDropped() { val result = run("relay") - assertTrue(result.none { it.titleRes == 200 }) + assertTrue(result.none { it.titleRes == dangerCategory }) } @Test @@ -131,8 +148,8 @@ class SettingsCatalogFilterTest { @Test fun prefixOfAWordMatches() { // "rel" is a prefix of "Relay" (title); "the" is a prefix of "theme" (keyword). - assertEquals(1, run("rel")[0].entries[0].titleRes) - assertEquals(2, run("the")[0].entries[0].titleRes) + assertEquals(relayEntry, run("rel")[0].entries[0].titleRes) + assertEquals(uiEntry, run("the")[0].entries[0].titleRes) } @Test @@ -143,7 +160,7 @@ class SettingsCatalogFilterTest { @Test fun everyQueryTermMustPrefixSomeWord() { - assertEquals(2, run("dark size")[0].entries[0].titleRes) // both terms hit UI Preferences + assertEquals(uiEntry, run("dark size")[0].entries[0].titleRes) // both terms hit UI Preferences assertTrue(run("dark zzz").isEmpty()) // second term matches nothing } }