From 0866d38065ce7df20bc573381dbac285028802ba Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 01:21:31 +0000 Subject: [PATCH 01/11] fix(build): resolve :nappletHost benchmark variant via matchingFallbacks The new plain-AGP :nappletHost library only declares debug/release build types (unlike the KMP :commons/:quartz libs, which match any build type). The :amethyst app's `benchmark` build type had no fallback, so Gradle could not resolve a matching :nappletHost variant for fdroidBenchmark/playBenchmark, breaking Test/Build CI on main. Add `matchingFallbacks += "release"` to the benchmark build type so it falls back to a library's release variant when no benchmark variant exists. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_014tzXcqVUcAPhyDDFak4SQ8 --- amethyst/build.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/amethyst/build.gradle.kts b/amethyst/build.gradle.kts index c8c3a72587..ceacdce4bf 100644 --- a/amethyst/build.gradle.kts +++ b/amethyst/build.gradle.kts @@ -212,6 +212,9 @@ android { } create("benchmark") { initWith(getByName("release")) + // Plain AGP library deps (e.g. :nappletHost) only declare debug/release build types, + // so fall back to their `release` variant when resolving the benchmark build type. + matchingFallbacks += "release" applicationIdSuffix = ".benchmark" versionNameSuffix = "-BENCHMARK" resValue("string", "app_name", "@string/app_name_benchmark") From 4574005019d9c24c83d351dc8fb93ff981166161 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 01:46:58 +0000 Subject: [PATCH 02/11] feat: fetch author relay list when addressable event is missing When a kind-16 repost (or any event) has an a-tag pointing to an addressable event by an unknown author (no NIP-65 relay list loaded), potentialRelaysToFindAddress() returned an empty set and the event was never found. AddressableAuthorRelayLoaderSubAssembler mirrors UserOutboxFinderSubAssembler but is driven by EventFinderQueryState. It detects AddressableNote stubs whose author relay list is missing and emits kind-0/10002 filters to indexer/search relays. On EOSE it calls invalidateFilters(), causing NoteEventLoaderSubAssembler to re-run with the now-populated outbox relays and fetch the addressable event. --- .../RelaySubscriptionsCoordinator.kt | 2 +- .../event/EventFinderFilterAssembler.kt | 6 + ...ddressableAuthorRelayLoaderSubAssembler.kt | 145 ++++++++++++++++++ 3 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/AddressableAuthorRelayLoaderSubAssembler.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinator.kt index 9a7c1b5342..3c76a2389a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinator.kt @@ -99,7 +99,7 @@ class RelaySubscriptionsCoordinator( // loaders of content that is not yet in the device. // they are active when looking at events, users, channels. val channelFinder = ChannelFinderFilterAssemblyGroup(client) - val eventFinder = EventFinderFilterAssembler(client) + val eventFinder = EventFinderFilterAssembler(client, cache, failureTracker) val userFinder = UserFinderFilterAssembler(client, cache, failureTracker) // active when searching or tagging users. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt index 29a6767490..abafef5d79 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt @@ -23,10 +23,13 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.AddressableAuthorRelayLoaderSubAssembler import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.NoteEventLoaderSubAssembler import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.watchers.EventWatcherSubAssembler import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient +import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayOfflineTracker // This allows multiple screen to be listening to tags, even the same tag @Stable @@ -38,11 +41,14 @@ class EventFinderQueryState( @Stable class EventFinderFilterAssembler( client: INostrClient, + cache: LocalCache, + failureTracker: RelayOfflineTracker, ) : ComposeSubscriptionManager() { val group = listOf( NoteEventLoaderSubAssembler(client, ::allKeys), EventWatcherSubAssembler(client, ::allKeys), + AddressableAuthorRelayLoaderSubAssembler(client, cache, failureTracker, ::allKeys), ) override fun invalidateFilters() = group.forEach { it.invalidateFilters() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/AddressableAuthorRelayLoaderSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/AddressableAuthorRelayLoaderSubAssembler.kt new file mode 100644 index 0000000000..31c33bc0cb --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/AddressableAuthorRelayLoaderSubAssembler.kt @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders + +import com.vitorpamplona.amethyst.commons.defaults.DefaultIndexerRelayList +import com.vitorpamplona.amethyst.commons.defaults.DefaultSearchRelayList +import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.BaseEoseManager +import com.vitorpamplona.amethyst.model.AddressableNote +import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.follows.pickRelaysToLoadUsers +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState +import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent +import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient +import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayOfflineTracker +import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter +import com.vitorpamplona.quartz.nip01Core.relay.client.pool.groupByRelay +import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener +import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent +import com.vitorpamplona.quartz.utils.TimeUtils + +/** + * When an AddressableNote stub (event == null) references an author whose NIP-65 relay list has + * not been loaded yet, [potentialRelaysToFindAddress] returns an empty set and the event is never + * found. This assembler detects those cases and fetches kind 0 + kind 10002 for the missing + * authors from indexer/search relays. Once the relay list arrives, [invalidateFilters] re-triggers + * [NoteEventLoaderSubAssembler], which can now query the author's actual outbox relays. + */ +class AddressableAuthorRelayLoaderSubAssembler( + client: INostrClient, + val cache: LocalCache, + val failureTracker: RelayOfflineTracker, + allKeys: () -> Set, +) : BaseEoseManager(client, allKeys) { + val relayListKinds = listOf(MetadataEvent.KIND, AdvertisedRelayListEvent.KIND) + + var hasTried: EOSEAccountFast = EOSEAccountFast(200) + + val sub = + requestNewSubscription( + object : SubscriptionListener { + override fun onEose( + relay: NormalizedRelayUrl, + forFilters: List?, + ) { + forFilters?.forEach { filter -> + filter.authors?.forEach { pubKey -> + cache.getUserIfExists(pubKey)?.let { user -> + hasTried.newEose(user, relay, TimeUtils.now()) + } + } + } + invalidateFilters() + } + }, + ) + + override fun updateSubscriptions(keys: Set) { + val newFilters = updateFilter(keys.toList())?.ifEmpty { null } + sub.updateFilters(newFilters?.groupByRelay()) + } + + fun updateFilter(keys: List): List? { + val unknownAuthors = mutableSetOf() + keys.forEach { key -> + val note = key.note + if (note is AddressableNote && note.event == null) { + val author = cache.getOrCreateUser(note.address.pubKeyHex) + if (author.authorRelayList() == null) { + unknownAuthors.add(author) + } + } + } + + if (unknownAuthors.isEmpty()) return null + + val accounts = keys.mapTo(mutableSetOf()) { it.account } + val connectedRelays = client.connectedRelaysFlow().value + + val perRelayKeys = + pickRelaysToLoadUsers( + unknownAuthors, + accounts, + connectedRelays, + failureTracker.cannotConnectRelays, + hasTried, + ) + + val activeFilters = + perRelayKeys.mapNotNull { (relay, pubkeys) -> + val sorted = pubkeys.sorted() + if (sorted.isNotEmpty()) { + RelayBasedFilter( + relay = relay, + filter = Filter(kinds = relayListKinds, authors = sorted), + ) + } else { + null + } + } + + val placedPubkeys = perRelayKeys.values.asSequence().flatten().toSet() + val abandonedPubkeys = + unknownAuthors.mapNotNullTo(mutableSetOf()) { user -> + user.pubkeyHex.takeIf { it !in placedPubkeys } + } + + if (abandonedPubkeys.isEmpty()) return activeFilters + + val sortedAbandoned = abandonedPubkeys.sorted() + val fallbackRelays = (DefaultIndexerRelayList + DefaultSearchRelayList) - failureTracker.cannotConnectRelays + val fallbackFilters = + fallbackRelays.map { relay -> + RelayBasedFilter( + relay = relay, + filter = Filter(kinds = relayListKinds, authors = sortedAbandoned), + ) + } + + return activeFilters + fallbackFilters + } +} From 1f4ff914196175b80ae13a2a50672ab389781d57 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 02:50:48 +0000 Subject: [PATCH 03/11] fix(build): give :nappletHost a benchmark variant for :amethyst benchmark builds The new plain-AGP :nappletHost library only declared debug/release build types (unlike the KMP :commons/:quartz/:nestsClient libs, which match any build type). The :amethyst app's `benchmark` build type therefore had no matching :nappletHost variant, breaking fdroidBenchmark/playBenchmark resolution and Test/Build CI on main. Declare a matching `benchmark` build type (initWith release) in :nappletHost so the app resolves a real variant. Library AARs aren't signed, so no signing config is needed here. :nappletHost is the only plain-AGP library :amethyst depends on, so no consumer-side matchingFallbacks is required. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_014tzXcqVUcAPhyDDFak4SQ8 --- amethyst/build.gradle.kts | 3 --- nappletHost/build.gradle.kts | 8 ++++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/amethyst/build.gradle.kts b/amethyst/build.gradle.kts index ceacdce4bf..c8c3a72587 100644 --- a/amethyst/build.gradle.kts +++ b/amethyst/build.gradle.kts @@ -212,9 +212,6 @@ android { } create("benchmark") { initWith(getByName("release")) - // Plain AGP library deps (e.g. :nappletHost) only declare debug/release build types, - // so fall back to their `release` variant when resolving the benchmark build type. - matchingFallbacks += "release" applicationIdSuffix = ".benchmark" versionNameSuffix = "-BENCHMARK" resValue("string", "app_name", "@string/app_name_benchmark") diff --git a/nappletHost/build.gradle.kts b/nappletHost/build.gradle.kts index ddc19cd17c..c362b1195c 100644 --- a/nappletHost/build.gradle.kts +++ b/nappletHost/build.gradle.kts @@ -16,6 +16,14 @@ android { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 } + + buildTypes { + // :amethyst defines a `benchmark` build type (release + profileable) for macrobenchmarks. + // Declare a matching variant here so the app can resolve this library for benchmark builds. + create("benchmark") { + initWith(getByName("release")) + } + } } kotlin { From 82a20c012ad820ca21d1727fe23d3e7f1d9963fe Mon Sep 17 00:00:00 2001 From: vitorpamplona <532031+vitorpamplona@users.noreply.github.com> Date: Tue, 23 Jun 2026 03:10:29 +0000 Subject: [PATCH 04/11] chore: sync Crowdin translations and seed translator npub placeholders --- .../src/main/res/values-ar-rSA/strings.xml | 3 ++ .../src/main/res/values-bn-rBD/strings.xml | 3 ++ .../src/main/res/values-ca-rES/strings.xml | 3 ++ .../src/main/res/values-cs-rCZ/strings.xml | 3 ++ .../src/main/res/values-cy-rGB/strings.xml | 3 ++ .../src/main/res/values-da-rDK/strings.xml | 3 ++ .../src/main/res/values-de-rDE/strings.xml | 3 ++ .../src/main/res/values-el-rGR/strings.xml | 3 ++ .../src/main/res/values-en-rGB/strings.xml | 3 ++ .../src/main/res/values-eo-rUY/strings.xml | 3 ++ .../src/main/res/values-es-rES/strings.xml | 3 ++ .../src/main/res/values-es-rMX/strings.xml | 3 ++ .../src/main/res/values-es-rUS/strings.xml | 3 ++ .../src/main/res/values-fa-rIR/strings.xml | 3 ++ .../src/main/res/values-fi-rFI/strings.xml | 3 ++ .../src/main/res/values-fr-rCA/strings.xml | 3 ++ .../src/main/res/values-fr-rFR/strings.xml | 42 +++++++++++++++++++ .../src/main/res/values-gu-rIN/strings.xml | 3 ++ .../src/main/res/values-hi-rIN/strings.xml | 3 ++ .../src/main/res/values-hr-rHR/strings.xml | 3 ++ .../src/main/res/values-hu-rHU/strings.xml | 3 ++ .../src/main/res/values-in-rID/strings.xml | 3 ++ .../src/main/res/values-it-rIT/strings.xml | 3 ++ .../src/main/res/values-iw-rIL/strings.xml | 3 ++ .../src/main/res/values-ja-rJP/strings.xml | 3 ++ .../src/main/res/values-kk-rKZ/strings.xml | 3 ++ .../src/main/res/values-ko-rKR/strings.xml | 3 ++ .../src/main/res/values-ks-rIN/strings.xml | 3 ++ .../src/main/res/values-lt-rLT/strings.xml | 3 ++ .../src/main/res/values-lv-rLV/strings.xml | 3 ++ .../src/main/res/values-ne-rNP/strings.xml | 3 ++ .../src/main/res/values-nl-rNL/strings.xml | 3 ++ .../src/main/res/values-pcm-rNG/strings.xml | 3 ++ .../src/main/res/values-pl-rPL/strings.xml | 3 ++ .../src/main/res/values-pt-rBR/strings.xml | 3 ++ .../src/main/res/values-pt-rPT/strings.xml | 3 ++ .../src/main/res/values-ru-rRU/strings.xml | 3 ++ .../src/main/res/values-ru-rUA/strings.xml | 3 ++ .../src/main/res/values-sa-rIN/strings.xml | 3 ++ .../src/main/res/values-sl-rSI/strings.xml | 9 ++++ .../src/main/res/values-sr-rSP/strings.xml | 3 ++ .../src/main/res/values-sv-rSE/strings.xml | 3 ++ amethyst/src/main/res/values-sw/strings.xml | 3 ++ .../src/main/res/values-ta-rIN/strings.xml | 3 ++ .../src/main/res/values-th-rTH/strings.xml | 3 ++ .../src/main/res/values-tr-rTR/strings.xml | 3 ++ .../src/main/res/values-uk-rUA/strings.xml | 3 ++ .../src/main/res/values-uz-rUZ/strings.xml | 3 ++ .../src/main/res/values-vi-rVN/strings.xml | 3 ++ .../src/main/res/values-zh-rCN/strings.xml | 4 ++ .../src/main/res/values-zh-rHK/strings.xml | 3 ++ .../src/main/res/values-zh-rSG/strings.xml | 3 ++ .../src/main/res/values-zh-rTW/strings.xml | 3 ++ docs/changelog/translators.json | 13 +++++- 54 files changed, 217 insertions(+), 1 deletion(-) diff --git a/amethyst/src/main/res/values-ar-rSA/strings.xml b/amethyst/src/main/res/values-ar-rSA/strings.xml index 5b291fc736..d264e4d9a9 100644 --- a/amethyst/src/main/res/values-ar-rSA/strings.xml +++ b/amethyst/src/main/res/values-ar-rSA/strings.xml @@ -255,6 +255,9 @@ حظر المفضلة المسودات + + + المفضلة الخاصة المفضلة العامة إضافة إلى المفضلات الخاصة diff --git a/amethyst/src/main/res/values-bn-rBD/strings.xml b/amethyst/src/main/res/values-bn-rBD/strings.xml index 56ff9e92a7..d831e08aac 100644 --- a/amethyst/src/main/res/values-bn-rBD/strings.xml +++ b/amethyst/src/main/res/values-bn-rBD/strings.xml @@ -239,6 +239,9 @@ ব্লক করুন এবং অভিযোগ পেশ করুন ব্লক করুন বুকমার্ক করুন + + + ব্যক্তিগত বুকমার্ক সর্বজনীন বুকমার্ক ব্যক্তিগত বুকমার্কে যুক্ত করুন diff --git a/amethyst/src/main/res/values-ca-rES/strings.xml b/amethyst/src/main/res/values-ca-rES/strings.xml index 6d9f1504ab..2988c0bd94 100644 --- a/amethyst/src/main/res/values-ca-rES/strings.xml +++ b/amethyst/src/main/res/values-ca-rES/strings.xml @@ -2,6 +2,9 @@ + + + + + Zdroj: %1$s v%1$s Stáhnout diff --git a/amethyst/src/main/res/values-cy-rGB/strings.xml b/amethyst/src/main/res/values-cy-rGB/strings.xml index 6d9f1504ab..2988c0bd94 100644 --- a/amethyst/src/main/res/values-cy-rGB/strings.xml +++ b/amethyst/src/main/res/values-cy-rGB/strings.xml @@ -2,6 +2,9 @@ + + + + + + + + Quelle: %1$s v%1$s Herunterladen diff --git a/amethyst/src/main/res/values-el-rGR/strings.xml b/amethyst/src/main/res/values-el-rGR/strings.xml index 21ff85b738..d7264dab56 100644 --- a/amethyst/src/main/res/values-el-rGR/strings.xml +++ b/amethyst/src/main/res/values-el-rGR/strings.xml @@ -199,6 +199,9 @@ Αποκλεισμός και Αναφορά Αποκλεισμός Σελιδοδείκτες + + + Ιδιωτικοί Σελιδοδείκτες Δημόσιοι Σελιδοδείκτες Προσθήκη στους Ιδιωτικούς Σελιδοδείκτες diff --git a/amethyst/src/main/res/values-en-rGB/strings.xml b/amethyst/src/main/res/values-en-rGB/strings.xml index b20c782ccd..5b8cb76998 100644 --- a/amethyst/src/main/res/values-en-rGB/strings.xml +++ b/amethyst/src/main/res/values-en-rGB/strings.xml @@ -9,6 +9,9 @@ 👀 + + + + + Privataj Legosignoj Publikaj Legosignoj Aldoni al Privataj Legosignoj diff --git a/amethyst/src/main/res/values-es-rES/strings.xml b/amethyst/src/main/res/values-es-rES/strings.xml index 7eccf944f9..c4566bffa8 100644 --- a/amethyst/src/main/res/values-es-rES/strings.xml +++ b/amethyst/src/main/res/values-es-rES/strings.xml @@ -569,6 +569,9 @@ Yoga Meditación Dieta + + + Cortos Reactivar micrófono Transmitir en vivo… diff --git a/amethyst/src/main/res/values-es-rMX/strings.xml b/amethyst/src/main/res/values-es-rMX/strings.xml index 3350f1b181..6d2307f6d9 100644 --- a/amethyst/src/main/res/values-es-rMX/strings.xml +++ b/amethyst/src/main/res/values-es-rMX/strings.xml @@ -566,6 +566,9 @@ Yoga Meditación Dieta + + + Cortos Reactivar micrófono Transmitir en vivo… diff --git a/amethyst/src/main/res/values-es-rUS/strings.xml b/amethyst/src/main/res/values-es-rUS/strings.xml index 03c52ebaba..81a5c4017c 100644 --- a/amethyst/src/main/res/values-es-rUS/strings.xml +++ b/amethyst/src/main/res/values-es-rUS/strings.xml @@ -566,6 +566,9 @@ Yoga Meditación Dieta + + + Cortos Reactivar micrófono Transmitir en vivo… diff --git a/amethyst/src/main/res/values-fa-rIR/strings.xml b/amethyst/src/main/res/values-fa-rIR/strings.xml index 0a8279c039..008ad30de2 100644 --- a/amethyst/src/main/res/values-fa-rIR/strings.xml +++ b/amethyst/src/main/res/values-fa-rIR/strings.xml @@ -269,6 +269,9 @@ بلاک علامت گذاشته ها پیش‌نویس‌ + + + علامت گاشته های خصوصی علامت گذاشته های عمومی افزودن به علامت گذاشته های خصوصی diff --git a/amethyst/src/main/res/values-fi-rFI/strings.xml b/amethyst/src/main/res/values-fi-rFI/strings.xml index 6e2f19c28d..2060deb3c8 100644 --- a/amethyst/src/main/res/values-fi-rFI/strings.xml +++ b/amethyst/src/main/res/values-fi-rFI/strings.xml @@ -202,6 +202,9 @@ Estä ja ilmoita Estä Kirjanmerkit + + + Yksityiset kirjanmerkit Julkiset kirjanmerkit Lisää yksityisiin kirjanmerkkeihin diff --git a/amethyst/src/main/res/values-fr-rCA/strings.xml b/amethyst/src/main/res/values-fr-rCA/strings.xml index 6d9f1504ab..2988c0bd94 100644 --- a/amethyst/src/main/res/values-fr-rCA/strings.xml +++ b/amethyst/src/main/res/values-fr-rCA/strings.xml @@ -2,6 +2,9 @@ + + + + Impossible de trouver ce message "Erreur lors du chargement des réponses : " Essayer à nouveau @@ -406,9 +411,13 @@ Brouillons Publications planifiées Planifier + Heure + Planifier la publication + Annuler la planification Dans 1 heure Demain à 9 h Lundi prochain à 9 h + Ouvrir les paramètres Continuer quand même Demain Déconnecté @@ -428,6 +437,7 @@ %d en file d\'attente %d en file d\'attente + ID de note copié Sondages Ouvrir Fermé @@ -446,6 +456,8 @@ Relais Auteur Requêtes + Utilisateurs bannis + Ajouter un utilisateur banni Nom, npub, ou NIP-05 Nouveau badge Badge de récompense @@ -471,6 +483,18 @@ Applications · %1$d Applications Recommandez les applications Nostr que vous utilisez pour que les autres puissent les découvrir. + Applications recommandées + Rechercher des applications par nom + Aucune application ne correspond à votre recherche. + Aucune application recommandée ne correspond à ce filtre. + Application sans nom + N\'annonce pas le contenu qu\'il gère + Recommander + Recommandé + par + Disponible sur + Web + Android iOS Images Entraînements @@ -486,6 +510,7 @@ Rythme cardiaque Rythme cardiaque maximum Poids + Volume Notes Heures Minutes @@ -500,7 +525,12 @@ Méditation Alimentation Jeûne + EMOM + AMRAP Applications + + + Source : %1$s v%1$s Télécharger @@ -513,21 +543,28 @@ par Afficher les anciennes versions Masquer les anciennes versions + Shorts Diffusions en direct Lever la main + Reconnexion… Fermer ce salon ? Fermer le salon Discuter + Couper le micro + Réactiver le micro Hôte Modérateur Microphone désactivé Parler maintenant Microphone ouvert parle actuellement + EN DIRECT + Chat Annuler Retour Récemment terminé Annuler + De quoi s\'agit-il ? Annuler Ajouter Relais @@ -610,6 +647,7 @@ Déplacer en privé Envoyer on-chain à la place %1$s sats + Réessayer Confidentialité des Zaps Contrôle comment votre identité est affichée lorsque vous envoyez un zap. Connecter le portefeuille @@ -1211,6 +1249,7 @@ Supprimer le portefeuille Supprimer ce portefeuille ? Payer la facture + Terminé Renommer Renommer le portefeuille Transactions On-chain @@ -1237,6 +1276,8 @@ ~1 h ~30 min ~10 min + Chargement de l\'estimation des frais… + Envoyer %1$s sats Nouveau Message Nouveaux Shorts : images ou vidéos Nouvelle Note Communautaire @@ -1292,6 +1333,7 @@ Rappels du calendrier Envoyer des rappels Tous les calendriers + Afficher les événements de… Vous n\'avez pas encore créé de calendriers. Ouvrir dans la carte Ouvrir le lien diff --git a/amethyst/src/main/res/values-gu-rIN/strings.xml b/amethyst/src/main/res/values-gu-rIN/strings.xml index 6d9f1504ab..2988c0bd94 100644 --- a/amethyst/src/main/res/values-gu-rIN/strings.xml +++ b/amethyst/src/main/res/values-gu-rIN/strings.xml @@ -2,6 +2,9 @@ + + + + + स्रोत : %1$s संस्करण %1$s अवरोहण diff --git a/amethyst/src/main/res/values-hr-rHR/strings.xml b/amethyst/src/main/res/values-hr-rHR/strings.xml index 6d9f1504ab..2988c0bd94 100644 --- a/amethyst/src/main/res/values-hr-rHR/strings.xml +++ b/amethyst/src/main/res/values-hr-rHR/strings.xml @@ -2,6 +2,9 @@ + + + + + Forrás: %1$s v%1$s Letöltés diff --git a/amethyst/src/main/res/values-in-rID/strings.xml b/amethyst/src/main/res/values-in-rID/strings.xml index d6346cb69d..cb41b4d2ae 100644 --- a/amethyst/src/main/res/values-in-rID/strings.xml +++ b/amethyst/src/main/res/values-in-rID/strings.xml @@ -198,6 +198,9 @@ Blokir dan Laporkan Blokir Markah + + + Markah Privat Markah Publik Tambahkan ke Markah Privat diff --git a/amethyst/src/main/res/values-it-rIT/strings.xml b/amethyst/src/main/res/values-it-rIT/strings.xml index 8ecb2ee766..2eae14a927 100644 --- a/amethyst/src/main/res/values-it-rIT/strings.xml +++ b/amethyst/src/main/res/values-it-rIT/strings.xml @@ -191,6 +191,9 @@ Blocca e Segnala Blocca Segnalibri + + + Segnalibri privati Segnalibri pubblici Aggiungi ai segnalibri privati diff --git a/amethyst/src/main/res/values-iw-rIL/strings.xml b/amethyst/src/main/res/values-iw-rIL/strings.xml index 6d9f1504ab..2988c0bd94 100644 --- a/amethyst/src/main/res/values-iw-rIL/strings.xml +++ b/amethyst/src/main/res/values-iw-rIL/strings.xml @@ -2,6 +2,9 @@ + + + + + 非公開ブックマーク 公開ブックマーク 非公開ブックマークに追加 diff --git a/amethyst/src/main/res/values-kk-rKZ/strings.xml b/amethyst/src/main/res/values-kk-rKZ/strings.xml index 6d9f1504ab..2988c0bd94 100644 --- a/amethyst/src/main/res/values-kk-rKZ/strings.xml +++ b/amethyst/src/main/res/values-kk-rKZ/strings.xml @@ -2,6 +2,9 @@ + + + + + + + + + + + + + + Pievienot attēlu Pievienot video Pievienot dokumentu diff --git a/amethyst/src/main/res/values-ne-rNP/strings.xml b/amethyst/src/main/res/values-ne-rNP/strings.xml index 6d9f1504ab..2988c0bd94 100644 --- a/amethyst/src/main/res/values-ne-rNP/strings.xml +++ b/amethyst/src/main/res/values-ne-rNP/strings.xml @@ -2,6 +2,9 @@ + + + + + Bron: %1$s Downloaden Over diff --git a/amethyst/src/main/res/values-pcm-rNG/strings.xml b/amethyst/src/main/res/values-pcm-rNG/strings.xml index 6d9f1504ab..2988c0bd94 100644 --- a/amethyst/src/main/res/values-pcm-rNG/strings.xml +++ b/amethyst/src/main/res/values-pcm-rNG/strings.xml @@ -2,6 +2,9 @@ + + + + + Źródło: %1$s v%1$s Pobierz diff --git a/amethyst/src/main/res/values-pt-rBR/strings.xml b/amethyst/src/main/res/values-pt-rBR/strings.xml index 042d68a122..7c4b724950 100644 --- a/amethyst/src/main/res/values-pt-rBR/strings.xml +++ b/amethyst/src/main/res/values-pt-rBR/strings.xml @@ -614,6 +614,9 @@ Dieta Jejum Aplicativos + + + Fonte: %1$s v%1$s Baixar diff --git a/amethyst/src/main/res/values-pt-rPT/strings.xml b/amethyst/src/main/res/values-pt-rPT/strings.xml index 6d9f1504ab..2988c0bd94 100644 --- a/amethyst/src/main/res/values-pt-rPT/strings.xml +++ b/amethyst/src/main/res/values-pt-rPT/strings.xml @@ -2,6 +2,9 @@ + + + + + Видео Личные закладки Публичные закладки diff --git a/amethyst/src/main/res/values-ru-rUA/strings.xml b/amethyst/src/main/res/values-ru-rUA/strings.xml index 6d9f1504ab..2988c0bd94 100644 --- a/amethyst/src/main/res/values-ru-rUA/strings.xml +++ b/amethyst/src/main/res/values-ru-rUA/strings.xml @@ -2,6 +2,9 @@ + + + + + + + + Vir: %1$s v%1$s Prenos diff --git a/amethyst/src/main/res/values-sr-rSP/strings.xml b/amethyst/src/main/res/values-sr-rSP/strings.xml index b1a26a27e5..183fdcf2e2 100644 --- a/amethyst/src/main/res/values-sr-rSP/strings.xml +++ b/amethyst/src/main/res/values-sr-rSP/strings.xml @@ -32,6 +32,9 @@ Нема подешавања износа Зап. Дуго притисните да бисте променили + + + + + Källa: %1$s v%1$s Ladda ner diff --git a/amethyst/src/main/res/values-sw/strings.xml b/amethyst/src/main/res/values-sw/strings.xml index e460c8c816..f137ecffea 100644 --- a/amethyst/src/main/res/values-sw/strings.xml +++ b/amethyst/src/main/res/values-sw/strings.xml @@ -227,6 +227,9 @@ Zuia na Ripoti Zuia Vialamisho + + + Vialamisho Binafsi Vialamisho vya Umma Ongeza kwenye Vialamisho Binafsi diff --git a/amethyst/src/main/res/values-ta-rIN/strings.xml b/amethyst/src/main/res/values-ta-rIN/strings.xml index 66391eddf2..01ee1da2c5 100644 --- a/amethyst/src/main/res/values-ta-rIN/strings.xml +++ b/amethyst/src/main/res/values-ta-rIN/strings.xml @@ -188,6 +188,9 @@ தடு மற்றும் புகாரளி தடு புக்மார்க்குகள் + + + தனிப்பட்ட புக்மார்க்குகள் பொது புக்மார்க்குகள் தனிப்பட்ட புக்மார்க்குகளில் சேர்க்கவும் diff --git a/amethyst/src/main/res/values-th-rTH/strings.xml b/amethyst/src/main/res/values-th-rTH/strings.xml index f2d05a022f..6f6dbc1790 100644 --- a/amethyst/src/main/res/values-th-rTH/strings.xml +++ b/amethyst/src/main/res/values-th-rTH/strings.xml @@ -242,6 +242,9 @@ บล๊อก บุ๊คมาร์ค ฉบับร่าง + + + บุ๊คมาร์คส่วนตัว บุ๊คมาร์คสาธรณะ เพิ่มไปยังบุ๊คมาร์คส่วนตัว diff --git a/amethyst/src/main/res/values-tr-rTR/strings.xml b/amethyst/src/main/res/values-tr-rTR/strings.xml index e0114ca5fc..29992154bc 100644 --- a/amethyst/src/main/res/values-tr-rTR/strings.xml +++ b/amethyst/src/main/res/values-tr-rTR/strings.xml @@ -136,6 +136,9 @@ Tüm bilinenleri okundu olarak işaretle Tüm yenileri okundu olarak işaretle Tümünü okundu olarak işaretle + + + + + Особисті закладки Публічні закладки Додати в Особисті закладки diff --git a/amethyst/src/main/res/values-uz-rUZ/strings.xml b/amethyst/src/main/res/values-uz-rUZ/strings.xml index 71e7c05d2a..ed6f4b09ba 100644 --- a/amethyst/src/main/res/values-uz-rUZ/strings.xml +++ b/amethyst/src/main/res/values-uz-rUZ/strings.xml @@ -63,6 +63,9 @@ Profil banneri + + + + + + + + 来源: %1$s v%1$s 下载 diff --git a/amethyst/src/main/res/values-zh-rHK/strings.xml b/amethyst/src/main/res/values-zh-rHK/strings.xml index 12c579e18a..8744036a89 100644 --- a/amethyst/src/main/res/values-zh-rHK/strings.xml +++ b/amethyst/src/main/res/values-zh-rHK/strings.xml @@ -190,6 +190,9 @@ 阻止與舉報 僅阻止 書籤 + + + 私人書籤 公開書籤 添加到私人書籤 diff --git a/amethyst/src/main/res/values-zh-rSG/strings.xml b/amethyst/src/main/res/values-zh-rSG/strings.xml index 6d9f1504ab..2988c0bd94 100644 --- a/amethyst/src/main/res/values-zh-rSG/strings.xml +++ b/amethyst/src/main/res/values-zh-rSG/strings.xml @@ -2,6 +2,9 @@ + + + + + 私人書籤 公開書籤 添加到私人書籤 diff --git a/docs/changelog/translators.json b/docs/changelog/translators.json index 31cf8a58bc..c7039c2b90 100644 --- a/docs/changelog/translators.json +++ b/docs/changelog/translators.json @@ -108,7 +108,18 @@ }, { "user": "vitorpamplona", - "languages": [] + "languages": [ + "Chinese Simplified", + "Czech", + "Dutch", + "German", + "Hindi", + "Hungarian", + "Polish", + "Portuguese, Brazilian", + "Slovenian", + "Swedish" + ] }, { "user": "greenart7c3", From 128eed4a25107eb7b9fc6bfff285c5451d3d5c5c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 13:34:15 +0000 Subject: [PATCH 05/11] refactor: reuse UserFinderFilterAssembler instead of duplicating relay-list logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the self-contained AddressableAuthorRelayLoaderSubAssembler (which duplicated UserOutboxFinderSubAssembler) with a thin bridge that injects UserFinderQueryState entries directly into the existing UserFinderFilterAssembler. When EventFinderFilterAssembler detects an AddressableNote stub whose author relay list is unknown, it subscribes that author to userFinder. UserOutboxFinderSubAssembler already handles the kind-0/10002 fetch and relay resolution — no logic is duplicated. Subscriptions are cleaned up when the note loads or the EventFinder key is removed. --- .../RelaySubscriptionsCoordinator.kt | 2 +- .../event/EventFinderFilterAssembler.kt | 6 +- ...ddressableAuthorRelayLoaderSubAssembler.kt | 139 +++++------------- 3 files changed, 38 insertions(+), 109 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinator.kt index 3c76a2389a..fb84051fe5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinator.kt @@ -99,8 +99,8 @@ class RelaySubscriptionsCoordinator( // loaders of content that is not yet in the device. // they are active when looking at events, users, channels. val channelFinder = ChannelFinderFilterAssemblyGroup(client) - val eventFinder = EventFinderFilterAssembler(client, cache, failureTracker) val userFinder = UserFinderFilterAssembler(client, cache, failureTracker) + val eventFinder = EventFinderFilterAssembler(client, cache, userFinder) // active when searching or tagging users. val search = SearchFilterAssembler(client, scope, cache) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt index abafef5d79..3e00e65548 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt @@ -28,8 +28,8 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.AddressableAuthorRelayLoaderSubAssembler import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.NoteEventLoaderSubAssembler import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.watchers.EventWatcherSubAssembler +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssembler import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient -import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayOfflineTracker // This allows multiple screen to be listening to tags, even the same tag @Stable @@ -42,13 +42,13 @@ class EventFinderQueryState( class EventFinderFilterAssembler( client: INostrClient, cache: LocalCache, - failureTracker: RelayOfflineTracker, + userFinder: UserFinderFilterAssembler, ) : ComposeSubscriptionManager() { val group = listOf( NoteEventLoaderSubAssembler(client, ::allKeys), EventWatcherSubAssembler(client, ::allKeys), - AddressableAuthorRelayLoaderSubAssembler(client, cache, failureTracker, ::allKeys), + AddressableAuthorRelayLoaderSubAssembler(cache, ::allKeys, userFinder), ) override fun invalidateFilters() = group.forEach { it.invalidateFilters() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/AddressableAuthorRelayLoaderSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/AddressableAuthorRelayLoaderSubAssembler.kt index 31c33bc0cb..5f81cb8d22 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/AddressableAuthorRelayLoaderSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/AddressableAuthorRelayLoaderSubAssembler.kt @@ -12,134 +12,63 @@ * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders -import com.vitorpamplona.amethyst.commons.defaults.DefaultIndexerRelayList -import com.vitorpamplona.amethyst.commons.defaults.DefaultSearchRelayList -import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.BaseEoseManager +import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.IEoseManager import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.LocalCache -import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.follows.pickRelaysToLoadUsers import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState -import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast -import com.vitorpamplona.quartz.nip01Core.core.HexKey -import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent -import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient -import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayOfflineTracker -import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter -import com.vitorpamplona.quartz.nip01Core.relay.client.pool.groupByRelay -import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener -import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter -import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl -import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent -import com.vitorpamplona.quartz.utils.TimeUtils +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssembler +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState /** - * When an AddressableNote stub (event == null) references an author whose NIP-65 relay list has - * not been loaded yet, [potentialRelaysToFindAddress] returns an empty set and the event is never - * found. This assembler detects those cases and fetches kind 0 + kind 10002 for the missing - * authors from indexer/search relays. Once the relay list arrives, [invalidateFilters] re-triggers - * [NoteEventLoaderSubAssembler], which can now query the author's actual outbox relays. + * Bridges missing-addressable-note authors into [UserFinderFilterAssembler]. + * + * When an [AddressableNote] stub (event == null) references an author whose NIP-65 relay list + * has not been loaded yet, [potentialRelaysToFindAddress] returns an empty set and the event + * is never fetched. This manager detects those cases and injects a [UserFinderQueryState] for + * the author into the existing [UserFinderFilterAssembler], which already knows how to fetch + * kind-0 / kind-10002 and resolve outbox relays via [UserOutboxFinderSubAssembler]. Once the + * relay list arrives, [EventFinderFilterAssembler] is invalidated and can query the correct relay. */ class AddressableAuthorRelayLoaderSubAssembler( - client: INostrClient, val cache: LocalCache, - val failureTracker: RelayOfflineTracker, - allKeys: () -> Set, -) : BaseEoseManager(client, allKeys) { - val relayListKinds = listOf(MetadataEvent.KIND, AdvertisedRelayListEvent.KIND) + val allKeys: () -> Set, + val userFinder: UserFinderFilterAssembler, +) : IEoseManager { + private val activeSubscriptions = mutableSetOf() - var hasTried: EOSEAccountFast = EOSEAccountFast(200) + override fun invalidateFilters(ignoreIfDoing: Boolean) { + val needed = mutableSetOf() - val sub = - requestNewSubscription( - object : SubscriptionListener { - override fun onEose( - relay: NormalizedRelayUrl, - forFilters: List?, - ) { - forFilters?.forEach { filter -> - filter.authors?.forEach { pubKey -> - cache.getUserIfExists(pubKey)?.let { user -> - hasTried.newEose(user, relay, TimeUtils.now()) - } - } - } - invalidateFilters() - } - }, - ) - - override fun updateSubscriptions(keys: Set) { - val newFilters = updateFilter(keys.toList())?.ifEmpty { null } - sub.updateFilters(newFilters?.groupByRelay()) - } - - fun updateFilter(keys: List): List? { - val unknownAuthors = mutableSetOf() - keys.forEach { key -> + allKeys().forEach { key -> val note = key.note if (note is AddressableNote && note.event == null) { val author = cache.getOrCreateUser(note.address.pubKeyHex) if (author.authorRelayList() == null) { - unknownAuthors.add(author) + needed.add(UserFinderQueryState(author, key.account)) } } } - if (unknownAuthors.isEmpty()) return null + val toAdd = needed - activeSubscriptions + val toRemove = activeSubscriptions - needed - val accounts = keys.mapTo(mutableSetOf()) { it.account } - val connectedRelays = client.connectedRelaysFlow().value + userFinder.subscribe(toAdd.toList()) + userFinder.unsubscribe(toRemove.toList()) - val perRelayKeys = - pickRelaysToLoadUsers( - unknownAuthors, - accounts, - connectedRelays, - failureTracker.cannotConnectRelays, - hasTried, - ) + activeSubscriptions.clear() + activeSubscriptions.addAll(needed) + } - val activeFilters = - perRelayKeys.mapNotNull { (relay, pubkeys) -> - val sorted = pubkeys.sorted() - if (sorted.isNotEmpty()) { - RelayBasedFilter( - relay = relay, - filter = Filter(kinds = relayListKinds, authors = sorted), - ) - } else { - null - } - } - - val placedPubkeys = perRelayKeys.values.asSequence().flatten().toSet() - val abandonedPubkeys = - unknownAuthors.mapNotNullTo(mutableSetOf()) { user -> - user.pubkeyHex.takeIf { it !in placedPubkeys } - } - - if (abandonedPubkeys.isEmpty()) return activeFilters - - val sortedAbandoned = abandonedPubkeys.sorted() - val fallbackRelays = (DefaultIndexerRelayList + DefaultSearchRelayList) - failureTracker.cannotConnectRelays - val fallbackFilters = - fallbackRelays.map { relay -> - RelayBasedFilter( - relay = relay, - filter = Filter(kinds = relayListKinds, authors = sortedAbandoned), - ) - } - - return activeFilters + fallbackFilters + override fun destroy() { + userFinder.unsubscribe(activeSubscriptions.toList()) + activeSubscriptions.clear() } } From 1365be12a53aeb956fd20c55701aa1198c1c34d5 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 23 Jun 2026 11:05:04 -0400 Subject: [PATCH 06/11] Fixes tests --- .../napplet/gateways/AccountNappletGateways.kt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/gateways/AccountNappletGateways.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/gateways/AccountNappletGateways.kt index 04e0c50991..c99dbcc37d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/gateways/AccountNappletGateways.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/gateways/AccountNappletGateways.kt @@ -57,6 +57,7 @@ import com.vitorpamplona.quartz.utils.sha256.sha256 import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.withTimeout import java.io.ByteArrayInputStream +import kotlin.time.Duration.Companion.seconds /** * The account adapter: turns a signed-in [Account] into a configured [NappletBroker] by wiring the @@ -137,6 +138,8 @@ class AccountNappletGateways( body: String, ) { val manager = NotificationManagerCompat.from(context) + if (!manager.areNotificationsEnabled()) return + val channel = NotificationChannelCompat .Builder(NOTIFY_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT) @@ -151,7 +154,11 @@ class AccountNappletGateways( .setContentText(body) .setAutoCancel(true) .build() - manager.notify(id.hashCode(), notification) + try { + manager.notify(id.hashCode(), notification) + } catch (_: SecurityException) { + // Best effort: if the user revoked permission between the check and here, just no-op. + } } /** @@ -213,7 +220,7 @@ class AccountNappletGateways( emptyList() } else { runCatching { - account.client.fetchAll(filters = relays.associateWith { filters }, timeoutMs = QUERY_TIMEOUT_MS) + account.client.fetchAll(filters = relays.associateWith { filters }, timeoutMs = QUERY_TIMEOUT.inWholeMilliseconds) }.getOrDefault(emptyList()) } val fromCache = filters.flatMap { filter -> account.cache.filter(filter).mapNotNull { it.event } } @@ -245,12 +252,12 @@ class AccountNappletGateways( else -> result.completeExceptionally(RuntimeException("Unexpected wallet response.")) } } - return withTimeout(WALLET_TIMEOUT_MS) { result.await() } + return withTimeout(WALLET_TIMEOUT) { result.await() } } companion object { - private const val QUERY_TIMEOUT_MS = 8_000L - private const val WALLET_TIMEOUT_MS = 60_000L + private val QUERY_TIMEOUT = 8.seconds + private val WALLET_TIMEOUT = 60.seconds /** Amethyst's brand purple (`AmethystPurple`, commons Colors.kt), exposed as the theme primary. */ private const val AMETHYST_PURPLE = "#9A82DB" From 1eca812aec571aeece29559980245543396e3a31 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 23 Jun 2026 11:05:04 -0400 Subject: [PATCH 07/11] Fixes tests --- .../NotificationFeedFilterModeOverrideTest.kt | 17 ++++++++++------- .../ThreadDualAxisChartAssemblerTest.kt | 17 ++++++++++------- .../napplet/gateways/AccountNappletGateways.kt | 17 ++++++++++++----- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/NotificationFeedFilterModeOverrideTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/NotificationFeedFilterModeOverrideTest.kt index 4392be5a34..c869020eea 100644 --- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/NotificationFeedFilterModeOverrideTest.kt +++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/NotificationFeedFilterModeOverrideTest.kt @@ -67,13 +67,16 @@ class NotificationFeedFilterModeOverrideTest { private val client = NostrClient( - OkHttpWebSocket.Builder { - OkHttpClient - .Builder() - .followRedirects(true) - .followSslRedirects(true) - .build() - }, + OkHttpWebSocket.Builder( + httpClient = { + OkHttpClient + .Builder() + .followRedirects(true) + .followSslRedirects(true) + .build() + }, + canDial = { true }, + ), scope, ) diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ThreadDualAxisChartAssemblerTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ThreadDualAxisChartAssemblerTest.kt index 2bf278b03b..2cc4fcd1e9 100644 --- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ThreadDualAxisChartAssemblerTest.kt +++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ThreadDualAxisChartAssemblerTest.kt @@ -58,13 +58,16 @@ class ThreadDualAxisChartAssemblerTest { val client = NostrClient( - OkHttpWebSocket.Builder { - OkHttpClient - .Builder() - .followRedirects(true) - .followSslRedirects(true) - .build() - }, + OkHttpWebSocket.Builder( + httpClient = { + OkHttpClient + .Builder() + .followRedirects(true) + .followSslRedirects(true) + .build() + }, + canDial = { true }, + ), scope, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/gateways/AccountNappletGateways.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/gateways/AccountNappletGateways.kt index 04e0c50991..c99dbcc37d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/gateways/AccountNappletGateways.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/gateways/AccountNappletGateways.kt @@ -57,6 +57,7 @@ import com.vitorpamplona.quartz.utils.sha256.sha256 import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.withTimeout import java.io.ByteArrayInputStream +import kotlin.time.Duration.Companion.seconds /** * The account adapter: turns a signed-in [Account] into a configured [NappletBroker] by wiring the @@ -137,6 +138,8 @@ class AccountNappletGateways( body: String, ) { val manager = NotificationManagerCompat.from(context) + if (!manager.areNotificationsEnabled()) return + val channel = NotificationChannelCompat .Builder(NOTIFY_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT) @@ -151,7 +154,11 @@ class AccountNappletGateways( .setContentText(body) .setAutoCancel(true) .build() - manager.notify(id.hashCode(), notification) + try { + manager.notify(id.hashCode(), notification) + } catch (_: SecurityException) { + // Best effort: if the user revoked permission between the check and here, just no-op. + } } /** @@ -213,7 +220,7 @@ class AccountNappletGateways( emptyList() } else { runCatching { - account.client.fetchAll(filters = relays.associateWith { filters }, timeoutMs = QUERY_TIMEOUT_MS) + account.client.fetchAll(filters = relays.associateWith { filters }, timeoutMs = QUERY_TIMEOUT.inWholeMilliseconds) }.getOrDefault(emptyList()) } val fromCache = filters.flatMap { filter -> account.cache.filter(filter).mapNotNull { it.event } } @@ -245,12 +252,12 @@ class AccountNappletGateways( else -> result.completeExceptionally(RuntimeException("Unexpected wallet response.")) } } - return withTimeout(WALLET_TIMEOUT_MS) { result.await() } + return withTimeout(WALLET_TIMEOUT) { result.await() } } companion object { - private const val QUERY_TIMEOUT_MS = 8_000L - private const val WALLET_TIMEOUT_MS = 60_000L + private val QUERY_TIMEOUT = 8.seconds + private val WALLET_TIMEOUT = 60.seconds /** Amethyst's brand purple (`AmethystPurple`, commons Colors.kt), exposed as the theme primary. */ private const val AMETHYST_PURPLE = "#9A82DB" From b7cbc03b485a52303949f249332ae7acd58f53bd Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 23 Jun 2026 11:55:40 -0400 Subject: [PATCH 08/11] Fixes the compilation error --- .../amethyst/napplet/NappletBrokerService.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/NappletBrokerService.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/NappletBrokerService.kt index 54c6be7520..1d1c286453 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/NappletBrokerService.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/NappletBrokerService.kt @@ -22,14 +22,12 @@ package com.vitorpamplona.amethyst.napplet import android.app.Service import android.content.Intent -import android.os.Binder import android.os.Bundle import android.os.Handler import android.os.IBinder import android.os.Looper import android.os.Message import android.os.Messenger -import android.os.Process import android.os.RemoteException import android.util.Log import com.vitorpamplona.amethyst.Amethyst @@ -91,11 +89,10 @@ class NappletBrokerService : Service() { .map { (it as? AccountState.LoggedIn)?.account?.signer?.pubKey ?: "" } } - override fun onBind(intent: Intent?): IBinder? { - // Defense in depth on top of exported=false: only our own UID may bind. - if (Binder.getCallingUid() != Process.myUid()) return null - return incoming.binder - } + // Binding is restricted to our own UID by exported=false in the manifest, enforced by the OS. + // A UID check here would be useless: onBind() runs once (the binder is cached and reused for all + // later clients) and outside any binder transaction, so getCallingUid() returns our own UID anyway. + override fun onBind(intent: Intent?): IBinder? = incoming.binder override fun onDestroy() { liveSubscriptions.closeAll() From 832951b90c37f49c03d3c50147eb8bfef4e9aad3 Mon Sep 17 00:00:00 2001 From: vitorpamplona <532031+vitorpamplona@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:58:03 +0000 Subject: [PATCH 09/11] chore: sync Crowdin translations and seed translator npub placeholders --- .../src/main/res/values-es-rES/strings.xml | 35 ++- .../src/main/res/values-hi-rIN/strings.xml | 266 +++++++++++++++++- .../src/main/res/values-hu-rHU/strings.xml | 72 ++++- docs/changelog/translators.json | 24 +- 4 files changed, 374 insertions(+), 23 deletions(-) diff --git a/amethyst/src/main/res/values-es-rES/strings.xml b/amethyst/src/main/res/values-es-rES/strings.xml index c4566bffa8..de822e6043 100644 --- a/amethyst/src/main/res/values-es-rES/strings.xml +++ b/amethyst/src/main/res/values-es-rES/strings.xml @@ -16,6 +16,10 @@ %1$d activo incluido %1$d activos incluidos + + +%1$d\nrespuesta + +%1$d\nrespuestas + El evento se está cargando o no se puede encontrar en la lista de relés 👀 Imagen del canal @@ -34,7 +38,7 @@ Violencia Autor desconocido Copiar texto - Copiar PubKey del usuario + Copiar pubkey del usuario Copiar ID de la nota Copiar JSON sin procesar Transmisión @@ -218,6 +222,7 @@ Cargar archivo Hacer una foto Grabar un video + No se ha encontrado ninguna aplicación de cámara en este dispositivo Grabar un mensaje Haz clic y mantén pulsado para grabar un mensaje Volver a grabar @@ -265,6 +270,7 @@ Desbloquear Copiar ID de usuario Desbloquear usuario + Este usuario está bloqueado/silenciado. Sus publicaciones están ocultas. "npub, hex, username " Limpiar Logo de la app @@ -387,6 +393,10 @@ Seleccionar Compartir enlace del navegador Compartir + Compartir como imagen + Compartir como Url de Imagen + Generando vista previa… + Compartido a través de Amethyst ID del autor ID de la nota Copiar texto @@ -512,12 +522,31 @@ Aprobaciones Reglas verificables (opcional) Publica un documento de reglas legibles automáticamente (NIP-9B) junto con la comunidad. Los clientes que lo admiten bloquean las publicaciones que no coinciden antes de enviarlas. + Kinds de eventos permitidos + Tocar para permitir un tipo. Mantener pulsado un tipo para establecer el tamaño por evento o los límites de publicación diarios. + Texto corto (1) + Artículo de formato extenso (30023) + Publicación de imagen (20) + Publicación de video (21) + Vídeo corto (22) + Comentario de la comunidad (1111) + Tipo personalizado + Añadir tipo + Límites para el tipo %1$d + Tamaño máximo de evento (bytes) + Número máximo de publicaciones por autor por día + Dejar en blanco sin límite Guardar límites Usuarios bloqueados - Estas claves públicas no pueden publicar en la comunidad. + Estas pubkeys no pueden publicar en la comunidad. Agregar un usuario bloqueado Nombre, npub, o NIP-05 Portal de red de confianza (opcional) + Solo permite el acceso a autores accesibles desde una pubkey raíz en un máximo de N saltos. Varias puertas se consideran válidas si alguna de ellas lo es. + Npub raíz o pubkey hexadecimal + Profundidad + Añadir compuerta WoT + Tamaño máximo de evento global (opcional) Insignias · %1$d Elige cuál de las insignias que has recibido aparecen en tu perfil. Aún no has recibido ninguna insignia. @@ -897,7 +926,7 @@ NIP compatibles Requisitos %1$d ms - Kinds aceptados + Tipos aceptados Suscripciones activas Suscripciones REQ (%1$d) Suscripciones COUNT (%1$d) diff --git a/amethyst/src/main/res/values-hi-rIN/strings.xml b/amethyst/src/main/res/values-hi-rIN/strings.xml index db200b3a0f..c78cb962e9 100644 --- a/amethyst/src/main/res/values-hi-rIN/strings.xml +++ b/amethyst/src/main/res/values-hi-rIN/strings.xml @@ -16,6 +16,10 @@ %1$d संसाधन समाविष्ट %1$d संसाधन समाविष्ट + + सं॰%1$d\n उत्तर + सं॰%1$d\n उत्तर + घटना उपलब्ध की जा रही है अथवा आपके पुनःप्रसारक सूची में से प्राप्त नहीं देखें प्रणाली चित्र @@ -218,6 +222,7 @@ अभिलेख आरोहण एक चित्र लें चलचित्र का अभिलेखन करें + कोई चित्रग्राहक क्रमक उपलब्ध नहीं इस यन्त्र में ऐक संदेश का अभिलेखन करें टाँकें तथा दबाए रखें संदेश का अभिलेखन करने के लिए पुनरभिलेखन @@ -265,6 +270,7 @@ बाधा हटाएँ उपयोगकर्ता विभेदक की अनुकृति करें उपयोगकर्ता बाधा हटाएँ + प्रयोक्ता बाधित अथवा मौनकृत। उनके पत्र छिपाए गए। "एनपुब॰, उपयोगकर्ता नाम, लेख" रिक्त करें क्रमक चिह्न @@ -387,6 +393,10 @@ चयन करें जालसंचारक योजक बाँटें बाँटें + चित्र के रूप में बाँटें + चित्र योजक के रूप में बाँटें + पूर्वीक्षण चित्र उत्पादन चालू… + अमेथिस्ट द्वारा बाँटा गया लेखक विभेदक टीका विभेदक लेख की अनुकृति करें @@ -565,12 +575,15 @@ अनुशम्सित क्रमक चयन करें नोस्टर क्रमकों को जिनकी आप सार्वजनिक रूप से अनुशम्सा करते हैं। अनुशम्साएँ आपके परिचयपृष्ठ पर दिखेंगे तथा अन्यों की सहायता करते हैं क्रमकों की खोज करने के लिए उन विषयवस्तुओं के लिए जो यह ग्राहक खोल नहीं सकता। कोई क्रमक प्राप्त नहीं अब तक। क्रमक यहाँ दिखेंगे जब वे प्राप्त होंगे आपके पुनःप्रसारकों पर। + क्रमक खोजें नाम से + आपके खोज अनुकूल कोई क्रमक प्राप्त नहीं। + कोई अनुशम्सित क्रमक नहीं इस छलनी के अनुकूल। नाम रहित क्रमक घोषणा नहीं करता की किस विषयवस्तु का आलम्बन करता है अनुशम्सा अनुशम्सित सम्भालता है - द्वारा + इनके द्वारा पर उपलब्ध जाल आण्ड्रोइड @@ -583,17 +596,27 @@ अवधि दूरी गति + गति ऊँचाई + ऊँचाई घटाव उष्ममान पद हृदय गति + उच्चतम हृदय गति चरण बार भार + व्यायाम + मात्रा टीकाएँ घण्टे मिनुट सेकण्ड + आपके व्यायाम बाँटें + अमेथिस्ट को समाप्त व्यायाम पढने की अनुमति दें स्वास्थ्य संयोजन से (साम्संग स्वास्थ्य तथा गूगिल फिट तथा फिटबिट तथा गार्मिन इत्यादि) तथा पत्र सुझाव लिखने दें। + संयोजन + %1$s सहस्रमीटर + स्वास्थ्य संयोजन से धावन पैदल द्विचक्रिकाचालन @@ -605,10 +628,70 @@ ध्यान आहार उपवास + परिक्रम + प्रति मिनट प्रत्येक मिनट पर + जितनी बार हो सके क्रमक + क्रमक + नोस्टर संलग्नक्रमक + नोस्टरस्थान + कोई नोस्टरस्थान प्राप्त नहीं अब तक। + नोस्टर संलग्नक्रमक अनुमतियाँ + अनुमतियाँ प्रबन्धन + कोई नोस्टर संलग्नक्रमक अनुमतियाँ नहीं अब तक + नोस्टर संलग्नक्रमकों को आपके द्वारा दिए गए अनुमतियाँ यहाँ दिखेंगे। + इस नोस्टर संलग्नक्रमक को भूल जाएँ + बाधित + निराकरण + शीर्षकरहित नोस्टर संलग्नक्रमक + कोई नोस्टर संलग्नक्रमक प्राप्त नहीं अब तक। + नोस्टर संलग्नक्रमक %1$s… + छाल + परिचय + अक्षरफलक कार्य + पुनःप्रसारक + भण्डार + भुगतान + जाल + आरोहण + पूछें क्या क्षमताएँ उपलब्ध हैं + आपकी ख्याप्यकुंचिका पढें + अक्षरफलक द्रुतपथ बन्धन + पढें हस्ताक्षर करें तथा प्रकाशित करें आपके घटनाओं को + उसका अपना निजी भण्डार + लैटनिंग चालान का भुगतान + जाल तथा ब्लोस्सम॰ संसाधन ले आएँ + आपके प्रसारसंगणक तक अभिलेखों का आरोहण + प्रदर्शनशैली + सूचनाएँ + सन्देश प्रेषण + आपके क्रमक के वर्णों के अनुकूल + सूचनाएँ दिखाएँ + अन्य नोस्टर संलग्नक्रमकों के साथ सन्देशों का विनिमय + क्षमता : %1$s + सर्वदा अनुमत + एक बार अनुमत + कभी अनुमत नहीं + अभी नहीं + यह नोस्टर संलग्नक्रमक आपकी ख्याप्यकुंचिका पढना चाहता है। + यह नोस्टर संलग्नक्रमक आपका परिचय तथा लेखा जानकारी पढना चाहता है। + यह नोस्टर संलग्नक्रमक आपके रूप में एक %1$d प्रकार घटना का हस्ताक्षरण तथा प्रकाशन करना चाहता है। + यह नोस्टर संलग्नक्रमक आपके रूप में एक %1$d प्रकार घटना का हस्ताक्षरण तथा प्रकाशन करना चाहता है : + यह नोस्टर संलग्नक्रमक आपके रूप में एक रहस्यीकृत घटना भेजना चाहता है। + यह नोस्टर संलग्नक्रमक आपके पुनःप्रसारकों से घटनाओं को पढना चाहता है। + यह नोस्टर संलग्नक्रमक अपना निजी भण्डार का प्रयेग करना चाहता है। + यह नोस्टर संलग्नक्रमक एक लैटनिंग चालान का भुगतान करना चाहता है। + यह नोस्टर संलग्नक्रमक एक जाल संसाधन लाना चाहता है। + यह नोस्टर संलग्नक्रमक एक अभिलेख को आपके प्रसारसंगणक तक आरोहण करना चाहता है। + यह नोस्टर संलग्नक्रमक आपको सूचनाएँ दिखाना चाहता है। + यह स्थान आपकी नोस्टर कुंचिका के साथ एक %1$d प्रकार घटना का हस्ताक्षर करना चाहता है। + + यह नोस्टर संलग्नक्रमक %1$d साट् का एक लैटनिंग चालान का भुगतान करना चाहता है। + यह नोस्टर संलग्नक्रमक %1$d साट्स का एक लैटनिंग चालान का भुगतान करना चाहता है। + स्रोत : %1$s संस्करण %1$s अवरोहण @@ -618,10 +701,10 @@ योजक नवीनतम संस्करण सर्वप्रथम टिप्पणी करें। - द्वारा + इनके द्वारा पुराने संस्करण दिखाएँ पुराने संस्करण छिपाएँ - छोटे + लघु चलचित्र सार्वजनिक चर्चा अनुचरण पोटलियाँ सीधा प्रसारण @@ -810,7 +893,7 @@ सार्वजनिक रूप से इस पत्र के साथ विषयसूचक (निप॰३२ चिप्पी) जोडें। जो लोग आपका अनुचरण करते हैं उस विषयसूचक की सूचनावली में इस पत्र को देखेंगे। विषयसूचक जोडें - के द्वारा जोडा गया + इनके द्वारा जोडा गया टँके गए टीकाएँ आपके टँके गए टीकाएँ परिचय के साथ जोडें @@ -1149,11 +1232,21 @@ योग्यता प्रमाण आपके आगतपेटिका पुनःप्रसारकों के साथ संयोजन सक्रिय रखता है तत्काल सूचनाओं के लिए अमेथिस्ट सूचनाएँ सक्रिय + + संयोजित %1$d आगतपेटिका पुनःप्रसारक के साथ + संयोजित %1$d आगतपेटिका पुनःप्रसारकों के साथ + + + संयोजित %1$d पुनःप्रसारक के साथ + संयोजित %1$d पुनःप्रसारकों के साथ + आगतपेटिका पुनःप्रसारकों के साथ संयोजन किया जा रहा है \u2026 सदैव सक्रिय सूचना सेवा अनवरत संयोजन बनाए रखता है आपके आगतपेटिका पुनःप्रसारकों के साथ तत्काल सूचना वितरण के लिए। एक स्थायी सूचना दिखाता है। विद्युत्कोष का अधिक उपयोग करता है पर निश्चित करता है कि आप कभी भी सन्देश नहीं खोएँगे। सूचनाएँ विभाजित करें अनुचरित के अनुसार दो सूचना पृष्ठसूचक दिखाएँ। अनुचरित (लोग जिनका आप अनुचरण करते हैं) तथा सभी। अपठित संकेतक चमकता है केवल आपके द्वारा अनुचरित लोगों के गतिविधियों के लिए। + सन्देशों को दिखाएँ + सूचनाएँ पृष्ठ पर सीधे तथा समूह सन्देशों को समाविष्ट करें। निष्क्रिय करें यदि सन्देशों को केवल सन्देश पृष्ठ में रखना चाहते हों तो। का अनुचरण सभी विद्युत्कोष अनुकूलन सक्रिय @@ -1199,7 +1292,7 @@ अष्टकों में मात्रा जो इस पुनःप्रसारक से प्राप्त हुआ था छलनियाँ तथा घटनाएँ समेत घटनाएँ रखे गए अध्यक्ष - द्वारा उपयुक्त + इनके द्वारा उपयुक्त सेवा कुंजी संस्करण क्रमक @@ -1385,6 +1478,22 @@ एक एमपीत्री॰ अथवा वेव॰ अथवा फ्लाक॰ का चयन करें। इसका आरोहण किया जाएगा आपके प्रसारसंगणक पर अभिलेखन करने पर। ध्वनि अभिलेख आरोहण के लिए तत्पर बाहरी चित्र तथा ध्वनि अभिलेख का आरोहण तथा प्रकाशन चालू। यह चलता रहेगा आप पटल से विगमन करें तो भी। + संगीतसूची सम्पादन + संगीतसूची सम्पादन + शीर्षक + विवरण (विकल्पात्मक) + टीकाएँ (विकल्पात्मक) + बाहरी चित्र आरोहण करें + एक वर्गाकार चित्र का चयन करें इस संगीतसूची की कलाकृती के रूप में। + आपके सार्वजनिक परिचय से इस संगीतसूची को छिपाएँ। + अन्यों को इस संगीतसूची में अभिलेखों को जोडने की अनुमति दें। + इस संगीतसूची को मिटाएँ + क्या इस संगीतसूची के लिए एक निप॰०९ विलोपन प्रकाशित करें। + बाहरी चित्र का आरोहण तथा प्रकाशन चालू। यह चलता रहेगा आप पटल से विगमन करें तो भी। + कोई अभिलेख नहीं अब तक। अभिलेखों को जोडें गाने के विकल्पसूची में \"संगीतसूची में जोडें\' का उपयोग करके। + अभिलेख को ऊपर करें + अभिलेख को नीचे करें + संगीतसूची से अभिलेख हटाएँ स्थान प्राप्त किया जा रहा है स्थान प्राप्त करने की अनुमति नहीं आपके विषयवस्तु दिखाने से पूर्व संवेदनशील विषयवस्तु चेतावनी जोडता है। यह आदर्श है किसी कार्यालय अनुचित विषयवस्तु के लिए अथवा जो कुछ लोगों के लिए आपत्तिजनक अथवा व्याकुल करनेवाला लग सकता है @@ -1636,6 +1745,7 @@ टकसाल टकसाल जालपता टकसाल हटाएँ + टकसाल जोडें इतिहास नटज्साप कुंचिका (उन्नत) एक पृथक निजी कुंचिका केवल निप॰६१ नटज्साप प्राप्त करने के लिए। आपकी नोस्टर लेखा बद्ध कुंचिका नहीं। @@ -1644,7 +1754,7 @@ काशयू॰ धनकोष सम्पादन परिवर्तनों का अभिलेखन कोई अनुशंसाएँ नहीं अब तक - द्वारा अनुशंसित + इनके द्वारा अनुशंसित %1$d अधिक %1$d अधिक @@ -1673,6 +1783,7 @@ धनकोष विवरण सम्पादन टकसाल तथा नटज्साप कुंचिका मेरे टकसाल अनुशंसाएँ + सार्वजनिक रूप से समर्थन करें उन टकसालों का जिन पर आप विश्वास करते हैं तथा अनुशम्साएँ हटाएँ। आपने किसी भी टकसाल की अनुशंसा नहीं की अब तक। अंगूठाऊपरचिह्न दबाएँ किसी टकसाल के आगे उसका समर्थन सार्वजनिक रूप से करने के लिए। अनुशंसा मिटाएँ क्या अनुशंसा का प्रत्याहरण करें। @@ -1683,6 +1794,25 @@ टकसालों की जाँच चालू… कोई पुनःप्राप्य प्रमाण प्राप्त नहीं। %1$s साट्स पुनःप्राप्त %2$s प्रमाणों में से। + नटज्साप॰ प्राप्त करना रोक दें + आपका प्रकार १००१९ हटा दें जिससे कि अन्य लोग आपको नटज्साप॰ नहीं भेज पाएँगे। आपका धनकोष तथा शेष वैसे ही रहेंगे। + क्या नटज्साप॰ प्राप्त करना रोक दें। + हम आपके नटज्साप॰ जानकारी घटना का प्रतिस्थापन एक रिक्त घटना के साथ करेंगे तथा उसको मिटाने का अनुरोध करेंगे। लोग आपको नटज्साप॰ नहीं कर पाएँगे। आप इसे पुनः सक्रिय कर सकते हैं आपके धनकोष के सम्पादन द्वारा। आपका शेष प्रभावित नहीं। + नटज्साप॰ कुंचिका पुनः उत्पादन + एक नई कुंचिका का उत्पादन करें नटज्साप प्राप्त करने के लिए। विरलतः ही आवश्यक। सामान्यतः केवल यदि आपकी वर्तमान कुंचिका अनावृत हो जाए। नटज्साप जो आपकी पुरानी कुंचिका को भेजे गए तथा अब तक नहीं चुकाए गए खो दिए जाएँगे। + क्या नटज्साप॰ कुंचिका का पुनः उत्पादन करें। + एक नई पीटूपीके॰ कुंचिका का उत्पादन होगा तथा आपके प्रकार १००१९ तथा प्रकार १७३७५ में प्रकाशित होगा आपके वर्तमान टकसालों के साथ। प्रेषक नई कुंचिका के साथ नटज्साप बान्धेंगे। कोई नटज्साप जो आपकी पुरानी कुंचिका को भेजी गई तथा जिसे आपने अब तक चुकाया नहीं उनकी पुनःप्राप्ति नहीं हो सकेगी। यह विरलतः ही आवश्यक। + कुंचिका पुनः उत्पादन + नटज्साप कुंचिका आयात + आपकी नटज्साप कुंचिका का प्रतिस्थापन करें आपके द्वारा चिपकाए गए कुंचिका के साथ। उ॰ धनकोष की पुनःस्थापना के लिए अन्यत्र सुरक्षित रखे गए धनकोष से। विरलतः आवश्यक। नटज्साप जो आपकी पुरानी कुंचिका से बद्ध हैं आगे से चुकाए नहीं जा सकते। + क्या नटज्साप कुंचिका आयात करें। + पीटूपीके॰ निजी कुंचिका (षोडशांक) चिपकाएँ नटज्साप प्राप्त करने के लिए। आपके प्रकार १००१९ तथा प्रकार १७३७५ का पुनः प्रकाशन होगा इस कुंचिका के साथ तथा आपके वर्तमान टकसालों को रखते हुए। नटज्साप जो आपकी वर्तमान कुंचिका से बद्ध हैं आगे से नहीं चुकाए जा सकते अन्यथा कि वह कुंचिका समान हो। यह विरलतः आवश्यक। + पीटूपीके॰ निजी कुंचिका (षोडशांक) + कुंचिका आयात + धनकोष मिटाएँ + धनकोष हटाएँ तथा नटज्साप रोक दें। बचा हुआ शेष सम्भाव्यतः पुनः अप्राप्य। + क्या इस धनकोष को मिटा दें। + यह अनुरोध करता है आपके प्रकार १७३७५ धनकोष तथा प्रकार १००१९ नटज्साप जानकारी का विलोपन। आपके ईकाश॰ प्रमाणों को टकसालों से नहीं मिटाया जाएगा। परन्तु धनकोष कुंचिका मिटने से बचा हुआ शेष तथा नटज्साप जो नहीं चुकाए गए अप्राप्य हो जाएँगे। सुनिश्चित करें की धन का व्यय अथवा विस्थापन पहले कर चुके हैं। इसको पूर्ववत नहीं किया जा सकता। चालान भुगतान उद्धरण प्राप्त करें टकसाल से उद्धरण का अनुरोध… @@ -1701,6 +1831,7 @@ हो गया टकसाल से चालान का अनुरोध… चालान चुकाए जाने की प्रतीक्षा में… + टकसाल का जाँच… प्रमाणों का प्रदान चालू… टकसाल द्वारा भुगतान चालू… प्रमाणों का प्रतिस्थापन… @@ -1990,7 +2121,7 @@ निष्क्रिय वर्णक्रम पट्टियाँ वर्ण तरंगें - सममध्य वलय + किरण वलय प्रभात चमक प्राचीन तरंगरूप स्थायी चित्र @@ -2110,9 +2241,13 @@ विषय सूची व्यक्तिगत पतभेद स्थिर जालस्थान : %1$s + नोस्टर संलग्नक्रमक : %1$s + अनुमतियाँ : + क्रमक तथा स्थान मूल स्थान स्रोत : सेवासंगणक : + खोलें ओ॰टी॰एस : %1$s समयांकन प्रमाण प्रमाण उपलब्ध है कि इस पत्र पर हस्ताक्षर किया गया %1$s के कुछ पहले। प्रमाण अंकित किया गया द्व्यंकरूप्य खण्डश्रृंखला में उस समय उस दिन पर। @@ -2457,9 +2592,9 @@ विश्वसनीय पुनःप्रसारक विश्वसनीय प्रदाता चलचित्र (अनु) - छोटे (अनु) + लघु चलचित्र (अनु) चलचित्र - छोटे + लघु चलचित्र ध्वनि सन्देश ध्वनि उत्तर जगाना @@ -2568,6 +2703,23 @@ दबाएँ पुनः चलाने के लिए तथा स्थिति जाँचने के लिए। पुनः चलाएँ + + धन %1$d टकसाल मे रखे गए जिसका उपयोग आप नहीं करते + धन %1$d टकसालों मे रखे गए जिनका उपयोग आप नहीं करते + + ये रूप्य एक नटज्साप से आए हैं। एक विश्वसनीय टकसाल में विस्थापन करें अथवा लैटनिंग में ले आएँ उन्हें सुरक्षित रखने के लिए। + आपके धनकोष में नहीं + स्थानान्तरण + रूप्य स्थानान्तरण + %2$s साट्स एक टकसाल में जिसका उपयोग आप नहीं करते (%1$s)। विस्थापन करें किसी विश्वसनीय स्थान में। + किसी विश्वसनीय टकसाल में स्थानान्तरण करें + स्थानान्तरण + इस टकसाल से एक अल्पमात्रा लैटनिंग शुल्क लिया जाता है। इसलिए आपको पूर्ण शेष से कुछ न्यून भेजना पड सकता है। + पहले टकसाल जोडें अपने धनकोष में इन रूप्यों को टकसालों के बीच स्थानान्तरण करने के लिए। अथवा नीचे विकल्पों का प्रयोग करें। + लैटनिंग द्वारा निकालें… + काशयू॰ अक्षरराशि के रूप में निर्यात… + रूप्य स्थानान्तरण… + %1$s साट्स स्थानान्तरित %2$s तक। <%1$s संयोजन किया जा रहा है अवरोहण चालू @@ -2669,6 +2821,24 @@ %1$s सं॰ %2$d अधिक + रक्षापालिका + गति चित्रग्राहक + यातायात अचलता + दुर्घटना + मार्ग आवृत + निर्माण + संकट + मार्ग परिस्थिति + गड्ढा + धूमिका + हिम + मार्ग पर मृग + मार्ग घटना + मार्ग घटना + अब भी वहीं + अब वहाँ नहीं + मार्ग सूचना की पुष्टि + सूचना की मार्ग घटना हटाया गया उद्देश्य मात्रा (साट्स) १००००० आपके उद्देश्य का विवरण करें @@ -2773,6 +2943,8 @@ यन्त्र स्थित एआई॰ प्रतिरूप का प्रयोग करता है लेख सुधार तथा स्वर परिवर्तन प्रस्तावों के लिए। पदचिह्न युक्त प्रसारण पदचिह्न युक्त प्रसारक का उपयोग करें घटनाओं को भेजते समय। तत्काल प्रगति दिखाता है तथा प्रत्येक पुनःप्रसारक की स्थिति प्रसारण करते समय। + व्यायामों का सुझाव बाँटनें के लिए + सम्पन्न व्यायाम पढता है स्वास्थ्य संयोजन से (सामसंग स्वास्थ्य तथा गूगिल फिट तथा फिटबिट तथा गार्मिन इत्यादि) तथा पत्र के रूप में बाँटने का सुझाव देता है। अनुमति अनुरोध केवल संयोजन करने पर। सम्पादन स्थापना विकल्प पाण्डुलिपियाँ स्वतः बनाएँ स्वतः पाण्डुलिपि घटना का अभिलेखन करता है जब आप टंकण करते है अथवा सम्पादक छोडते हैं अप्रेषित लेख के साथ तथा आपके निजी निर्गतपेटिका पुनःप्रसारकों को भेजता है। @@ -2843,6 +3015,82 @@ नोव्हेर कला नोव्हेर मंच + मार्मोट॰ समूह + समूह बनाएँ + मार्मोट॰ समूह बनाएँ + एक नया एमएलएस॰ समूह बनेगा। आप सदस्य जोड सकते हैं तत्पश्चात। + मार्मोट॰ समूह + समूह %1$s… + %1$s… + ज्ञात + ज्ञात (%1$d) + नये अनुरोध + नये अनुरोध (%1$d) + कोई समूह नहीं अब तक + समूह बनाएँ अथवा आमन्त्रण स्वीकार करें। + कोई आमन्त्रण नहीं + जब किसी ने समूह में आपको जोडा तो यहाँ दिखेगा आपके द्वारा उत्तर दिए जाने तक। + कोई सन्देश नहीं अब तक + + %1$d सन्देश + %1$d सन्देश + + + %1$d सदस्य + %1$d सदस्य + + समूह नाम + समूह नाम प्रविष्ट करें + समूह विवरण प्रविष्ट करें (विकल्पात्मक) + परिवर्तनों का अभिलेखन समूह में किया जाएगा एमएलएस॰ द्वारा तथा सभी सदस्यों तक प्रसारित किया जाएगा। + समूह विवरण का अद्यतन किया गया + अद्यतन असफल : %1$s + समूह बनाने में असफल : %1$s + कुंचिकापोटली पुनःप्रसारक स्थापित नहीं + आपकी कोई कुंचिकापोटली पुनःप्रसारक सूची नहीं अब तक (मिप॰००)। यह सूची अन्य लोगों को बताता है आपकी कुंचिकापोटली कहाँ प्रकाशित है जिससे कि वे आपको समूह चर्चाओं में आमन्त्रित कर सके।\n\nक्या इसके लिए आपके वर्तमान निर्गतपेटिका पुनःप्रसारकों का उपयोग करें। + निर्गतपेटिका पुनःप्रसारकों का उपयोग करें + अभी के लिए छोडें + समूह जानकारी + समूह जानकारी सम्पादन + समूह छोडें + इस समूह + क्या आप सुनिश्चित रूप से %1$s छोडना चाहते हैं। आप आगे से इस समूह से कोई सन्देश प्राप्त नहीं करेंगे। + समूह छोडनें में असफल : %1$s + जोडा जा रहा है %1$s… + %1$s जोडने में असफल : %2$s + अज्ञात अपक्रम + वे एक कुंचिकापोटली (प्रकार ३०४४३) को प्रकाशित करना चाहिए जुडने के लिए। + समूह में जोडें + सदस्य जोडें + सदस्य जोडें + नाम एनपुब॰ अथवा निप॰०५ + (आप) + - प्रबन्धक + सदस्य हटाया गया + सदस्य हटाने में असफल : %1$s + सदस्य हटाएँ + क्या आप सुनिश्चित रूप से %1$s को हटाना चाहते हैं इस समूह से। + प्रदान + प्रबन्धन अधिकार प्रदान + प्रबन्धन अधिकार प्रदान + क्या %1$s को इस समूह का एक प्रबन्धक बनाया जाए। प्रबन्धक सदस्यों को जोड सकते हैं हटा सकते हैं। तथा समूह जानकारी परिवर्तित कर सकते हैं। तथा अन्य सदस्यों को प्रबन्धक अधिकार प्रदान कर सकते हैं। + प्रबन्धन अधिकार दिए गए + प्रबन्धन अधिकार प्रदान असफल : %1$s + निराकरण + प्रबन्धन अधिकार निराकरण + प्रबन्धन अधिकार निराकरण + क्या %1$s के प्रबन्धक अधिकारों का निराकरण किया जाए। वे समूह के सदस्य बने रहेंगे। + प्रबन्धन अधिकार हटाए गए + प्रबन्धन अधिकार निराकरण असफल : %1$s + पुनःप्रसारक + कोई घटना नहीं अब तक + अन्तिम घटना %1$s + इस समूह का सदस्य नहीं + अनुकृति + स्वतःचयन का पुनःस्थापन + एक्स्प्लोरर॰ एपीआई॰ आधारभूत जालपता + डीवीएम॰ से चालान का भुगतान + डीवीएम॰ को %1$s साट्स भुगतान ग्राहक की लैटनिंग सेवा %1$s पर उपलब्ध नहीं। इसका परिकलन किया गया \"%2$s\" लैटनिंग पता से। अपक्रम : %3$s। जाँच करें यदि सेवा संगणक चल रहा है तथा यदि लैटनिंग पता सम्यक है %1$s का सुलझाव नहीं कर पाए। जाँच करें यदि आप संयोजित हैं, यदि सेवा संगणक चल रहा है तथा यदि लैटनिंग पता %2$s सम्यक है।\n\nअपवर्ग था : %3$s %1$s से लाए गए चालान के जेसोन॰ परखनें में अपक्रम। उपयोगकर्ता की लैटनिंग स्थापना की जाँच करें diff --git a/amethyst/src/main/res/values-hu-rHU/strings.xml b/amethyst/src/main/res/values-hu-rHU/strings.xml index ef4efbb568..1d7ee3f4e3 100644 --- a/amethyst/src/main/res/values-hu-rHU/strings.xml +++ b/amethyst/src/main/res/values-hu-rHU/strings.xml @@ -270,6 +270,7 @@ Letiltás megszüntetése Felhasználói azonosító másolása Felhasználó letiltásának megszüntetése + Ez a felhasználó le van tiltva/némítva. A bejegyzései el vannak rejtve. "npub, felhasználónév, szöveg" Kiürítés Alkalmazáslogó @@ -574,6 +575,9 @@ Ajánlott alkalmazások Válassza ki, mely Nostr alkalmazásokat ajánlja nyilvánosan. Az ajánlások megjelennek a profilon, és segítenek másoknak felfedezni azokat az alkalmazásokat olyan tartalmakhoz, amelyeket ez a kliens nem tud megnyitni. Még nincsenek alkalmazások. Az alkalmazások itt fognak megjelenni, amint az átjátszók felfedezik őket. + Alkalmazás keresése név alapján + Egyetlen alkalmazás sem felel meg a keresésnek. + Egyetlen ajánlott alkalmazás sem felel meg ennek a szűrőnek. Névtelen alkalmazás Nem jelzi, hogy milyen tartalmat kezel Ajánlás @@ -602,6 +606,8 @@ Sorozatok Ismétlések Tömeg + Feladatok + Mennyiség Jegyzetek Óra Perc @@ -622,10 +628,70 @@ Meditáció Étrend Böjt + Kör + EMOM + AMRAP Alkalmazások + Alkalmazások + nKisalkalmazások + nOldalak + Még nem találhatók nOldalak. + nKisalkalmazások jogosultságai + Jogosultságok kezelése + Még nincsenek nKisalkalmazás-jogosultságok + A nKisalkalmazásoknak adott jogosultságok itt fognak megjelenni. + nKisalkalmazás elfelejtése + Letiltva + Visszavonás + Névtelen nKisalkalmazás + Még nem találhatók nKisalkalmazások. + nKisalkalmazás: %1$s… + Parancsértelmező + Azonosság + Billentyűzetműveletek + Átjátszók + Tárhely + Fizetések + Hálózat + Feltöltések + Kérdezze meg, milyen funkciók érhetők el + Az Ön nyilvános kulcsának olvasása + Billentyűkötések hozzárendelése + Események olvasása, aláírása és közzététele + Saját privát tárhely + Lightning számlák kifizetése + Webes és Blossom erőforrások lekérése + Fájlok feltöltése a saját médiakiszolgálóra + Téma + Értesítések + Üzenetváltás + Alkalmazkodás az alkalmazás színeihez + Értesítések megjelenítése + Üzenetváltás más nKisalkalmazással + Funkció: %1$s + Mindig engedélyez + Engedélyezés egyszer + Soha nem engedélyez + Most nem + Ez a nKisalkalmazás olvasni szeretné az Ön nyilvános kulcsát. + Ez a nKisalkalmazás olvasni szeretné az Ön profil- és fiókadatait. + Ez a nKisalkalmazás alá szeretne írni és közzétenni egy %1$d típusú eseményt az Ön nevében. + Ez a nKisalkalmazás alá szeretne írni és közzétenni egy %1$d típusú eseményt az Ön nevében: + Ez a nKisalkalmazás egy titkosított eseményt szeretne küldeni az Ön nevében. + Ez a nKisalkalmazás eseményeket szeretne olvasni az Ön átjátszóiról. + Ez a nKisalkalmazás az Ön privát tárhelyét szeretné használni. + Ez a nKisalkalmazás ki szeretne fizetni egy Lightning számlát. + Ez a nKisalkalmazás le szeretne kérni egy webes erőforrást. + Ez a nKisalkalmazás fel szeretne tölteni egy fájlt az Ön médiakiszolgálójára. + Ez a nKisalkalmazás értesítéseket szeretne megjeleníteni Önnek. + Ez az oldal egy %1$d típusú eseményt szeretne aláírni az Ön Nostr-kulcsával. + + Ez a nKisalkalmazás ki szeretne fizetni egy %1$d satoshi értékű Lightning számlát. + Ez a nKisalkalmazás ki szeretne fizetni egy %1$d satoshi értékű Lightning számlát. + Forrás: %1$s v%1$s Letöltés @@ -2174,10 +2240,14 @@ Karbantartók Témák Személyes ág - Statikus weboldal: %1$s + nOldal: %1$s + nKisalkalmazás: %1$s + Engedélyek: + Alkalmazások és oldalak Főoldal Forrás: Kiszolgálók: + Megnyitás OTS: %1$s Időbélyeg-igazolás Bizonyíték van arra, hogy ezt a bejegyzést valamikor %1$s előtt írták alá. A bizonyítékot ezen a napon és időpontban bélyegezték a Bitcoin blokkláncába. diff --git a/docs/changelog/translators.json b/docs/changelog/translators.json index c7039c2b90..ab1f563a49 100644 --- a/docs/changelog/translators.json +++ b/docs/changelog/translators.json @@ -76,12 +76,24 @@ "tag": "v1.12.6", "since": "2026-06-19T19:01:59-04:00", "translators": [ + { + "user": "rajs19420616", + "languages": [ + "Hindi" + ] + }, { "user": "anthony-robin", "languages": [ "French" ] }, + { + "user": "summoner001", + "languages": [ + "Hungarian" + ] + }, { "user": "maxblake2015", "languages": [ @@ -101,9 +113,9 @@ ] }, { - "user": "summoner001", + "user": "BitByBit21", "languages": [ - "Hungarian" + "Spanish" ] }, { @@ -197,10 +209,6 @@ "user": "crackadoo", "languages": [] }, - { - "user": "BitByBit21", - "languages": [] - }, { "user": "csavastel", "languages": [] @@ -257,10 +265,6 @@ "user": "flobstr", "languages": [] }, - { - "user": "rajs19420616", - "languages": [] - }, { "user": "Maxblake", "languages": [] From dd062861f389a0d252a7adbb0e324e1f9930c9ec Mon Sep 17 00:00:00 2001 From: davotoula <1747287+davotoula@users.noreply.github.com> Date: Tue, 23 Jun 2026 16:14:26 +0000 Subject: [PATCH 10/11] chore: sync Crowdin translations and seed translator npub placeholders --- docs/changelog/translators.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/changelog/translators.json b/docs/changelog/translators.json index ab1f563a49..3867422ae9 100644 --- a/docs/changelog/translators.json +++ b/docs/changelog/translators.json @@ -100,6 +100,12 @@ "Polish" ] }, + { + "user": "BitByBit21", + "languages": [ + "Spanish" + ] + }, { "user": "StellarStoic", "languages": [ @@ -112,12 +118,6 @@ "Chinese Simplified" ] }, - { - "user": "BitByBit21", - "languages": [ - "Spanish" - ] - }, { "user": "vitorpamplona", "languages": [ From a97529d7afdbb8eb04c98700a31d8cf79fc6e630 Mon Sep 17 00:00:00 2001 From: davotoula Date: Tue, 23 Jun 2026 18:13:19 +0200 Subject: [PATCH 11/11] fix: detect markdown when a heading/list follows a blank line isMarkdown() treated the second newline of a blank line as a non-space character, flipping the line-start tracker off. ATX headings, blockquotes, and list markers that follow the standard blank-line spacing went undetected, so NIP-23 long-form articles made of prose plus section headings rendered as raw text. Exclude newline/carriage-return from the line-start guard so a blank line stays at line start. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../amethyst/service/CachedRichTextParser.kt | 9 ++++- .../CachedRichTextParserMarkdownTest.kt | 40 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt index 7a401f57b8..ea4dfc2402 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt @@ -207,7 +207,14 @@ object CachedRichTextParser { // checks that need to know "how much non-space text has // appeared on the current line". if (isNewLine) { - if (c != ' ' && c != '\t') { + // A blank line is still "at line start": skipping '\n'/'\r' + // here keeps isNewLine true so a heading/blockquote/list + // marker on the next line (the standard `\n\n#` spacing) is + // still recognized as line-leading. Without the newline + // exclusion, the second '\n' of a blank line flipped + // isNewLine to false and ATX headings after a blank line + // went undetected. + if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { isNewLine = false nonSpaceCharCountOnLine = 1 lastNonSpaceChar = c diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/CachedRichTextParserMarkdownTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/CachedRichTextParserMarkdownTest.kt index 7f5c78ba4d..366e7e322e 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/CachedRichTextParserMarkdownTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/CachedRichTextParserMarkdownTest.kt @@ -68,6 +68,46 @@ class CachedRichTextParserMarkdownTest { @Test fun atxAfterNewlineIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\n# Heading")) + // ---- Line-leading markers after a BLANK line ------------------------ + // Regression: the standard `\n\n#` spacing (a blank line before a + // heading) was undetected because the blank line's second '\n' flipped + // the line-start tracker off. NIP-23 long-form articles that are pure + // prose plus `## Section` headings rendered as raw text as a result. + + @Test fun atxAfterBlankLineIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\n\n# Heading")) + + @Test fun atxH3AfterBlankLineIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\n\n### Heading")) + + @Test fun atxAfterMultipleBlankLinesIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\n\n\n\n## Heading")) + + @Test fun atxAfterCrlfBlankLineIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\r\n\r\n## Heading")) + + @Test fun blockquoteAfterBlankLineIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\n\n> quoted")) + + @Test fun bulletAfterBlankLineIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\n\n- item")) + + @Test fun orderedListAfterBlankLineIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\n\n1. first")) + + @Test + fun proseArticleWithHeadingAfterBlankLineIsMarkdown() = + assertTrue( + CachedRichTextParser.isMarkdown( + "The cryptographic part of commerce has been solved.\n\n" + + "What follows is a history.\n\n" + + "### The merchant posts of the Hansa\n\n" + + "Long before any king claimed a monopoly on letters.", + ), + ) + + // Blank lines alone must NOT promote ordinary prose to markdown. + @Test + fun plainProseWithBlankLinesIsNotMarkdown() = + assertFalse( + CachedRichTextParser.isMarkdown( + "Just a normal sentence.\n\nAnother paragraph with no markdown at all.", + ), + ) + // ---- Blockquote ----------------------------------------------------- @Test fun blockquoteIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("> a quote"))