mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-10 16:33:27 +00:00
Merge remote-tracking branch 'upstream/main' into feat/desktop-wallet-zapping
# Conflicts: # desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/FeedScreen.kt # desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/NoteActions.kt
This commit is contained in:
@@ -74,7 +74,14 @@ Before presenting results, **scan the missing English strings** for two red-flag
|
||||
1. **Hardcoded `"1"` next to a noun.** A new English string like `"1 reply"`, `"1 follower"`, or `"1 minute ago"` almost always belongs in a `<plurals>` resource — not a `<string>`. Hardcoding `1` in English forces every translator to either also hardcode `1` (breaking languages where the `one` category covers other numbers, e.g. some Slavic languages) or to silently change the meaning.
|
||||
2. **A `%d` / `%1$d` placeholder in a clearly singular/plural sentence** (e.g. `"%1$d reply"`, `"%d follower"`). Even though the placeholder is parameterised, English-only `one`/`other` agreement won't survive translation into languages that need `few`/`many`.
|
||||
|
||||
Also **audit existing `<plurals>` resources** for the same anti-pattern — any locale's `quantity="one"` item that hardcodes the literal `1` (instead of using a `%d` / `%1$d` placeholder) is broken for languages where the `one` CLDR category covers more than just `n=1` (Russian, Ukrainian, Croatian, etc.). Flag and offer to fix:
|
||||
Also **audit existing `<plurals>` resources** for two anti-patterns:
|
||||
|
||||
1. **`quantity="one"` items that hardcode the literal `1`** (instead of using a `%d` / `%1$d` placeholder) — broken for languages where the `one` CLDR category covers more than just `n=1` (Russian, Ukrainian, Croatian, etc.).
|
||||
2. **`quantity="zero"` items in any locale that doesn't natively use the `zero` CLDR category** — i.e. **everything except Arabic (`ar`) and Welsh (`cy`)**. ICU/CLDR maps `count=0` to `other` for English and all the locales we ship to (cs, de, pt-BR, sv, etc.), so `<item quantity="zero">` is **dead code** there: `getQuantityString(id, 0)` will pick `other`, never the zero entry, and the visible runtime string ends up `"…0 items"` instead of the intended `"…no items"`.
|
||||
|
||||
If a UX genuinely wants special "no items" wording at count=0, that has to be a call-site `if (count == 0)` branch to a separate `<string>`, **not** a `quantity="zero"` plural item.
|
||||
|
||||
Flag and offer to fix:
|
||||
|
||||
```bash
|
||||
# Scan every locale's strings.xml for <item quantity="one"> entries that
|
||||
@@ -96,6 +103,27 @@ for f in amethyst/src/main/res/values/strings.xml amethyst/src/main/res/values-*
|
||||
done
|
||||
```
|
||||
|
||||
Then scan for dead `quantity="zero"` entries. CLDR's `zero` category is integer-bearing only in **Arabic (`ar`)** and **Welsh (`cy`)**. In every other locale, count=0 falls through to `other`, so a `<item quantity="zero">` entry is dead and likely a translator/author bug (or it silently never fires):
|
||||
|
||||
```bash
|
||||
for f in amethyst/src/main/res/values/strings.xml amethyst/src/main/res/values-*/strings.xml; do
|
||||
# Skip Arabic and Welsh — they natively use the zero category.
|
||||
case "$f" in
|
||||
*values-ar*|*values-cy*) continue ;;
|
||||
esac
|
||||
awk -v file="$f" '
|
||||
/<plurals/ { in_plurals = 1; name = $0; sub(/.*name="/, "", name); sub(/".*/, "", name) }
|
||||
in_plurals && /quantity="zero"/ {
|
||||
text = $0; sub(/^[^>]*>/, "", text); sub(/<.*$/, "", text)
|
||||
print file ": <plurals name=\"" name "\"> zero=\"" text "\""
|
||||
}
|
||||
/<\/plurals>/ { in_plurals = 0 }
|
||||
' "$f"
|
||||
done
|
||||
```
|
||||
|
||||
For each hit, warn the user that the entry is unreachable in that locale. The fix is to **remove the `<item quantity="zero">`** and, if the UX wanted distinct wording for count=0, add a separate `<string>` plus an `if (count == 0)` branch at the call site (see "Plurals: handle with care" below).
|
||||
|
||||
Quick scan over the missing keys:
|
||||
|
||||
```bash
|
||||
@@ -147,6 +175,14 @@ When adding or proposing **`<plurals>`** entries, follow these rules:
|
||||
- Arabic (`ar`): `zero`, `one`, `two`, `few`, `many`, `other`
|
||||
- German / Swedish / Brazilian Portuguese: `one`, `other`
|
||||
- When a missing string contains a count placeholder and is conceptually a singular/plural pair, **flag it before translating** — it may belong as a `<plurals>` resource rather than a single `<string>`. Surface this to the user before proposing translations.
|
||||
- **Do not use `quantity="zero"` outside Arabic (`ar`) and Welsh (`cy`).** CLDR's `zero` category is integer-bearing only in those two languages. Android calls `PluralRules.select(0)` for the device locale; in English/German/Czech/Polish/Russian/Swedish/Portuguese/etc. it returns `other`, so the explicit `<item quantity="zero">` is never picked at runtime and the user sees `"…0 items"` instead of the intended wording. If the design calls for "no items" at count=0, model it as a separate `<string>` and an `if (count == 0)` branch at the call site:
|
||||
```kotlin
|
||||
val label = if (count == 0) {
|
||||
stringRes(R.string.foo_no_items, dateLabel)
|
||||
} else {
|
||||
pluralStringResource(R.plurals.foo_items, count, dateLabel, count)
|
||||
}
|
||||
```
|
||||
- Reference: [Android `<plurals>` docs](https://developer.android.com/guide/topics/resources/string-resource#Plurals) and [CLDR plural rules](https://unicode-org.github.io/cldr-staging/charts/latest/supplemental/language_plural_rules.html).
|
||||
|
||||
**Then ask the user:** "Would you like me to translate these missing strings into [list of target locales]?"
|
||||
@@ -165,4 +201,5 @@ When adding translated strings to locale files:
|
||||
- **Diffing each locale separately** — only diff against `cs-rCZ`; assume the same keys are missing everywhere
|
||||
- **Inserting strings in a specific position** — always append at the bottom; ordering is handled separately
|
||||
- **Hardcoding `"1"` in a `<plurals>` `quantity="one"` item** — always use the count placeholder; otherwise non-English `one` categories produce wrong text
|
||||
- **Copying English's `one`/`other` set into every locale** — each language must include all CLDR plural categories it uses (e.g. Czech needs `one`, `few`, `many`, `other`)
|
||||
- **Copying English's `one`/`other` set into every locale** — each language must include all CLDR plural categories it uses (e.g. Czech needs `one`, `few`, `many`, `other`)
|
||||
- **Using `<item quantity="zero">` to special-case count=0** — outside Arabic and Welsh, this entry is unreachable: ICU/CLDR maps 0 → `other`, so the runtime never picks the zero item and the user sees `"…0 items"`. Special-case at the call site with a separate `<string>` instead.
|
||||
+2
-2
@@ -37,8 +37,8 @@ val DefaultReactions =
|
||||
"\uD83D\uDE31",
|
||||
)
|
||||
|
||||
val DefaultZapAmounts = listOf(100L, 500L, 1000L)
|
||||
val DefaultOnchainZapAmounts = listOf(10_000L)
|
||||
val DefaultZapAmounts = listOf(21L, 50L, 100L)
|
||||
val DefaultOnchainZapAmounts = listOf(5_000L)
|
||||
val DefaultReportWarningThreshold = 5
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -2099,7 +2099,15 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
wasVerified: Boolean,
|
||||
): Boolean {
|
||||
val requestId = event.requestId()
|
||||
val pending = paymentTracker.onResponseReceived(requestId) ?: return false
|
||||
val pending =
|
||||
paymentTracker.onResponseReceived(requestId) ?: run {
|
||||
Log.w(
|
||||
"LocalCache",
|
||||
"NWC response ${event.id} from ${event.pubKey} references request e=$requestId but no pending request is registered. " +
|
||||
"The response was either delivered after timeout, the user holds a stale subscription, or the wallet service set the wrong e tag.",
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
val zappedNote = pending.zappedNote
|
||||
val responseCallback = pending.onResponse
|
||||
|
||||
+8
-2
@@ -152,7 +152,11 @@ class NwcSignerState(
|
||||
|
||||
val assembler = nwcFilterAssembler()
|
||||
|
||||
assembler.subscribe(filter)
|
||||
// Synchronous flush so the REQ frame is queued on the WebSocket before
|
||||
// the EVENT is published. Without this, the bundler may delay REQ up
|
||||
// to 500ms, and the wallet service's ephemeral kind 23195 reply can
|
||||
// be missed.
|
||||
assembler.subscribeAndFlush(filter)
|
||||
|
||||
scope.launch(Dispatchers.IO) {
|
||||
delay(60000)
|
||||
@@ -189,7 +193,9 @@ class NwcSignerState(
|
||||
|
||||
val assembler = nwcFilterAssembler()
|
||||
|
||||
assembler.subscribe(filter)
|
||||
// Synchronous flush so the REQ frame is queued before the EVENT.
|
||||
// See sendNwcRequestToWallet above for the rationale.
|
||||
assembler.subscribeAndFlush(filter)
|
||||
|
||||
scope.launch(Dispatchers.IO) {
|
||||
delay(60000) // waits 1 minute to complete payment.
|
||||
|
||||
+2
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.mixChatsLive.ChannelMetadataAndLiveActivityWatcherSubAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats.ChannelLoaderSubAssembler
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
@@ -31,6 +32,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
@Stable
|
||||
class ChannelFinderQueryState(
|
||||
val channel: Channel,
|
||||
val account: Account,
|
||||
)
|
||||
|
||||
@Stable
|
||||
|
||||
+5
-3
@@ -24,24 +24,26 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@Composable
|
||||
fun ChannelFinderFilterAssemblerSubscription(
|
||||
channel: Channel,
|
||||
accountViewModel: AccountViewModel,
|
||||
) = ChannelFinderFilterAssemblerSubscription(channel, accountViewModel.dataSources().channelFinder)
|
||||
) = ChannelFinderFilterAssemblerSubscription(channel, accountViewModel.account, accountViewModel.dataSources().channelFinder)
|
||||
|
||||
@Composable
|
||||
fun ChannelFinderFilterAssemblerSubscription(
|
||||
channel: Channel,
|
||||
account: Account,
|
||||
dataSource: ChannelFinderFilterAssemblyGroup,
|
||||
) {
|
||||
// different screens get different states
|
||||
// even if they are tracking the same tag.
|
||||
val state =
|
||||
remember(channel) {
|
||||
ChannelFinderQueryState(channel)
|
||||
remember(channel, account) {
|
||||
ChannelFinderQueryState(channel, account)
|
||||
}
|
||||
|
||||
LifecycleAwareKeyDataSourceSubscription(state, dataSource)
|
||||
|
||||
+19
-18
@@ -20,6 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultSearchRelayList
|
||||
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
@@ -34,29 +36,28 @@ fun filterMissingChannelsById(keys: List<ChannelFinderQueryState>): List<RelayBa
|
||||
mapOfSet {
|
||||
keys.forEach { key ->
|
||||
if (key.channel is PublicChatChannel && key.channel.event == null) {
|
||||
key.channel.relays().forEach {
|
||||
val searchRelays =
|
||||
key.account.searchRelayList.flow.value
|
||||
.ifEmpty { DefaultSearchRelayList }
|
||||
val indexerRelays =
|
||||
key.account.indexerRelayList.flow.value
|
||||
.ifEmpty { DefaultIndexerRelayList }
|
||||
|
||||
(key.channel.relays() + searchRelays + indexerRelays).forEach {
|
||||
add(it, key.channel.idHex)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (relayPerChannel.isEmpty()) return emptyList()
|
||||
|
||||
return relayPerChannel.mapNotNull {
|
||||
if (it.value.isEmpty()) {
|
||||
RelayBasedFilter(
|
||||
relay = it.key,
|
||||
filter =
|
||||
Filter(
|
||||
kinds = filterMissingPublicChannelsByIdKinds,
|
||||
ids = it.value.sorted(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
return relayPerChannel.map { (relay, channelIds) ->
|
||||
RelayBasedFilter(
|
||||
relay = relay,
|
||||
filter =
|
||||
Filter(
|
||||
kinds = filterMissingPublicChannelsByIdKinds,
|
||||
ids = channelIds.sorted(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -48,5 +48,17 @@ class NWCPaymentFilterAssembler(
|
||||
|
||||
override fun invalidateKeys() = invalidateFilters()
|
||||
|
||||
/**
|
||||
* Synchronously sends the REQ frame to the relay, bypassing the 500ms
|
||||
* BundledUpdate debounce. Used for NIP-47 RPC where the response is an
|
||||
* ephemeral event (kind 23195) and the subscription must be active on the
|
||||
* relay before we publish the request event — otherwise the relay drops
|
||||
* the response with no replay.
|
||||
*/
|
||||
fun subscribeAndFlush(query: NWCPaymentQueryState) {
|
||||
subscribe(query)
|
||||
group.forEach { it.forceInvalidate() }
|
||||
}
|
||||
|
||||
override fun destroy() = group.forEach { it.destroy() }
|
||||
}
|
||||
|
||||
+7
-1
@@ -82,7 +82,13 @@ fun SensitivityWarning(
|
||||
accountViewModel: AccountViewModel,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
note.event?.let { SensitivityWarning(it, accountViewModel, content) }
|
||||
val noteEvent = note.event
|
||||
|
||||
if (noteEvent == null) {
|
||||
content()
|
||||
} else {
|
||||
SensitivityWarning(noteEvent, accountViewModel, content)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -21,23 +21,44 @@
|
||||
package com.vitorpamplona.amethyst.ui.note
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.BannerImage
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.SimpleImageBorder
|
||||
|
||||
@Composable
|
||||
fun DisplayAuthorBanner(
|
||||
note: Note,
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
modifier: Modifier = SimpleImageBorder,
|
||||
) {
|
||||
WatchAuthor(note, accountViewModel) {
|
||||
val noteAuthor = baseNote.author
|
||||
if (noteAuthor != null) {
|
||||
BannerImage(
|
||||
it,
|
||||
noteAuthor,
|
||||
modifier,
|
||||
accountViewModel,
|
||||
)
|
||||
} else {
|
||||
val authorState by observeNote(baseNote, accountViewModel)
|
||||
CrossfadeIfEnabled(authorState.note.author, accountViewModel = accountViewModel) { author ->
|
||||
if (author != null) {
|
||||
BannerImage(
|
||||
author,
|
||||
modifier,
|
||||
accountViewModel,
|
||||
)
|
||||
} else {
|
||||
BannerImage(
|
||||
null as String?,
|
||||
modifier,
|
||||
accountViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,12 +154,11 @@ fun LoadPublicChatChannel(
|
||||
accountViewModel: AccountViewModel,
|
||||
content: @Composable (PublicChatChannel) -> Unit,
|
||||
) {
|
||||
val channel =
|
||||
produceStateIfNotNull(accountViewModel.getPublicChatChannelIfExists(id), id) {
|
||||
value = accountViewModel.checkGetOrCreatePublicChatChannel(id)
|
||||
}
|
||||
val channel by produceStateIfNotNull(accountViewModel.getPublicChatChannelIfExists(id), id) {
|
||||
value = accountViewModel.checkGetOrCreatePublicChatChannel(id)
|
||||
}
|
||||
|
||||
channel.value?.let { content(it) }
|
||||
channel?.let { content(it) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -284,7 +284,7 @@ fun UpdateZapAmountContent(
|
||||
),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "100, 1000, 5000",
|
||||
text = "21, 50, 100",
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
@@ -406,7 +406,7 @@ fun UpdateZapAmountContent(
|
||||
),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "10000, 50000, 250000",
|
||||
text = "5000, 25000, 100000",
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
|
||||
@@ -236,7 +236,7 @@ fun ZapCustomDialog(
|
||||
),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "100, 1000, 5000",
|
||||
text = "21, 50, 100",
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
|
||||
+17
-23
@@ -119,38 +119,32 @@ fun BannerImage(
|
||||
if (!banner.isNullOrBlank()) {
|
||||
MyAsyncImage(
|
||||
imageUrl = banner,
|
||||
contentDescription =
|
||||
stringRes(
|
||||
R.string.preview_card_image_for,
|
||||
banner,
|
||||
),
|
||||
contentDescription = stringRes(R.string.preview_card_image_for, banner),
|
||||
contentScale = ContentScale.Crop,
|
||||
mainImageModifier = Modifier,
|
||||
loadedImageModifier = modifier,
|
||||
accountViewModel = accountViewModel,
|
||||
onLoadingBackground = {
|
||||
Image(
|
||||
painter = painterRes(R.drawable.profile_banner, 4),
|
||||
contentDescription = stringRes(R.string.profile_banner),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = modifier,
|
||||
)
|
||||
DefaultProfileBanner(modifier, 4)
|
||||
},
|
||||
onError = {
|
||||
Image(
|
||||
painter = painterRes(R.drawable.profile_banner, 4),
|
||||
contentDescription = stringRes(R.string.profile_banner),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = modifier,
|
||||
)
|
||||
DefaultProfileBanner(modifier, 4)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
painter = painterRes(R.drawable.profile_banner, 5),
|
||||
contentDescription = stringRes(R.string.profile_banner),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = modifier,
|
||||
)
|
||||
DefaultProfileBanner(modifier, 5)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DefaultProfileBanner(
|
||||
modifier: Modifier,
|
||||
sizeReference: Int,
|
||||
) {
|
||||
Image(
|
||||
painter = painterRes(R.drawable.profile_banner, sizeReference),
|
||||
contentDescription = stringRes(R.string.profile_banner),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.ui.screen.loggedIn.calendars
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
|
||||
// Picks calendar_day_a11y_no_events when count is 0 because ICU/CLDR maps 0 to
|
||||
// the `other` category for every locale we ship, which would otherwise render
|
||||
// "[date], 0 events" instead of "[date], no events".
|
||||
@Composable
|
||||
fun calendarDayA11yLabel(
|
||||
dateLabel: String,
|
||||
count: Int,
|
||||
): String =
|
||||
if (count == 0) {
|
||||
stringRes(R.string.calendar_day_a11y_no_events, dateLabel)
|
||||
} else {
|
||||
pluralStringResource(R.plurals.calendar_day_a11y_events, count, dateLabel, count)
|
||||
}
|
||||
+1
-3
@@ -46,7 +46,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
@@ -266,8 +265,7 @@ private fun DayCell(
|
||||
MaterialTheme.colorScheme.surface
|
||||
}
|
||||
|
||||
val baseDescription =
|
||||
pluralStringResource(R.plurals.calendar_day_a11y_events, totalEventCount, dateLabel, totalEventCount)
|
||||
val baseDescription = calendarDayA11yLabel(dateLabel, totalEventCount)
|
||||
val todaySuffix = stringRes(R.string.calendar_day_a11y_today_suffix)
|
||||
val selectedSuffix = stringRes(R.string.calendar_day_a11y_selected_suffix)
|
||||
val a11y =
|
||||
|
||||
+1
-2
@@ -44,7 +44,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
@@ -202,7 +201,7 @@ private fun WeekStrip(
|
||||
}
|
||||
|
||||
val dateLabel = formatLongDate(date.atStartOfDay(ZoneId.systemDefault()).toEpochSecond())
|
||||
val baseA11y = pluralStringResource(R.plurals.calendar_day_a11y_events, count, dateLabel, count)
|
||||
val baseA11y = calendarDayA11yLabel(dateLabel, count)
|
||||
val todaySuffix = stringRes(R.string.calendar_day_a11y_today_suffix)
|
||||
val selectedSuffix = stringRes(R.string.calendar_day_a11y_selected_suffix)
|
||||
val a11y =
|
||||
|
||||
+1
-4
@@ -68,7 +68,6 @@ import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.grayText
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -81,8 +80,6 @@ fun RenderPublicChatChannelThumb(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = baseNote.event as? ChannelCreateEvent ?: return
|
||||
|
||||
LoadPublicChatChannel(baseNote.idHex, accountViewModel) {
|
||||
RenderPublicChatChannelThumb(baseNote = baseNote, channel = it, accountViewModel, nav)
|
||||
}
|
||||
@@ -96,7 +93,7 @@ fun RenderPublicChatChannelThumb(
|
||||
nav: INav,
|
||||
) {
|
||||
val channelUpdates by observeChannel(channel, accountViewModel)
|
||||
val publicChat = channelUpdates?.channel as PublicChatChannel
|
||||
val publicChat = (channelUpdates?.channel as? PublicChatChannel) ?: channel
|
||||
|
||||
val name = remember(channelUpdates) { publicChat.toBestDisplayName() }
|
||||
val description = remember(channelUpdates) { publicChat.summary()?.ifBlank { null } }
|
||||
|
||||
+158
-21
@@ -20,24 +20,46 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.publicChats
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyItemScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.layouts.rememberFeedContentPadding
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport
|
||||
import com.vitorpamplona.amethyst.ui.note.ClickableNote
|
||||
import com.vitorpamplona.amethyst.ui.note.LongPressToQuickAction
|
||||
import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.calculateBackgroundColor
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.ChannelCardCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats.RenderPublicChatChannelThumb
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
|
||||
@Composable
|
||||
fun PublicChatsFeedLoaded(
|
||||
@@ -47,30 +69,145 @@ fun PublicChatsFeedLoaded(
|
||||
nav: INav,
|
||||
) {
|
||||
val items by loaded.feed.collectAsStateWithLifecycle()
|
||||
val followedSet by accountViewModel.account.publicChatList.flowSet
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
val pinned by accountViewModel.account.publicChatList.flowSetNote
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
val unpinned =
|
||||
remember(items.list, followedSet) {
|
||||
items.list.filter { it.idHex !in followedSet }
|
||||
}
|
||||
|
||||
LaunchedEffect(pinned.firstOrNull()?.idHex, unpinned.firstOrNull()?.idHex) {
|
||||
if (listState.firstVisibleItemIndex <= 1) {
|
||||
listState.animateScrollToItem(0)
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
contentPadding = rememberFeedContentPadding(FeedPadding),
|
||||
state = listState,
|
||||
) {
|
||||
itemsIndexed(
|
||||
items.list,
|
||||
key = { _, item -> item.idHex },
|
||||
contentType = { _, item -> item.event?.kind ?: -1 },
|
||||
) { _, item ->
|
||||
Row(Modifier.fillMaxWidth().animateItem()) {
|
||||
ChannelCardCompose(
|
||||
baseNote = item,
|
||||
routeForLastRead = "PublicChatsFeed",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
forceEventKind = ChannelCreateEvent.KIND,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
items(
|
||||
pinned,
|
||||
key = { item -> "pinned-" + item.idHex },
|
||||
) { item ->
|
||||
PublicChatRow(item, pinned = true, accountViewModel, nav)
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
if (pinned.isNotEmpty() && unpinned.isNotEmpty()) {
|
||||
item(key = "pinned-unpinned-gap", contentType = "section-gap") {
|
||||
Box(Modifier.fillMaxWidth().height(8.dp))
|
||||
}
|
||||
}
|
||||
|
||||
itemsIndexed(
|
||||
unpinned,
|
||||
key = { _, item -> item.idHex },
|
||||
) { _, item ->
|
||||
PublicChatRow(item, pinned = false, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyItemScope.PublicChatRow(
|
||||
baseNote: Note,
|
||||
pinned: Boolean,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val modifier = Modifier.fillMaxWidth()
|
||||
|
||||
Box(Modifier.fillMaxWidth().animateItem()) {
|
||||
WatchNoteEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
onBlank = {
|
||||
RenderChannel(baseNote, modifier, accountViewModel, nav)
|
||||
},
|
||||
onNoteEventFound = {
|
||||
CheckHiddenFeedWatchBlockAndReport(
|
||||
note = baseNote,
|
||||
modifier = modifier,
|
||||
ignoreAllBlocksAndReports = false,
|
||||
showHiddenWarning = false,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
) { _ ->
|
||||
RenderChannel(baseNote, modifier, accountViewModel, nav)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
if (pinned) {
|
||||
PinBadge(
|
||||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.padding(start = 14.dp, top = 14.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderChannel(
|
||||
baseNote: Note,
|
||||
modifier: Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
LongPressToQuickAction(baseNote, accountViewModel, nav) { showPopup ->
|
||||
ClickableNote(
|
||||
baseNote = baseNote,
|
||||
backgroundColor =
|
||||
calculateBackgroundColor(
|
||||
createdAt = baseNote.createdAt(),
|
||||
routeForLastRead = "PublicChatsFeed",
|
||||
parentBackgroundColor = null,
|
||||
accountViewModel = accountViewModel,
|
||||
),
|
||||
modifier = modifier,
|
||||
accountViewModel = accountViewModel,
|
||||
showPopup = showPopup,
|
||||
nav = nav,
|
||||
) {
|
||||
Column(StdPadding) {
|
||||
SensitivityWarning(
|
||||
note = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
RenderPublicChatChannelThumb(baseNote, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PinBadge(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier =
|
||||
modifier
|
||||
.size(22.dp)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.85f),
|
||||
shape = CircleShape,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.PushPin,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(14.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+24
-1
@@ -99,6 +99,13 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.NumberFormat
|
||||
|
||||
// UX floor for onchain zaps. Below this any on-chain transaction is dominated
|
||||
// by miner fees — the recipient nets close to nothing even at low fee rates,
|
||||
// so quietly funneling the user to a Lightning zap is the friendlier outcome.
|
||||
// This is stricter than the protocol-level [OnchainZapBuilder.DUST_THRESHOLD_SATS]
|
||||
// (330 sats), which only guards against creating outputs the network rejects.
|
||||
private const val MIN_ONCHAIN_ZAP_SATS = 1_000L
|
||||
|
||||
private enum class FeeTier(
|
||||
val label: String,
|
||||
val etaLabel: String,
|
||||
@@ -242,12 +249,14 @@ fun OnchainZapSendDialog(
|
||||
previewShares.orEmpty().filter { it.sats < OnchainZapBuilder.DUST_THRESHOLD_SATS }
|
||||
}
|
||||
|
||||
val belowMinimum = amountSats != null && amountSats > 0 && amountSats < MIN_ONCHAIN_ZAP_SATS
|
||||
|
||||
val canSend =
|
||||
!sending &&
|
||||
result == null &&
|
||||
(splitMode || (resolvedRecipient != null && resolvedRecipient != senderPubKey)) &&
|
||||
amountSats != null &&
|
||||
amountSats > 0 &&
|
||||
amountSats >= MIN_ONCHAIN_ZAP_SATS &&
|
||||
fees != null &&
|
||||
(!splitMode || (previewShares != null && belowDustShares.isEmpty()))
|
||||
|
||||
@@ -353,6 +362,7 @@ fun OnchainZapSendDialog(
|
||||
amountInput = amountInput,
|
||||
onAmountChange = { amountInput = it },
|
||||
presetAmounts = presetAmounts,
|
||||
belowMinimum = belowMinimum,
|
||||
)
|
||||
|
||||
SectionSpacer()
|
||||
@@ -610,6 +620,7 @@ private fun AmountSection(
|
||||
amountInput: String,
|
||||
onAmountChange: (String) -> Unit,
|
||||
presetAmounts: List<Long>,
|
||||
belowMinimum: Boolean,
|
||||
) {
|
||||
SectionLabel("Amount")
|
||||
|
||||
@@ -636,8 +647,20 @@ private fun AmountSection(
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
placeholder = { Text("0") },
|
||||
suffix = { Text("sats", color = MaterialTheme.colorScheme.onSurfaceVariant) },
|
||||
isError = belowMinimum,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
if (belowMinimum) {
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text =
|
||||
"Minimum on-chain zap is ${NumberFormat.getNumberInstance().format(MIN_ONCHAIN_ZAP_SATS)} sats — " +
|
||||
"smaller amounts are eaten by miner fees. Use a Lightning zap instead.",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
+28
-4
@@ -39,6 +39,7 @@ import com.vitorpamplona.quartz.nip47WalletConnect.rpc.NwcTransaction
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.rpc.PayInvoiceErrorResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.rpc.PayInvoiceMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.rpc.PayInvoiceSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.rpc.Response
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -172,6 +173,19 @@ class WalletViewModel : ViewModel() {
|
||||
private val _receiveState = MutableStateFlow<ReceiveState>(ReceiveState.Idle)
|
||||
val receiveState = _receiveState.asStateFlow()
|
||||
|
||||
// A null `Response` means the wallet service replied but the payload could
|
||||
// not be decrypted (wrong key, unexpected format). Any other unexpected
|
||||
// subtype means the response shape didn't match any known NIP-47 result.
|
||||
// Both used to be swallowed silently — now we surface them so the user
|
||||
// can distinguish "wallet never answered" from "wallet answered with
|
||||
// something we can't read".
|
||||
private fun unreadableResponseError(response: Response?): String =
|
||||
if (response == null) {
|
||||
"Could not decrypt the wallet's reply — the wallet may be using a different key"
|
||||
} else {
|
||||
"Wallet returned an unrecognized reply for ${response.resultType}"
|
||||
}
|
||||
|
||||
private fun launchTimeout(onTimeout: () -> Unit): Job =
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
delay(NWC_TIMEOUT_MS)
|
||||
@@ -315,7 +329,9 @@ class WalletViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
else -> {
|
||||
updateWalletInfo(walletId) { it.copy(isLoading = false) }
|
||||
updateWalletInfo(walletId) {
|
||||
it.copy(error = unreadableResponseError(response), isLoading = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,13 +392,16 @@ class WalletViewModel : ViewModel() {
|
||||
is GetBalanceSuccessResponse -> {
|
||||
_balanceSats.value = (response.result?.balance ?: 0L) / 1000L
|
||||
updateWalletInfo(walletId) { it.copy(balanceSats = _balanceSats.value) }
|
||||
_error.value = null
|
||||
}
|
||||
|
||||
is NwcErrorResponse -> {
|
||||
_error.value = response.error?.message ?: "Balance request failed"
|
||||
}
|
||||
|
||||
else -> {}
|
||||
else -> {
|
||||
_error.value = unreadableResponseError(response)
|
||||
}
|
||||
}
|
||||
_isLoading.value = false
|
||||
}
|
||||
@@ -444,13 +463,16 @@ class WalletViewModel : ViewModel() {
|
||||
} else {
|
||||
txs.size >= pageSize
|
||||
}
|
||||
_error.value = null
|
||||
}
|
||||
|
||||
is NwcErrorResponse -> {
|
||||
_error.value = response.error?.message ?: "Failed to load transactions"
|
||||
}
|
||||
|
||||
else -> {}
|
||||
else -> {
|
||||
_error.value = unreadableResponseError(response)
|
||||
}
|
||||
}
|
||||
_isLoading.value = false
|
||||
}
|
||||
@@ -498,7 +520,9 @@ class WalletViewModel : ViewModel() {
|
||||
_error.value = response.error?.message ?: "Failed to load more transactions"
|
||||
}
|
||||
|
||||
else -> {}
|
||||
else -> {
|
||||
_error.value = unreadableResponseError(response)
|
||||
}
|
||||
}
|
||||
_isLoadingMore.value = false
|
||||
}
|
||||
|
||||
@@ -562,6 +562,7 @@
|
||||
<string name="profile_badges_description">Vyberte, které z odznaků, které jste obdrželi, se zobrazí na vašem profilu.</string>
|
||||
<string name="profile_badges_empty">Zatím jste neobdrželi žádné odznaky.</string>
|
||||
<string name="pictures">Obrázky</string>
|
||||
<string name="calendars">Kalendáře</string>
|
||||
<string name="shorts">Krátká videa</string>
|
||||
<string name="public_chats">Veřejné chaty</string>
|
||||
<string name="follow_packs">Seznamy sledování</string>
|
||||
@@ -796,6 +797,10 @@
|
||||
<string name="wallet_connect_manual_config">Pokročilé: zadejte podrobnosti připojení ručně</string>
|
||||
<string name="quick_zap_amounts">Rychlé částky Zap</string>
|
||||
<string name="quick_zap_amounts_explainer">Zobrazí se při stisknutí tlačítka zap. Klepnutím na částku ji odeberete. Pokud ponecháte prázdné, otevře se pokaždé dialog pro zadání částky.</string>
|
||||
<string name="quick_zap_amounts_onchain">Rychlé částky on-chain zapů</string>
|
||||
<string name="quick_zap_amounts_onchain_explainer">On-chain zapy platí poplatky těžařům, takže částky jsou obvykle vyšší než u Lightningu. Klepnutím na částku ji odstraníte.</string>
|
||||
<string name="new_amount_in_sats_onchain">Nová on-chain částka v sats</string>
|
||||
<string name="send_onchain_instead">Odeslat on-chain</string>
|
||||
<string name="zap_privacy_section">Soukromí Zap</string>
|
||||
<string name="zap_type_section_explainer">Určuje, jak je zobrazena vaše identita při odesílání zapsu.</string>
|
||||
<string name="wallet_connect_connect_app">Připojit peněženku</string>
|
||||
@@ -1606,6 +1611,8 @@
|
||||
<string name="route_global">Globální</string>
|
||||
<string name="route_video">Krátké</string>
|
||||
<string name="route_pictures">Obrázky</string>
|
||||
<string name="route_calendars">Kalendáře</string>
|
||||
<string name="route_calendar_collections">Seznamy kalendářů</string>
|
||||
<string name="route_chess">Šachy</string>
|
||||
<string name="wallet">Peněženka</string>
|
||||
<string name="wallet_balance">Zůstatek</string>
|
||||
@@ -1626,6 +1633,7 @@
|
||||
<string name="wallet_creating_invoice">Vytváření faktury…</string>
|
||||
<string name="wallet_copy_invoice">Kopírovat fakturu</string>
|
||||
<string name="wallet_no_transactions">Zatím žádné transakce</string>
|
||||
<string name="wallet_no_transactions_for_filter">Žádné transakce neodpovídají tomuto filtru</string>
|
||||
<string name="wallet_loading">Načítání…</string>
|
||||
<string name="wallet_incoming">Přijato</string>
|
||||
<string name="wallet_outgoing">Odesláno</string>
|
||||
@@ -1654,6 +1662,15 @@
|
||||
<string name="wallet_invalid_uri">Neplatné URI NWC připojení</string>
|
||||
<string name="wallet_move_up">Posunout nahoru</string>
|
||||
<string name="wallet_move_down">Posunout dolů</string>
|
||||
<string name="wallet_onchain_transactions">On-chain transakce</string>
|
||||
<string name="wallet_onchain_no_address">Pro tento účet není k dispozici žádná on-chain adresa.</string>
|
||||
<string name="wallet_onchain_no_backend">Není nakonfigurován žádný on-chain backend.</string>
|
||||
<string name="wallet_onchain_pending">Čeká</string>
|
||||
<string name="wallet_onchain_public_chip">Veřejné</string>
|
||||
<string name="wallet_onchain_public_dialog_title">Tato peněženka je veřejná</string>
|
||||
<string name="wallet_onchain_public_dialog_body">Vaše Taproot adresa je odvozena z vašeho veřejného klíče Nostr, takže kdokoli, kdo zná váš npub, může vidět zůstatek této peněženky a historii transakcí na blockchainu.\n\nAbyste zachovali soukromí, vkládejte do této peněženky a vybírejte z ní prostředky z nesoukromých účtů, jako jsou burzy. Nikdy nemíchejte tyto prostředky se svými studenými peněženkami a zacházejte s nimi jako s penězi, které můžete ztratit, protože kdokoli, kdo má pod kontrolou váš nsec, je může utratit.</string>
|
||||
<string name="wallet_onchain_public_dialog_confirm">Rozumím</string>
|
||||
<string name="wallet_onchain_copy_dialog_confirm">Zkopírovat adresu</string>
|
||||
<string name="route_security_filters">Bezpečnostní filtry</string>
|
||||
<string name="route_import_follows">Importovat sledované</string>
|
||||
<string name="new_post">Nový příspěvek</string>
|
||||
@@ -1666,6 +1683,128 @@
|
||||
<string name="new_zap_poll">Nová zap anketa</string>
|
||||
<string name="new_regular_poll">Nová běžná anketa</string>
|
||||
<string name="new_picture">Nový obrázek</string>
|
||||
<string name="new_calendar_event">Nová událost kalendáře</string>
|
||||
<string name="edit_calendar_event">Upravit událost kalendáře</string>
|
||||
<string name="calendar_event_all_day_locked">Uzamčeno během úprav — změna by vytvořila novou událost.</string>
|
||||
<string name="new_calendar_collection">Nový kalendář</string>
|
||||
<string name="edit_calendar_collection">Upravit kalendář</string>
|
||||
<string name="calendar_collection_events_section">Události v tomto kalendáři (%1$d)</string>
|
||||
<string name="calendar_collection_no_events_yet">Zatím jste nevytvořili žádné události.</string>
|
||||
<string name="calendar_view_feed">Kanál</string>
|
||||
<string name="calendar_view_month">Měsíc</string>
|
||||
<string name="calendar_view_week">Týden</string>
|
||||
<string name="calendar_view_day">Den</string>
|
||||
<string name="calendar_section_upcoming">Nadcházející</string>
|
||||
<string name="calendar_section_past">Minulé</string>
|
||||
<string name="calendar_empty_feed">Z vybraného kanálu zatím nejsou žádné nadcházející ani minulé události.</string>
|
||||
<string name="calendar_empty_collections">Zatím žádné kalendářové seznamy.</string>
|
||||
<string name="calendar_empty_feed_title">Váš kalendář je prázdný</string>
|
||||
<string name="calendar_empty_feed_subtitle">Události sdílené lidmi, které sledujete, se zobrazí zde. Klepnutím na tlačítko + vytvořte vlastní.</string>
|
||||
<string name="calendar_empty_collections_title">Zatím žádné seznamy</string>
|
||||
<string name="calendar_empty_collections_subtitle">Spojte události dohromady — sérii setkání, sekci konference, plán vašeho týmu. Klepnutím na + vytvořte nový.</string>
|
||||
<string name="calendar_empty_day_title">Nic naplánováno</string>
|
||||
<string name="calendar_empty_day_subtitle">V tento den nejsou žádné události. Klepnutím na + přidejte.</string>
|
||||
<string name="calendar_empty_week_title">Tento týden nic</string>
|
||||
<string name="calendar_empty_week_subtitle">V tomto týdnu nejsou žádné události.</string>
|
||||
<string name="calendar_event_title">Název</string>
|
||||
<string name="calendar_event_summary">Shrnutí</string>
|
||||
<string name="calendar_event_location">Místo</string>
|
||||
<string name="calendar_event_image">URL obrázku</string>
|
||||
<string name="calendar_event_all_day">Celodenní událost</string>
|
||||
<string name="calendar_event_start">Začátek</string>
|
||||
<string name="calendar_event_end">Konec</string>
|
||||
<string name="calendar_event_hashtags">Hashtagy (oddělené čárkou)</string>
|
||||
<string name="calendar_event_pick_date">Vybrat datum</string>
|
||||
<string name="calendar_event_pick_time">Vybrat čas</string>
|
||||
<string name="calendar_event_invalid">Název a začátek jsou povinné.</string>
|
||||
<string name="calendar_event_end_before_start">Konec musí být po začátku.</string>
|
||||
<string name="calendar_nav_previous_month">Předchozí měsíc</string>
|
||||
<string name="calendar_nav_next_month">Další měsíc</string>
|
||||
<string name="calendar_nav_previous_week">Předchozí týden</string>
|
||||
<string name="calendar_nav_next_week">Další týden</string>
|
||||
<string name="calendar_nav_previous_day">Předchozí den</string>
|
||||
<string name="calendar_nav_next_day">Další den</string>
|
||||
<string name="calendar_no_events_today">V tento den nejsou žádné události</string>
|
||||
<string name="calendar_no_events">Žádné události</string>
|
||||
<string name="calendar_continues">Pokračuje</string>
|
||||
<string name="calendar_day_of_total">Den %1$d z %2$d</string>
|
||||
<string name="calendar_add_to_phone_calendar">Přidat do kalendáře v telefonu</string>
|
||||
<string name="calendar_nav_jump_to_today">Skočit na dnešek</string>
|
||||
<plurals name="calendar_day_a11y_events">
|
||||
<item quantity="one">%1$s, %2$d událost</item>
|
||||
<item quantity="few">%1$s, %2$d události</item>
|
||||
<item quantity="many">%1$s, %2$d událostí</item>
|
||||
<item quantity="other">%1$s, %2$d událostí</item>
|
||||
</plurals>
|
||||
<string name="calendar_day_a11y_no_events">%1$s, žádné události</string>
|
||||
<string name="calendar_day_a11y_today_suffix">dnes</string>
|
||||
<string name="calendar_day_a11y_selected_suffix">vybráno</string>
|
||||
<string name="calendar_fab_new_event">Vytvořit novou událost kalendáře</string>
|
||||
<string name="calendar_fab_new_collection">Vytvořit nový kalendář</string>
|
||||
<string name="calendar_fab_toggle">Zobrazit možnosti vytvoření</string>
|
||||
<string name="calendar_untitled">(bez názvu)</string>
|
||||
<string name="calendar_all_day">Celodenní</string>
|
||||
<string name="calendar_rsvp_going_prefixed">✓ Účastním se</string>
|
||||
<string name="calendar_rsvp_maybe_prefixed">\? Možná</string>
|
||||
<string name="calendar_rsvp_not_going_prefixed">✗ Nemůžu</string>
|
||||
<string name="calendar_collection_title">Název</string>
|
||||
<string name="calendar_collection_description">Popis</string>
|
||||
<string name="calendar_collection_invalid">Název je povinný.</string>
|
||||
<plurals name="calendar_collection_count">
|
||||
<item quantity="one">%1$d událost</item>
|
||||
<item quantity="few">%1$d události</item>
|
||||
<item quantity="many">%1$d událostí</item>
|
||||
<item quantity="other">%1$d událostí</item>
|
||||
</plurals>
|
||||
<string name="calendar_collection_empty_members">V tomto kalendáři zatím nejsou žádné události.</string>
|
||||
<string name="calendar_rsvp_going">Účastním se</string>
|
||||
<string name="calendar_rsvp_maybe">Možná</string>
|
||||
<string name="calendar_rsvp_not_going">Nemůžu</string>
|
||||
<string name="calendar_rsvp_section">RSVP (%1$d)</string>
|
||||
<string name="calendar_rsvp_none">Zatím žádné RSVP.</string>
|
||||
<string name="calendar_participants_section">Účastníci (%1$d)</string>
|
||||
<string name="calendar_event_in_calendars">V kalendářích (%1$d)</string>
|
||||
<string name="calendar_event_in_no_calendars">Zatím není součástí žádného kalendáře.</string>
|
||||
<string name="calendar_event_loading">Načítání události…</string>
|
||||
<string name="calendar_relative_ongoing">Probíhá nyní</string>
|
||||
<string name="calendar_relative_ongoing_with_end">%1$s · končí %2$s</string>
|
||||
<string name="calendar_export_share_title">Sdílet událost kalendáře</string>
|
||||
<string name="calendar_export_event">Exportovat do kalendáře (.ics)</string>
|
||||
<string name="calendar_reminder_channel_name">Připomenutí kalendáře</string>
|
||||
<string name="calendar_reminder_channel_description">Upozornění, když je událost, které se účastníte, blízko začátku.</string>
|
||||
<string name="calendar_reminder_default_title">Událost kalendáře</string>
|
||||
<plurals name="calendar_reminder_body">
|
||||
<item quantity="one">Začíná za %1$d minutu</item>
|
||||
<item quantity="few">Začíná za %1$d minuty</item>
|
||||
<item quantity="many">Začíná za %1$d minut</item>
|
||||
<item quantity="other">Začíná za %1$d minut</item>
|
||||
</plurals>
|
||||
<string name="calendar_add_to_calendar_action">Přidat do jednoho z vašich kalendářů</string>
|
||||
<string name="calendar_add_to_calendar_title">Přidat do kalendáře</string>
|
||||
<string name="calendar_add_to_calendar_none">Zatím jste nevytvořili žádný kalendář.</string>
|
||||
<string name="calendar_collection_delete">Smazat kalendář</string>
|
||||
<string name="calendar_collection_delete_confirm_title">Smazat tento kalendář?</string>
|
||||
<string name="calendar_collection_delete_confirm_message">Kalendář bude odstraněn. Události v něm nebudou smazány.</string>
|
||||
<string name="calendar_event_pick_image">Vybrat obrázek</string>
|
||||
<string name="calendar_event_image_upload_failed">Nahrání obrázku selhalo</string>
|
||||
<string name="calendar_event_image_upload_failed_body">Vybraný obrázek se nepodařilo nahrát. Zkuste to znovu nebo vložte URL.</string>
|
||||
<string name="calendar_event_participants_section">Účastníci (%1$d)</string>
|
||||
<string name="calendar_event_participant_input">Hledat jméno, npub nebo nip-05</string>
|
||||
<string name="calendar_event_participant_invalid">Zadejte platný npub… nebo 64znakový hex pubkey.</string>
|
||||
<string name="calendar_event_participant_remove">Odebrat účastníka</string>
|
||||
<string name="calendar_reminder_settings_title">Připomenutí kalendáře</string>
|
||||
<string name="calendar_reminder_settings_enabled_title">Posílat připomenutí</string>
|
||||
<string name="calendar_reminder_settings_enabled_subtitle">Oznámení se spustí, když je událost, které se účastníte, blízko začátku.</string>
|
||||
<string name="calendar_reminder_settings_lead_title">Doba předstihu připomenutí</string>
|
||||
<string name="calendar_reminder_settings_lead_subtitle">Kolik minut před událostí chcete být upozorněni.</string>
|
||||
<string name="calendar_share_nostr">Sdílet jako Nostr odkaz</string>
|
||||
<string name="calendar_share_nostr_title">Sdílet odkaz na kalendář</string>
|
||||
<string name="calendar_filter_all">Všechny kalendáře</string>
|
||||
<string name="calendar_filter_sheet_title">Zobrazit události z…</string>
|
||||
<string name="calendar_filter_no_calendars">Zatím jste nevytvořili žádný kalendář.</string>
|
||||
<string name="calendar_open_in_maps">Otevřít v mapách</string>
|
||||
<string name="calendar_open_link">Otevřít odkaz</string>
|
||||
<string name="route_calendar_event_detail">Detail události</string>
|
||||
<string name="new_short_video">Nové krátké video</string>
|
||||
<string name="new_long_video">Nové dlouhé video</string>
|
||||
<string name="article_title">Název</string>
|
||||
@@ -1689,6 +1828,7 @@
|
||||
<string name="boost_or_quote_description">Zvýšit nebo citovat</string>
|
||||
<string name="like_description">Olajkovat</string>
|
||||
<string name="zap_description">Zap</string>
|
||||
<string name="onchain_zap_pending">Čeká na potvrzení</string>
|
||||
<string name="change_reaction">Změnit rychlé reakce</string>
|
||||
<string name="bottom_bar_settings">Spodní navigační lišta</string>
|
||||
<string name="bottom_bar_settings_description">Přetažením změníte pořadí. Přepnutím položku přidáte nebo odeberete ze spodní lišty. S nulovým počtem položek bude lišta skryta.</string>
|
||||
@@ -2040,6 +2180,9 @@
|
||||
<string name="remove_user_from_the_list">Odebrat uživatele ze seznamu</string>
|
||||
<string name="follow_list_item_label">Balíček Sledování</string>
|
||||
<string name="members">Členové</string>
|
||||
<string name="contact_list_containing_label">Seznam sledování (%1$d):</string>
|
||||
<string name="contact_list_screen_title">Seznam sledování</string>
|
||||
<string name="contact_list_screen_title_with_count">Seznam sledování (%1$d)</string>
|
||||
<string name="people_list_title">Metadata seznamu sledování</string>
|
||||
<string name="people_list_explainer">Metadata seznamů sledování mohou vidět všichni na Nostr. Pouze vaši soukromí členové jsou šifrovaní.</string>
|
||||
<string name="follow_pack_title">Metadata sady doporučení</string>
|
||||
|
||||
@@ -556,6 +556,7 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="profile_badges_description">Wähle aus, welche deiner erhaltenen Abzeichen auf deinem Profil erscheinen sollen.</string>
|
||||
<string name="profile_badges_empty">Du hast noch keine Abzeichen erhalten.</string>
|
||||
<string name="pictures">Bilder</string>
|
||||
<string name="calendars">Kalender</string>
|
||||
<string name="shorts">Kurzvideos</string>
|
||||
<string name="public_chats">Öffentliche Chats</string>
|
||||
<string name="follow_packs">Follow-Pakete</string>
|
||||
@@ -788,6 +789,10 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="wallet_connect_manual_config">Erweitert: Verbindungsdetails manuell eingeben</string>
|
||||
<string name="quick_zap_amounts">Schnelle Zap-Beträge</string>
|
||||
<string name="quick_zap_amounts_explainer">Wird beim Drücken der Zap-Schaltfläche angezeigt. Tippe auf einen Betrag, um ihn zu entfernen. Wenn du es leer lässt, wird jedes Mal ein Dialog zur Eingabe eines Betrags geöffnet.</string>
|
||||
<string name="quick_zap_amounts_onchain">Schnelle On-Chain-Zap-Beträge</string>
|
||||
<string name="quick_zap_amounts_onchain_explainer">On-Chain-Zaps zahlen Miner-Gebühren, daher sind die Beträge meist größer als bei Lightning. Tippe auf einen Betrag, um ihn zu entfernen.</string>
|
||||
<string name="new_amount_in_sats_onchain">Neuer On-Chain-Betrag in Sats</string>
|
||||
<string name="send_onchain_instead">Stattdessen On-Chain senden</string>
|
||||
<string name="zap_privacy_section">Zap-Datenschutz</string>
|
||||
<string name="zap_type_section_explainer">Legt fest, wie deine Identität beim Senden eines Zaps angezeigt wird.</string>
|
||||
<string name="wallet_connect_connect_app">Wallet verbinden</string>
|
||||
@@ -1597,6 +1602,8 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="route_global">Global</string>
|
||||
<string name="route_video">Kurzfilme</string>
|
||||
<string name="route_pictures">Bilder</string>
|
||||
<string name="route_calendars">Kalender</string>
|
||||
<string name="route_calendar_collections">Kalenderlisten</string>
|
||||
<string name="route_chess">Schach</string>
|
||||
<string name="wallet">Wallet</string>
|
||||
<string name="wallet_balance">Guthaben</string>
|
||||
@@ -1617,6 +1624,7 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="wallet_creating_invoice">Rechnung wird erstellt…</string>
|
||||
<string name="wallet_copy_invoice">Rechnung kopieren</string>
|
||||
<string name="wallet_no_transactions">Noch keine Transaktionen</string>
|
||||
<string name="wallet_no_transactions_for_filter">Keine Transaktionen entsprechen diesem Filter</string>
|
||||
<string name="wallet_loading">Wird geladen…</string>
|
||||
<string name="wallet_incoming">Empfangen</string>
|
||||
<string name="wallet_outgoing">Gesendet</string>
|
||||
@@ -1645,6 +1653,15 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="wallet_invalid_uri">Ungültige NWC-Verbindungs-URI</string>
|
||||
<string name="wallet_move_up">Nach oben</string>
|
||||
<string name="wallet_move_down">Nach unten</string>
|
||||
<string name="wallet_onchain_transactions">On-Chain-Transaktionen</string>
|
||||
<string name="wallet_onchain_no_address">Keine On-Chain-Adresse für dieses Konto verfügbar.</string>
|
||||
<string name="wallet_onchain_no_backend">Kein On-Chain-Backend konfiguriert.</string>
|
||||
<string name="wallet_onchain_pending">Ausstehend</string>
|
||||
<string name="wallet_onchain_public_chip">Öffentlich</string>
|
||||
<string name="wallet_onchain_public_dialog_title">Diese Wallet ist öffentlich</string>
|
||||
<string name="wallet_onchain_public_dialog_body">Deine Taproot-Adresse wird aus deinem öffentlichen Nostr-Schlüssel abgeleitet, daher kann jeder, der deinen npub kennt, den Saldo und die Transaktionshistorie dieser Wallet in der Blockchain einsehen.\n\nUm deine Privatsphäre zu wahren, lade diese Wallet von nicht-privaten Konten (z. B. Börsen) auf und sende Mittel auch nur an solche Konten zurück. Vermische diese Mittel niemals mit deinen Cold Wallets und behandle sie wie Geld, das du verlieren kannst, da jeder, der die Kontrolle über deinen nsec hat, die Mittel ausgeben kann.</string>
|
||||
<string name="wallet_onchain_public_dialog_confirm">Verstanden</string>
|
||||
<string name="wallet_onchain_copy_dialog_confirm">Adresse kopieren</string>
|
||||
<string name="route_security_filters">Sicherheitsfilter</string>
|
||||
<string name="route_import_follows">Folgeliste importieren</string>
|
||||
<string name="new_post">Neuer Beitrag</string>
|
||||
@@ -1657,6 +1674,124 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="new_zap_poll">Neue Zap-Umfrage</string>
|
||||
<string name="new_regular_poll">Neue reguläre Umfrage</string>
|
||||
<string name="new_picture">Neues Bild</string>
|
||||
<string name="new_calendar_event">Neuer Kalendertermin</string>
|
||||
<string name="edit_calendar_event">Kalendertermin bearbeiten</string>
|
||||
<string name="calendar_event_all_day_locked">Während der Bearbeitung gesperrt — eine Änderung würde stattdessen einen neuen Termin erstellen.</string>
|
||||
<string name="new_calendar_collection">Neuer Kalender</string>
|
||||
<string name="edit_calendar_collection">Kalender bearbeiten</string>
|
||||
<string name="calendar_collection_events_section">Termine in diesem Kalender (%1$d)</string>
|
||||
<string name="calendar_collection_no_events_yet">Du hast noch keine Kalendertermine erstellt.</string>
|
||||
<string name="calendar_view_month">Monat</string>
|
||||
<string name="calendar_view_week">Woche</string>
|
||||
<string name="calendar_view_day">Tag</string>
|
||||
<string name="calendar_section_upcoming">Bevorstehend</string>
|
||||
<string name="calendar_section_past">Vergangen</string>
|
||||
<string name="calendar_empty_feed">Noch keine bevorstehenden oder vergangenen Kalendertermine aus deinem ausgewählten Feed.</string>
|
||||
<string name="calendar_empty_collections">Noch keine Kalenderlisten.</string>
|
||||
<string name="calendar_empty_feed_title">Dein Kalender ist leer</string>
|
||||
<string name="calendar_empty_feed_subtitle">Termine, die von Personen geteilt werden, denen du folgst, erscheinen hier. Tippe auf +, um eigene zu erstellen.</string>
|
||||
<string name="calendar_empty_collections_title">Noch keine Listen</string>
|
||||
<string name="calendar_empty_collections_subtitle">Gruppiere Termine — eine Meetup-Reihe, ein Konferenz-Track, die Roadmap deines Teams. Tippe auf +, um eine zu erstellen.</string>
|
||||
<string name="calendar_empty_day_title">Nichts geplant</string>
|
||||
<string name="calendar_empty_day_subtitle">Keine Termine an diesem Tag. Tippe auf +, um einen hinzuzufügen.</string>
|
||||
<string name="calendar_empty_week_title">Nichts diese Woche</string>
|
||||
<string name="calendar_empty_week_subtitle">Keine Termine in dieser Woche.</string>
|
||||
<string name="calendar_event_title">Titel</string>
|
||||
<string name="calendar_event_summary">Zusammenfassung</string>
|
||||
<string name="calendar_event_location">Ort</string>
|
||||
<string name="calendar_event_image">Bild-URL</string>
|
||||
<string name="calendar_event_all_day">Ganztägiger Termin</string>
|
||||
<string name="calendar_event_start">Beginnt</string>
|
||||
<string name="calendar_event_end">Endet</string>
|
||||
<string name="calendar_event_hashtags">Hashtags (durch Komma getrennt)</string>
|
||||
<string name="calendar_event_pick_date">Datum wählen</string>
|
||||
<string name="calendar_event_pick_time">Zeit wählen</string>
|
||||
<string name="calendar_event_invalid">Titel und Start sind erforderlich.</string>
|
||||
<string name="calendar_event_end_before_start">Ende muss nach Beginn sein.</string>
|
||||
<string name="calendar_nav_previous_month">Vorheriger Monat</string>
|
||||
<string name="calendar_nav_next_month">Nächster Monat</string>
|
||||
<string name="calendar_nav_previous_week">Vorherige Woche</string>
|
||||
<string name="calendar_nav_next_week">Nächste Woche</string>
|
||||
<string name="calendar_nav_previous_day">Vorheriger Tag</string>
|
||||
<string name="calendar_nav_next_day">Nächster Tag</string>
|
||||
<string name="calendar_no_events_today">Keine Termine an diesem Tag</string>
|
||||
<string name="calendar_no_events">Keine Termine</string>
|
||||
<string name="calendar_continues">Geht weiter</string>
|
||||
<string name="calendar_day_of_total">Tag %1$d von %2$d</string>
|
||||
<string name="calendar_add_to_phone_calendar">Zum Telefonkalender hinzufügen</string>
|
||||
<string name="calendar_nav_jump_to_today">Zu heute springen</string>
|
||||
<plurals name="calendar_day_a11y_events">
|
||||
<item quantity="one">%1$s, %2$d Termin</item>
|
||||
<item quantity="other">%1$s, %2$d Termine</item>
|
||||
</plurals>
|
||||
<string name="calendar_day_a11y_no_events">%1$s, keine Termine</string>
|
||||
<string name="calendar_day_a11y_today_suffix">heute</string>
|
||||
<string name="calendar_day_a11y_selected_suffix">ausgewählt</string>
|
||||
<string name="calendar_fab_new_event">Neuen Kalendertermin erstellen</string>
|
||||
<string name="calendar_fab_new_collection">Neuen Kalender erstellen</string>
|
||||
<string name="calendar_fab_toggle">Erstellungsoptionen anzeigen</string>
|
||||
<string name="calendar_untitled">(ohne Titel)</string>
|
||||
<string name="calendar_all_day">Ganztägig</string>
|
||||
<string name="calendar_rsvp_going_prefixed">✓ Komme</string>
|
||||
<string name="calendar_rsvp_maybe_prefixed">\? Vielleicht</string>
|
||||
<string name="calendar_rsvp_not_going_prefixed">✗ Komme nicht</string>
|
||||
<string name="calendar_collection_title">Titel</string>
|
||||
<string name="calendar_collection_description">Beschreibung</string>
|
||||
<string name="calendar_collection_invalid">Ein Titel ist erforderlich.</string>
|
||||
<plurals name="calendar_collection_count">
|
||||
<item quantity="one">%1$d Termin</item>
|
||||
<item quantity="other">%1$d Termine</item>
|
||||
</plurals>
|
||||
<string name="calendar_collection_empty_members">Noch keine Termine in diesem Kalender.</string>
|
||||
<string name="calendar_rsvp_going">Komme</string>
|
||||
<string name="calendar_rsvp_maybe">Vielleicht</string>
|
||||
<string name="calendar_rsvp_not_going">Komme nicht</string>
|
||||
<string name="calendar_rsvp_none">Noch keine Antworten.</string>
|
||||
<string name="calendar_participants_section">Teilnehmer (%1$d)</string>
|
||||
<string name="calendar_event_in_calendars">In Kalendern (%1$d)</string>
|
||||
<string name="calendar_event_in_no_calendars">Noch nicht Teil eines Kalenders.</string>
|
||||
<string name="calendar_event_loading">Termin wird geladen…</string>
|
||||
<string name="calendar_relative_ongoing">Findet jetzt statt</string>
|
||||
<string name="calendar_relative_ongoing_with_end">%1$s · endet %2$s</string>
|
||||
<string name="calendar_export_share_title">Kalendertermin teilen</string>
|
||||
<string name="calendar_export_event">In Kalender exportieren (.ics)</string>
|
||||
<string name="calendar_reminder_channel_name">Kalendererinnerungen</string>
|
||||
<string name="calendar_reminder_channel_description">Hinweis, wenn ein Termin, an dem du teilnimmst, bald beginnt.</string>
|
||||
<string name="calendar_reminder_default_title">Kalendertermin</string>
|
||||
<plurals name="calendar_reminder_body">
|
||||
<item quantity="one">Beginnt in %1$d Minute</item>
|
||||
<item quantity="other">Beginnt in %1$d Minuten</item>
|
||||
</plurals>
|
||||
<string name="calendar_add_to_calendar_action">Zu einem deiner Kalender hinzufügen</string>
|
||||
<string name="calendar_add_to_calendar_title">Zu einem Kalender hinzufügen</string>
|
||||
<string name="calendar_add_to_calendar_none">Du hast noch keine Kalender erstellt.</string>
|
||||
<string name="calendar_collection_delete">Kalender löschen</string>
|
||||
<string name="calendar_collection_delete_confirm_title">Diesen Kalender löschen?</string>
|
||||
<string name="calendar_collection_delete_confirm_message">Die Kalenderliste wird entfernt. Termine darin werden nicht gelöscht.</string>
|
||||
<string name="calendar_event_pick_image">Bild wählen</string>
|
||||
<string name="calendar_event_image_upload_failed">Bild-Upload fehlgeschlagen</string>
|
||||
<string name="calendar_event_image_upload_failed_body">Das gewählte Bild konnte nicht hochgeladen werden. Versuche es erneut oder füge eine URL ein.</string>
|
||||
<string name="calendar_event_participants_section">Teilnehmer (%1$d)</string>
|
||||
<string name="calendar_event_participant_input">Name, npub oder nip-05 suchen</string>
|
||||
<string name="calendar_event_participant_invalid">Gib einen gültigen npub… oder 64-stelligen hex pubkey ein.</string>
|
||||
<string name="calendar_event_participant_remove">Teilnehmer entfernen</string>
|
||||
<string name="calendar_reminder_settings_title">Kalendererinnerungen</string>
|
||||
<string name="calendar_reminder_settings_enabled_title">Erinnerungen senden</string>
|
||||
<string name="calendar_reminder_settings_enabled_subtitle">Eine Benachrichtigung wird angezeigt, wenn ein Termin, an dem du teilnimmst, bald beginnt.</string>
|
||||
<string name="calendar_reminder_settings_lead_title">Vorlaufzeit der Erinnerung</string>
|
||||
<string name="calendar_reminder_settings_lead_subtitle">Wie viele Minuten vor dem Termin du benachrichtigt werden möchtest.</string>
|
||||
<plurals name="calendar_reminder_settings_lead_choice">
|
||||
<item quantity="one">%1$d Min.</item>
|
||||
<item quantity="other">%1$d Min.</item>
|
||||
</plurals>
|
||||
<string name="calendar_share_nostr">Als Nostr-Link teilen</string>
|
||||
<string name="calendar_share_nostr_title">Kalender-Link teilen</string>
|
||||
<string name="calendar_filter_all">Alle Kalender</string>
|
||||
<string name="calendar_filter_sheet_title">Termine anzeigen aus…</string>
|
||||
<string name="calendar_filter_no_calendars">Du hast noch keine Kalender erstellt.</string>
|
||||
<string name="calendar_open_in_maps">In Karten öffnen</string>
|
||||
<string name="calendar_open_link">Link öffnen</string>
|
||||
<string name="route_calendar_event_detail">Termindetails</string>
|
||||
<string name="new_short_video">Neues kurzes Video</string>
|
||||
<string name="new_long_video">Neues langes Video</string>
|
||||
<string name="article_title">Titel</string>
|
||||
@@ -1680,6 +1815,8 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="boost_or_quote_description">Boosten oder Zitieren</string>
|
||||
<string name="like_description">Gefällt mir</string>
|
||||
<string name="zap_description">Zap</string>
|
||||
<string name="onchain_zap_description">On-Chain-Bitcoin-Zap</string>
|
||||
<string name="onchain_zap_pending">Wartet auf Bestätigung</string>
|
||||
<string name="change_reaction">Schnelle Reaktionen ändern</string>
|
||||
<string name="bottom_bar_settings">Untere Navigationsleiste</string>
|
||||
<string name="bottom_bar_settings_description">Zum Umordnen ziehen. Zum Hinzufügen oder Entfernen eines Elements aus der unteren Leiste umschalten. Mit null Elementen wird die untere Leiste ausgeblendet.</string>
|
||||
@@ -2029,6 +2166,9 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="remove_user_from_the_list">Benutzer aus der Liste entfernen</string>
|
||||
<string name="follow_list_item_label">Folge Paket</string>
|
||||
<string name="members">Mitglieder</string>
|
||||
<string name="contact_list_containing_label">Folgenliste (%1$d Nutzer):</string>
|
||||
<string name="contact_list_screen_title">Folgenliste</string>
|
||||
<string name="contact_list_screen_title_with_count">Folgenliste (%1$d)</string>
|
||||
<string name="people_list_title">Metadaten der Follow-Liste</string>
|
||||
<string name="people_list_explainer">Metadaten von Follow-Listen können von allen auf Nostr gesehen werden. Nur deine privaten Mitglieder sind verschlüsselt.</string>
|
||||
<string name="follow_pack_title">Metadaten des Empfehlungspakets</string>
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
<string name="show_anyway">Mostrar de todos modos</string>
|
||||
<string name="post_was_hidden">Esta publicación se ocultó porque menciona tus usuarios o palabras ocultas</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">Post marcado como inapropiado por</string>
|
||||
<plurals name="post_was_hidden_due_to_too_many_hashtags">
|
||||
<item quantity="one">Esta publicación tiene más de %1$d hashtag</item>
|
||||
<item quantity="other">Esta publicación tiene más de %1$d hashtags</item>
|
||||
</plurals>
|
||||
<string name="post_not_found">El evento se está cargando o no se puede encontrar en la lista de relés</string>
|
||||
<string name="post_not_found_short">👀</string>
|
||||
<string name="channel_image">Imagen del canal</string>
|
||||
@@ -30,6 +34,7 @@
|
||||
<string name="copy_text">Copiar texto</string>
|
||||
<string name="copy_user_pubkey">Copiar PubKey del usuario</string>
|
||||
<string name="copy_note_id">Copiar ID de la nota</string>
|
||||
<string name="copy_raw_json">Copiar JSON sin procesar</string>
|
||||
<string name="broadcast">Transmisión</string>
|
||||
<string name="timestamp_it">Poner marca de tiempo</string>
|
||||
<string name="timestamp_pending">Marca de tiempo: confirmaciones pendientes</string>
|
||||
@@ -49,6 +54,10 @@
|
||||
<string name="login_with_a_private_key_to_be_able_to_boost_posts">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder impulsar publicaciones.</string>
|
||||
<string name="login_with_a_private_key_to_like_posts">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para indicar que te gustan las publicaciones.</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">Falta la configuración de zaps por defecto. Mantén pulsado unos segundos para cambiarla</string>
|
||||
<string name="chat_zap_amount_suffix">zapeó %1$s sats</string>
|
||||
<string name="chat_zap_anonymous">Anónimo</string>
|
||||
<string name="chat_raid_is_raiding">está en una incursión</string>
|
||||
<string name="chat_clip_created_a_clip">creó un clip</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder enviar zaps.</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder seguir a otros usuarios.</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder dejar de seguir a otros usuarios.</string>
|
||||
@@ -153,6 +162,8 @@
|
||||
<string name="pronouns">Pronombres</string>
|
||||
<string name="ln_address">Dirección LN</string>
|
||||
<string name="ln_url_outdated">Dirección LN (antigua)</string>
|
||||
<string name="stale_relay_hint_label">Los relés de origen pueden ser obsoletos</string>
|
||||
<string name="stale_relay_hint_icon_description">Aviso de relé obsoleto</string>
|
||||
<string name="download_to_phone">Guardar en el teléfono</string>
|
||||
<string name="save_to_gallery">Guardar en la galería</string>
|
||||
<string name="image_saved_to_the_gallery">Imagen guardada en la galería</string>
|
||||
@@ -161,6 +172,8 @@
|
||||
<string name="failed_to_save_the_image">Error al guardar la imagen</string>
|
||||
<string name="video_saved_to_the_gallery">Video guardado en la galería de videos del teléfono</string>
|
||||
<string name="failed_to_save_the_video">Error al guardar el video</string>
|
||||
<string name="pdf_saved_to_the_gallery">PDF guardado en Descargas/Amethyst</string>
|
||||
<string name="failed_to_save_the_pdf">Error al guardar el PDF</string>
|
||||
<string name="upload_image">Cargar imagen</string>
|
||||
<string name="upload_file">Cargar archivo</string>
|
||||
<string name="take_a_picture">Hacer una foto</string>
|
||||
@@ -270,6 +283,8 @@
|
||||
Son ideales para comunidades abiertas en torno a temas específicos. Algunos de estos grupos son efímeros
|
||||
y, por lo tanto, los mensajes del chat desaparecen con el tiempo</string>
|
||||
<string name="public_chat">Chat público</string>
|
||||
<string name="marmot_group">Grupo MLS</string>
|
||||
<string name="marmot_group_no_messages_yet">Aún no hay mensajes</string>
|
||||
<string name="public_chat_title">Metadatos públicos del chat</string>
|
||||
<string name="public_chat_explainer">Los chats públicos son visibles para todos en Nostr y cualquiera
|
||||
puede participar en ellos. Son ideales para comunidades abiertas alrededor de temas específicos.
|
||||
@@ -334,6 +349,11 @@
|
||||
<string name="private_conversation_notification">"<Unable to decrypt private message>\n\nTe citaron en una conversación privada o encriptada entre %1$s y %2$s."</string>
|
||||
<string name="account_switch_add_account_dialog_title">Agregar cuenta nueva</string>
|
||||
<string name="drawer_accounts">Cuentas</string>
|
||||
<string name="drawer_section_navigate">Navegar</string>
|
||||
<string name="drawer_section_you">Tú</string>
|
||||
<string name="drawer_section_feeds">Tablones</string>
|
||||
<string name="drawer_section_create">Crear</string>
|
||||
<string name="drawer_section_system">Sistema</string>
|
||||
<string name="account_switch_select_account">Seleccionar cuenta</string>
|
||||
<string name="account_switch_add_account_btn">Agregar cuenta nueva</string>
|
||||
<string name="account_switch_active_account">Cuenta activa</string>
|
||||
@@ -356,6 +376,8 @@
|
||||
<string name="quick_action_block_dialog_btn">Bloquear</string>
|
||||
<string name="quick_action_delete_dialog_btn">Eliminar</string>
|
||||
<string name="quick_action_block">Bloquear</string>
|
||||
<string name="quick_action_mute_thread">Silenciar hilo</string>
|
||||
<string name="quick_action_unmute_thread">Reactivar hilo</string>
|
||||
<string name="quick_action_report">Reportar</string>
|
||||
<string name="quick_action_delete_button">Eliminar</string>
|
||||
<string name="quick_action_dont_show_again_button">No mostrar de nuevo</string>
|
||||
@@ -385,9 +407,49 @@
|
||||
<string name="migrate_bookmarks_button">Mover todo a marcadores nuevos</string>
|
||||
<string name="migrate_bookmarks_success">Los marcadores se migraron correctamente</string>
|
||||
<string name="drafts">Borradores</string>
|
||||
<string name="scheduled_posts">Publicaciones programadas</string>
|
||||
<string name="schedule_post">Programar</string>
|
||||
<string name="schedule_post_time_label">Hora programada</string>
|
||||
<string name="schedule_post_helper">Las publicaciones se realizan en ~15 minutos a partir de la hora programada.</string>
|
||||
<string name="schedule_post_pick_time">Elegir hora programada</string>
|
||||
<string name="schedule_post_pick_label">Programación para…</string>
|
||||
<string name="schedule_post_publishes_in">Se publica en %1$s</string>
|
||||
<string name="schedule_post_was_due">Se venció hace %1$s</string>
|
||||
<string name="schedule_post_picker_time_title">Hora</string>
|
||||
<string name="schedule_post_button_add">Programar publicación</string>
|
||||
<string name="schedule_post_button_remove">Cancelar programación</string>
|
||||
<string name="schedule_post_warning_title">Las notificaciones siempre activas están deshabilitadas</string>
|
||||
<string name="schedule_post_warning_single">Las publicaciones programadas pueden no realizarse hasta que vuelvas a abrir la aplicación. Habilita la activación en \"Configuración → Preferencias de interfaz de usuario\" para garantizar una programación en segundo plano fiable.</string>
|
||||
<string name="schedule_post_warning_multi">Las publicaciones programadas pueden no realizarse hasta que vuelvas a abrir la aplicación. Las publicaciones programadas de otras cuentas no se activarán mientras esta cuenta esté activa. Habilita la activación en \"Configuración → Preferencias de interfaz de usuario\" para garantizar una programación en segundo plano fiable.</string>
|
||||
<string name="schedule_post_preset_in_one_hour">En 1 hora</string>
|
||||
<string name="schedule_post_preset_tomorrow_morning">Mañana a las 9 a. m.</string>
|
||||
<string name="schedule_post_preset_next_monday_morning">El próximo lunes a las 9 a. m.</string>
|
||||
<string name="schedule_post_always_on_prompt_title">¿Habilitar notificaciones siempre activas?</string>
|
||||
<string name="schedule_post_always_on_prompt_message">Las publicaciones programadas solo se realizan de forma fiable cuando las notificaciones siempre activas están habilitadas. De lo contrario, es posible que no se activen hasta que vuelvas a abrir la app.</string>
|
||||
<string name="schedule_post_always_on_prompt_open_settings">Abrir configuración</string>
|
||||
<string name="schedule_post_always_on_prompt_continue">Continuar de todos modos</string>
|
||||
<string name="scheduled_posts_at_time">%1$s · en %2$s</string>
|
||||
<string name="scheduled_posts_at_time_past">%1$s · hace %2$s</string>
|
||||
<string name="scheduled_posts_day_tomorrow">Mañana</string>
|
||||
<string name="scheduled_posts_logout_toast_zero">Sesión cerrada</string>
|
||||
<plurals name="scheduled_posts_logout_toast">
|
||||
<item quantity="one">Sesión cerrada · %d publicación programada eliminada</item>
|
||||
<item quantity="other">Sesión cerrada · %d publicaciones programadas eliminadas</item>
|
||||
</plurals>
|
||||
<string name="scheduled_posts_notification_sent_title">Se realizó la publicación programada</string>
|
||||
<string name="scheduled_posts_notification_failed_title">Fallo en la publicación programada</string>
|
||||
<string name="app_notification_scheduled_posts_channel_name">Publicaciones programadas</string>
|
||||
<string name="app_notification_scheduled_posts_channel_description">Notificaciones cuando una publicación programada se realiza o no.</string>
|
||||
<string name="scheduled_posts_action_send_now">Enviar ahora</string>
|
||||
<string name="polls">Encuestas</string>
|
||||
<string name="open_polls">Abiertas</string>
|
||||
<string name="closed_polls">Cerradas</string>
|
||||
<string name="new_community_rules_limits_save">Guardar límites</string>
|
||||
<string name="new_community_rules_banned_section">Usuarios bloqueados</string>
|
||||
<string name="new_community_rules_banned_hint">Estas claves públicas no pueden publicar en la comunidad.</string>
|
||||
<string name="new_community_rules_banned_add">Agregar un usuario bloqueado</string>
|
||||
<string name="new_community_rules_banned_placeholder">Nombre, npub, o NIP-05</string>
|
||||
<string name="new_community_rules_wot_section">Portal de red de confianza (opcional)</string>
|
||||
<string name="pictures">Imágenes</string>
|
||||
<string name="shorts">Cortos</string>
|
||||
<string name="longs">Videos</string>
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
<string name="show_anyway">Mostrar de todos modos</string>
|
||||
<string name="post_was_hidden">Esta publicación se ocultó porque menciona tus usuarios o palabras ocultas</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">La publicación fue reportada por</string>
|
||||
<plurals name="post_was_hidden_due_to_too_many_hashtags">
|
||||
<item quantity="one">Esta publicación tiene más de %1$d hashtag</item>
|
||||
<item quantity="other">Esta publicación tiene más de %1$d hashtags</item>
|
||||
</plurals>
|
||||
<string name="post_not_found">El evento se está cargando o no se puede encontrar en la lista de relés</string>
|
||||
<string name="post_not_found_short">👀</string>
|
||||
<string name="channel_image">Imagen del canal</string>
|
||||
@@ -30,6 +34,7 @@
|
||||
<string name="copy_text">Copiar texto</string>
|
||||
<string name="copy_user_pubkey">Copiar llave pública del usuario</string>
|
||||
<string name="copy_note_id">Copiar ID de la nota</string>
|
||||
<string name="copy_raw_json">Copiar JSON sin procesar</string>
|
||||
<string name="broadcast">Transmisión</string>
|
||||
<string name="timestamp_it">Poner marca de tiempo</string>
|
||||
<string name="timestamp_pending">Marca de tiempo: confirmaciones pendientes</string>
|
||||
@@ -49,6 +54,10 @@
|
||||
<string name="login_with_a_private_key_to_be_able_to_boost_posts">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder impulsar publicaciones</string>
|
||||
<string name="login_with_a_private_key_to_like_posts">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para indicar que te gustan las publicaciones.</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">No has configurado la cantidad de zaps. Mantén presionado el botón para cambiarla.</string>
|
||||
<string name="chat_zap_amount_suffix">zapeó %1$s sats</string>
|
||||
<string name="chat_zap_anonymous">Anónimo</string>
|
||||
<string name="chat_raid_is_raiding">está en una incursión</string>
|
||||
<string name="chat_clip_created_a_clip">creó un clip</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder enviar zaps.</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder seguir a otros usuarios.</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder dejar de seguir a otros usuarios.</string>
|
||||
@@ -152,6 +161,8 @@
|
||||
<string name="pronouns">Pronombres</string>
|
||||
<string name="ln_address">Dirección de Lightning</string>
|
||||
<string name="ln_url_outdated">URL de Lightning (obsoleta)</string>
|
||||
<string name="stale_relay_hint_label">Los relés de origen pueden ser obsoletos</string>
|
||||
<string name="stale_relay_hint_icon_description">Aviso de relé obsoleto</string>
|
||||
<string name="download_to_phone">Guardar en el teléfono</string>
|
||||
<string name="save_to_gallery">Guardar en la galería</string>
|
||||
<string name="image_saved_to_the_gallery">Imagen guardada en la galería</string>
|
||||
@@ -160,6 +171,8 @@
|
||||
<string name="failed_to_save_the_image">Error al guardar la imagen</string>
|
||||
<string name="video_saved_to_the_gallery">Video guardado en la galería de videos del teléfono</string>
|
||||
<string name="failed_to_save_the_video">Error al guardar el video</string>
|
||||
<string name="pdf_saved_to_the_gallery">PDF guardado en Descargas/Amethyst</string>
|
||||
<string name="failed_to_save_the_pdf">Error al guardar el PDF</string>
|
||||
<string name="upload_image">Subir imagen</string>
|
||||
<string name="upload_file">Subir archivo</string>
|
||||
<string name="take_a_picture">Tomar una foto</string>
|
||||
@@ -266,6 +279,8 @@
|
||||
Son ideales para comunidades abiertas en torno a temas específicos. Algunos de estos grupos son efímeros
|
||||
y, por lo tanto, los mensajes del chat desaparecen con el tiempo</string>
|
||||
<string name="public_chat">Chat público</string>
|
||||
<string name="marmot_group">Grupo MLS</string>
|
||||
<string name="marmot_group_no_messages_yet">Aún no hay mensajes</string>
|
||||
<string name="public_chat_title">Metadatos públicos del chat</string>
|
||||
<string name="public_chat_explainer">Los chats públicos son visibles para todos en Nostr y cualquiera
|
||||
puede participar en ellos. Son ideales para comunidades abiertas alrededor de temas específicos.
|
||||
@@ -330,6 +345,11 @@
|
||||
<string name="private_conversation_notification">"<Unable to decrypt private message>\n\nTe citaron en una conversación privada o encriptada entre %1$s y %2$s."</string>
|
||||
<string name="account_switch_add_account_dialog_title">Agregar cuenta nueva</string>
|
||||
<string name="drawer_accounts">Cuentas</string>
|
||||
<string name="drawer_section_navigate">Navegar</string>
|
||||
<string name="drawer_section_you">Tú</string>
|
||||
<string name="drawer_section_feeds">Tablones</string>
|
||||
<string name="drawer_section_create">Crear</string>
|
||||
<string name="drawer_section_system">Sistema</string>
|
||||
<string name="account_switch_select_account">Seleccionar cuenta</string>
|
||||
<string name="account_switch_add_account_btn">Agregar cuenta nueva</string>
|
||||
<string name="account_switch_active_account">Cuenta activa</string>
|
||||
@@ -352,6 +372,8 @@
|
||||
<string name="quick_action_block_dialog_btn">Bloquear</string>
|
||||
<string name="quick_action_delete_dialog_btn">Eliminar</string>
|
||||
<string name="quick_action_block">Bloquear</string>
|
||||
<string name="quick_action_mute_thread">Silenciar hilo</string>
|
||||
<string name="quick_action_unmute_thread">Reactivar hilo</string>
|
||||
<string name="quick_action_report">Reportar</string>
|
||||
<string name="quick_action_delete_button">Eliminar</string>
|
||||
<string name="quick_action_dont_show_again_button">No mostrar de nuevo</string>
|
||||
@@ -381,9 +403,49 @@
|
||||
<string name="migrate_bookmarks_button">Mover todo a marcadores nuevos</string>
|
||||
<string name="migrate_bookmarks_success">Los marcadores se migraron correctamente</string>
|
||||
<string name="drafts">Borradores</string>
|
||||
<string name="scheduled_posts">Publicaciones programadas</string>
|
||||
<string name="schedule_post">Programar</string>
|
||||
<string name="schedule_post_time_label">Hora programada</string>
|
||||
<string name="schedule_post_helper">Las publicaciones se realizan en ~15 minutos a partir de la hora programada.</string>
|
||||
<string name="schedule_post_pick_time">Elegir hora programada</string>
|
||||
<string name="schedule_post_pick_label">Programación para…</string>
|
||||
<string name="schedule_post_publishes_in">Se publica en %1$s</string>
|
||||
<string name="schedule_post_was_due">Se venció hace %1$s</string>
|
||||
<string name="schedule_post_picker_time_title">Hora</string>
|
||||
<string name="schedule_post_button_add">Programar publicación</string>
|
||||
<string name="schedule_post_button_remove">Cancelar programación</string>
|
||||
<string name="schedule_post_warning_title">Las notificaciones siempre activas están deshabilitadas</string>
|
||||
<string name="schedule_post_warning_single">Las publicaciones programadas pueden no realizarse hasta que vuelvas a abrir la aplicación. Habilita la activación en \"Configuración → Preferencias de interfaz de usuario\" para garantizar una programación en segundo plano fiable.</string>
|
||||
<string name="schedule_post_warning_multi">Las publicaciones programadas pueden no realizarse hasta que vuelvas a abrir la aplicación. Las publicaciones programadas de otras cuentas no se activarán mientras esta cuenta esté activa. Habilita la activación en \"Configuración → Preferencias de interfaz de usuario\" para garantizar una programación en segundo plano fiable.</string>
|
||||
<string name="schedule_post_preset_in_one_hour">En 1 hora</string>
|
||||
<string name="schedule_post_preset_tomorrow_morning">Mañana a las 9 a. m.</string>
|
||||
<string name="schedule_post_preset_next_monday_morning">El próximo lunes a las 9 a. m.</string>
|
||||
<string name="schedule_post_always_on_prompt_title">¿Habilitar notificaciones siempre activas?</string>
|
||||
<string name="schedule_post_always_on_prompt_message">Las publicaciones programadas solo se realizan de forma fiable cuando las notificaciones siempre activas están habilitadas. De lo contrario, es posible que no se activen hasta que vuelvas a abrir la app.</string>
|
||||
<string name="schedule_post_always_on_prompt_open_settings">Abrir configuración</string>
|
||||
<string name="schedule_post_always_on_prompt_continue">Continuar de todos modos</string>
|
||||
<string name="scheduled_posts_at_time">%1$s · en %2$s</string>
|
||||
<string name="scheduled_posts_at_time_past">%1$s · hace %2$s</string>
|
||||
<string name="scheduled_posts_day_tomorrow">Mañana</string>
|
||||
<string name="scheduled_posts_logout_toast_zero">Sesión cerrada</string>
|
||||
<plurals name="scheduled_posts_logout_toast">
|
||||
<item quantity="one">Sesión cerrada · %d publicación programada eliminada</item>
|
||||
<item quantity="other">Sesión cerrada · %d publicaciones programadas eliminadas</item>
|
||||
</plurals>
|
||||
<string name="scheduled_posts_notification_sent_title">Se realizó la publicación programada</string>
|
||||
<string name="scheduled_posts_notification_failed_title">Fallo en la publicación programada</string>
|
||||
<string name="app_notification_scheduled_posts_channel_name">Publicaciones programadas</string>
|
||||
<string name="app_notification_scheduled_posts_channel_description">Notificaciones cuando una publicación programada se realiza o no.</string>
|
||||
<string name="scheduled_posts_action_send_now">Enviar ahora</string>
|
||||
<string name="polls">Encuestas</string>
|
||||
<string name="open_polls">Abiertas</string>
|
||||
<string name="closed_polls">Cerradas</string>
|
||||
<string name="new_community_rules_limits_save">Guardar límites</string>
|
||||
<string name="new_community_rules_banned_section">Usuarios bloqueados</string>
|
||||
<string name="new_community_rules_banned_hint">Estas claves públicas no pueden publicar en la comunidad.</string>
|
||||
<string name="new_community_rules_banned_add">Agregar un usuario bloqueado</string>
|
||||
<string name="new_community_rules_banned_placeholder">Nombre, npub, o NIP-05</string>
|
||||
<string name="new_community_rules_wot_section">Portal de red de confianza (opcional)</string>
|
||||
<string name="pictures">Imágenes</string>
|
||||
<string name="shorts">Cortos</string>
|
||||
<string name="longs">Videos</string>
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
<string name="show_anyway">Mostrar de todos modos</string>
|
||||
<string name="post_was_hidden">Esta publicación se ocultó porque menciona tus usuarios o palabras ocultas</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">La publicación fue reportada por</string>
|
||||
<plurals name="post_was_hidden_due_to_too_many_hashtags">
|
||||
<item quantity="one">Esta publicación tiene más de %1$d hashtag</item>
|
||||
<item quantity="other">Esta publicación tiene más de %1$d hashtags</item>
|
||||
</plurals>
|
||||
<string name="post_not_found">El evento se está cargando o no se puede encontrar en la lista de relés</string>
|
||||
<string name="post_not_found_short">👀</string>
|
||||
<string name="channel_image">Imagen del canal</string>
|
||||
@@ -30,6 +34,7 @@
|
||||
<string name="copy_text">Copiar texto</string>
|
||||
<string name="copy_user_pubkey">Copiar ID del autor</string>
|
||||
<string name="copy_note_id">Copiar ID de la nota</string>
|
||||
<string name="copy_raw_json">Copiar JSON sin procesar</string>
|
||||
<string name="broadcast">Transmisión</string>
|
||||
<string name="timestamp_it">Poner marca de tiempo</string>
|
||||
<string name="timestamp_pending">Marca de tiempo: confirmaciones pendientes</string>
|
||||
@@ -49,6 +54,10 @@
|
||||
<string name="login_with_a_private_key_to_be_able_to_boost_posts">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder impulsar publicaciones</string>
|
||||
<string name="login_with_a_private_key_to_like_posts">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para indicar que te gustan las publicaciones.</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">No has configurado la cantidad de zaps. Mantén presionado el botón para cambiarla.</string>
|
||||
<string name="chat_zap_amount_suffix">zapeó %1$s sats</string>
|
||||
<string name="chat_zap_anonymous">Anónimo</string>
|
||||
<string name="chat_raid_is_raiding">está en una incursión</string>
|
||||
<string name="chat_clip_created_a_clip">creó un clip</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder enviar zaps.</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder seguir a otros usuarios.</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder dejar de seguir a otros usuarios.</string>
|
||||
@@ -152,6 +161,8 @@
|
||||
<string name="pronouns">Pronombres</string>
|
||||
<string name="ln_address">Dirección de Lightning</string>
|
||||
<string name="ln_url_outdated">URL de Lightning (obsoleta)</string>
|
||||
<string name="stale_relay_hint_label">Los relés de origen pueden ser obsoletos</string>
|
||||
<string name="stale_relay_hint_icon_description">Aviso de relé obsoleto</string>
|
||||
<string name="download_to_phone">Guardar en el teléfono</string>
|
||||
<string name="save_to_gallery">Guardar en la galería</string>
|
||||
<string name="image_saved_to_the_gallery">Imagen guardada en la galería</string>
|
||||
@@ -160,6 +171,8 @@
|
||||
<string name="failed_to_save_the_image">Error al guardar la imagen</string>
|
||||
<string name="video_saved_to_the_gallery">Video guardado en la galería de videos del teléfono</string>
|
||||
<string name="failed_to_save_the_video">Error al guardar el video</string>
|
||||
<string name="pdf_saved_to_the_gallery">PDF guardado en Descargas/Amethyst</string>
|
||||
<string name="failed_to_save_the_pdf">Error al guardar el PDF</string>
|
||||
<string name="upload_image">Subir imagen</string>
|
||||
<string name="upload_file">Subir archivo</string>
|
||||
<string name="take_a_picture">Tomar una foto</string>
|
||||
@@ -266,6 +279,8 @@
|
||||
Son ideales para comunidades abiertas en torno a temas específicos. Algunos de estos grupos son efímeros
|
||||
y, por lo tanto, los mensajes del chat desaparecen con el tiempo</string>
|
||||
<string name="public_chat">Chat público</string>
|
||||
<string name="marmot_group">Grupo MLS</string>
|
||||
<string name="marmot_group_no_messages_yet">Aún no hay mensajes</string>
|
||||
<string name="public_chat_title">Metadatos públicos del chat</string>
|
||||
<string name="public_chat_explainer">Los chats públicos son visibles para todos en Nostr y cualquiera
|
||||
puede participar en ellos. Son ideales para comunidades abiertas alrededor de temas específicos.
|
||||
@@ -330,6 +345,11 @@
|
||||
<string name="private_conversation_notification">"<No se pudo desencriptar el mensaje privado>\n\nTe citaron en una conversación privada o encriptada entre %1$s y %2$s."</string>
|
||||
<string name="account_switch_add_account_dialog_title">Agregar cuenta nueva</string>
|
||||
<string name="drawer_accounts">Cuentas</string>
|
||||
<string name="drawer_section_navigate">Navegar</string>
|
||||
<string name="drawer_section_you">Tú</string>
|
||||
<string name="drawer_section_feeds">Tablones</string>
|
||||
<string name="drawer_section_create">Crear</string>
|
||||
<string name="drawer_section_system">Sistema</string>
|
||||
<string name="account_switch_select_account">Seleccionar cuenta</string>
|
||||
<string name="account_switch_add_account_btn">Agregar cuenta nueva</string>
|
||||
<string name="account_switch_active_account">Cuenta activa</string>
|
||||
@@ -352,6 +372,8 @@
|
||||
<string name="quick_action_block_dialog_btn">Bloquear</string>
|
||||
<string name="quick_action_delete_dialog_btn">Eliminar</string>
|
||||
<string name="quick_action_block">Bloquear</string>
|
||||
<string name="quick_action_mute_thread">Silenciar hilo</string>
|
||||
<string name="quick_action_unmute_thread">Reactivar hilo</string>
|
||||
<string name="quick_action_report">Reportar</string>
|
||||
<string name="quick_action_delete_button">Eliminar</string>
|
||||
<string name="quick_action_dont_show_again_button">No mostrar de nuevo</string>
|
||||
@@ -381,9 +403,49 @@
|
||||
<string name="migrate_bookmarks_button">Mover todo a marcadores nuevos</string>
|
||||
<string name="migrate_bookmarks_success">Los marcadores se migraron correctamente</string>
|
||||
<string name="drafts">Borradores</string>
|
||||
<string name="scheduled_posts">Publicaciones programadas</string>
|
||||
<string name="schedule_post">Programar</string>
|
||||
<string name="schedule_post_time_label">Hora programada</string>
|
||||
<string name="schedule_post_helper">Las publicaciones se realizan en ~15 minutos a partir de la hora programada.</string>
|
||||
<string name="schedule_post_pick_time">Elegir hora programada</string>
|
||||
<string name="schedule_post_pick_label">Programación para…</string>
|
||||
<string name="schedule_post_publishes_in">Se publica en %1$s</string>
|
||||
<string name="schedule_post_was_due">Se venció hace %1$s</string>
|
||||
<string name="schedule_post_picker_time_title">Hora</string>
|
||||
<string name="schedule_post_button_add">Programar publicación</string>
|
||||
<string name="schedule_post_button_remove">Cancelar programación</string>
|
||||
<string name="schedule_post_warning_title">Las notificaciones siempre activas están deshabilitadas</string>
|
||||
<string name="schedule_post_warning_single">Las publicaciones programadas pueden no realizarse hasta que vuelvas a abrir la aplicación. Habilita la activación en \"Configuración → Preferencias de interfaz de usuario\" para garantizar una programación en segundo plano fiable.</string>
|
||||
<string name="schedule_post_warning_multi">Las publicaciones programadas pueden no realizarse hasta que vuelvas a abrir la aplicación. Las publicaciones programadas de otras cuentas no se activarán mientras esta cuenta esté activa. Habilita la activación en \"Configuración → Preferencias de interfaz de usuario\" para garantizar una programación en segundo plano fiable.</string>
|
||||
<string name="schedule_post_preset_in_one_hour">En 1 hora</string>
|
||||
<string name="schedule_post_preset_tomorrow_morning">Mañana a las 9 a. m.</string>
|
||||
<string name="schedule_post_preset_next_monday_morning">El próximo lunes a las 9 a. m.</string>
|
||||
<string name="schedule_post_always_on_prompt_title">¿Habilitar notificaciones siempre activas?</string>
|
||||
<string name="schedule_post_always_on_prompt_message">Las publicaciones programadas solo se realizan de forma fiable cuando las notificaciones siempre activas están habilitadas. De lo contrario, es posible que no se activen hasta que vuelvas a abrir la app.</string>
|
||||
<string name="schedule_post_always_on_prompt_open_settings">Abrir configuración</string>
|
||||
<string name="schedule_post_always_on_prompt_continue">Continuar de todos modos</string>
|
||||
<string name="scheduled_posts_at_time">%1$s · en %2$s</string>
|
||||
<string name="scheduled_posts_at_time_past">%1$s · hace %2$s</string>
|
||||
<string name="scheduled_posts_day_tomorrow">Mañana</string>
|
||||
<string name="scheduled_posts_logout_toast_zero">Sesión cerrada</string>
|
||||
<plurals name="scheduled_posts_logout_toast">
|
||||
<item quantity="one">Sesión cerrada · %d publicación programada eliminada</item>
|
||||
<item quantity="other">Sesión cerrada · %d publicaciones programadas eliminadas</item>
|
||||
</plurals>
|
||||
<string name="scheduled_posts_notification_sent_title">Se realizó la publicación programada</string>
|
||||
<string name="scheduled_posts_notification_failed_title">Fallo en la publicación programada</string>
|
||||
<string name="app_notification_scheduled_posts_channel_name">Publicaciones programadas</string>
|
||||
<string name="app_notification_scheduled_posts_channel_description">Notificaciones cuando una publicación programada se realiza o no.</string>
|
||||
<string name="scheduled_posts_action_send_now">Enviar ahora</string>
|
||||
<string name="polls">Encuestas</string>
|
||||
<string name="open_polls">Abiertas</string>
|
||||
<string name="closed_polls">Cerradas</string>
|
||||
<string name="new_community_rules_limits_save">Guardar límites</string>
|
||||
<string name="new_community_rules_banned_section">Usuarios bloqueados</string>
|
||||
<string name="new_community_rules_banned_hint">Estas claves públicas no pueden publicar en la comunidad.</string>
|
||||
<string name="new_community_rules_banned_add">Agregar un usuario bloqueado</string>
|
||||
<string name="new_community_rules_banned_placeholder">Nombre, npub, o NIP-05</string>
|
||||
<string name="new_community_rules_wot_section">Portal de red de confianza (opcional)</string>
|
||||
<string name="pictures">Imágenes</string>
|
||||
<string name="shorts">Cortos</string>
|
||||
<string name="longs">Videos</string>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<string name="post_not_found_short">👀</string>
|
||||
<string name="channel_image">Csatorna profilképe</string>
|
||||
<string name="referenced_event_not_found">A hivatkozott esemény nem található</string>
|
||||
<string name="could_not_decrypt_the_message">Nem sikerült az üzenetet visszafejteni</string>
|
||||
<string name="could_not_decrypt_the_message">Nem sikerült visszafejteni az üzenetet</string>
|
||||
<string name="group_picture">Csoport profilképe</string>
|
||||
<string name="explicit_content">Szókimondó tartalom</string>
|
||||
<string name="relay_notice">Átjátszóval kapcsolatos megjegyzések</string>
|
||||
@@ -785,6 +785,10 @@
|
||||
<string name="wallet_connect_manual_config">Speciális: kapcsolat részleteinek kézi megadása</string>
|
||||
<string name="quick_zap_amounts">Gyors Zap-összegek</string>
|
||||
<string name="quick_zap_amounts_explainer">A zap gomb megnyomásakor jelenik meg. Érintse meg az összeget, hogy eltávolítsa. Ha üresen hagyja, akkor minden alkalommal megnyílik a párbeszédablak az összeg beírásához.</string>
|
||||
<string name="quick_zap_amounts_onchain">Gyors láncon belüli Zap összegek</string>
|
||||
<string name="quick_zap_amounts_onchain_explainer">A láncon belüli Zapek bányászdíjat fizetnek, ezért az összegek általában nagyobbak, mint Lightning esetén. Koppintson egy összegre a eltávolításához.</string>
|
||||
<string name="new_amount_in_sats_onchain">Új láncon belüli összeg Satoshiban</string>
|
||||
<string name="send_onchain_instead">Küldés inkább láncon belül</string>
|
||||
<string name="zap_privacy_section">Zap adatvédelem</string>
|
||||
<string name="zap_type_section_explainer">Állítsa be, hogy hogyan jelenjen meg a személyazonossága a zap elküldésekor.</string>
|
||||
<string name="wallet_connect_connect_app">Pénztárca összekapcsolása</string>
|
||||
@@ -1454,8 +1458,8 @@
|
||||
<string name="automatically_show_profile_picture">Profilkép</string>
|
||||
<string name="automatically_show_profile_picture_description">Profilképek megjelenítése</string>
|
||||
<string name="select_an_option">Válasszon egy lehetőséget</string>
|
||||
<string name="error_dialog_pay_invoice_error">Sikertelen számla-kifizetés</string>
|
||||
<string name="error_dialog_pay_withdraw_error">Sikertelen pénzkivétel a pénztárcából</string>
|
||||
<string name="error_dialog_pay_invoice_error">Nem sikerült kifizetni a számlát</string>
|
||||
<string name="error_dialog_pay_withdraw_error">Nem sikerült a pénzkivétel a pénztárcából</string>
|
||||
<string name="error_parsing_nip47_title">Nem sikerült beállítani a Wallet Connectet</string>
|
||||
<string name="error_parsing_nip47">Hiba a NIP-47 kapcsolati karakterlánc elemzésekor. Ellenőrizze, hogy a pénztárca-szolgáltatónál a következő helyes-e: %1$s. Hiba: %2$s</string>
|
||||
<string name="error_parsing_nip47_no_error">Hiba a NIP-47 kapcsolati karakterlánc elemzésekor. Ellenőrizze, hogy a pénztárca-szolgáltatónál a következő helyes-e: %1$s.</string>
|
||||
@@ -1653,6 +1657,11 @@
|
||||
<string name="wallet_onchain_no_address">Ehhez a fiókhoz nincs elérhető láncon belüli cím.</string>
|
||||
<string name="wallet_onchain_no_backend">Nincs beállítva lánc-háttérprogram.</string>
|
||||
<string name="wallet_onchain_pending">Függőben</string>
|
||||
<string name="wallet_onchain_public_chip">Nyilvános</string>
|
||||
<string name="wallet_onchain_public_dialog_title">Ez a pénztárca nyilvános</string>
|
||||
<string name="wallet_onchain_public_dialog_body">A Taproot-cím az Ön Nostr nyilvános kulcsából van levezetve, ezért bárki, aki ismeri az npub-ot, láthatja a pénztárca egyenlegét és tranzakciótörténetét a blokkláncon.\n\nAz adatvédelem megőrzése érdekében ezt a pénztárcát kizárólag nem privát számlákról, például kriptovaluta-váltókról töltse fel és ürítse ki. Soha ne keverje ezeket az összegeket a hideg pénztárcákkal, és olyan pénzként kezelje őket, amelyet elveszíthet, mivel bárki, aki hozzáfér az nsec-hez, elköltheti.</string>
|
||||
<string name="wallet_onchain_public_dialog_confirm">Megértettem</string>
|
||||
<string name="wallet_onchain_copy_dialog_confirm">Cím másolása</string>
|
||||
<string name="route_security_filters">Biztonsági szűrők</string>
|
||||
<string name="route_import_follows">Követettek importálása</string>
|
||||
<string name="new_post">Új bejegyzés</string>
|
||||
@@ -1716,6 +1725,7 @@
|
||||
<item quantity="one">%1$s, %2$d esemény</item>
|
||||
<item quantity="other">%1$s, %2$d esemény</item>
|
||||
</plurals>
|
||||
<string name="calendar_day_a11y_no_events">%1$s, nincsenek események</string>
|
||||
<string name="calendar_day_a11y_today_suffix">ma</string>
|
||||
<string name="calendar_day_a11y_selected_suffix">kiválasztva</string>
|
||||
<string name="calendar_fab_new_event">Új naptáresemény létrehozása</string>
|
||||
@@ -1729,6 +1739,10 @@
|
||||
<string name="calendar_collection_title">Cím</string>
|
||||
<string name="calendar_collection_description">Leírás</string>
|
||||
<string name="calendar_collection_invalid">A cím megadása kötelező.</string>
|
||||
<plurals name="calendar_collection_count">
|
||||
<item quantity="one">%1$d esemény</item>
|
||||
<item quantity="other">%1$d esemény</item>
|
||||
</plurals>
|
||||
<string name="calendar_collection_empty_members">Ebben a naptárban még nincsenek események.</string>
|
||||
<string name="calendar_rsvp_going">Ott leszek</string>
|
||||
<string name="calendar_rsvp_maybe">Talán</string>
|
||||
@@ -1747,6 +1761,10 @@
|
||||
<string name="calendar_reminder_channel_name">Naptár-emlékeztetők</string>
|
||||
<string name="calendar_reminder_channel_description">Értesítés, ha egy esemény, amin Ön részt vesz, hamarosan elkezdődik.</string>
|
||||
<string name="calendar_reminder_default_title">Naptáresemény</string>
|
||||
<plurals name="calendar_reminder_body">
|
||||
<item quantity="one">%1$d perc múlva kezdődik</item>
|
||||
<item quantity="other">%1$d perc múlva kezdődik</item>
|
||||
</plurals>
|
||||
<string name="calendar_add_to_calendar_action">Vegye fel az egyik naptárba</string>
|
||||
<string name="calendar_add_to_calendar_title">Hozzáadás egy naptárhoz</string>
|
||||
<string name="calendar_add_to_calendar_none">Ön még nem hozott létre egyetlen naptárat sem.</string>
|
||||
@@ -2153,6 +2171,9 @@
|
||||
<string name="remove_user_from_the_list">Felhasználó törlése a listáról</string>
|
||||
<string name="follow_list_item_label">Követési csomag</string>
|
||||
<string name="members">Tagok</string>
|
||||
<string name="contact_list_containing_label">A követési lista %1$d felhasználót tartalmaz:</string>
|
||||
<string name="contact_list_screen_title">Követési lista</string>
|
||||
<string name="contact_list_screen_title_with_count">Követési lista (%1$d)</string>
|
||||
<string name="people_list_title">Követési lista metaadatai</string>
|
||||
<string name="people_list_explainer">A követési listák metaadatai a Nostr-on bárki számára láthatók. Csak a privát tagok adatai vannak titkosítva.</string>
|
||||
<string name="follow_pack_title">Követési csomag metaadatai</string>
|
||||
|
||||
@@ -797,6 +797,10 @@ Zaplanowane posty z innych kont nie zostaną opublikowane, dopóki to konto jest
|
||||
<string name="wallet_connect_manual_config">Zaawansowane: wprowadź szczegóły połączenia ręcznie</string>
|
||||
<string name="quick_zap_amounts">Kwoty Quick Zap</string>
|
||||
<string name="quick_zap_amounts_explainer">Wyświetlane po naciśnięciu przycisku zap. Dotknij kwoty, aby ją usunąć. Jeśli pozostawisz to pole puste, za każdym razem otworzy się okno dialogowe umożliwiające wprowadzenie kwoty.</string>
|
||||
<string name="quick_zap_amounts_onchain">Szybkie kwoty zapoów On-chain</string>
|
||||
<string name="quick_zap_amounts_onchain_explainer">Transakcje on-chain są obciążone opłatami minerów, więc kwoty są zazwyczaj wyższe niż w sieci Lightning. Kliknij kwotę, aby ją usunąć.</string>
|
||||
<string name="new_amount_in_sats_onchain">Nowa kwota on-chain w satoszach</string>
|
||||
<string name="send_onchain_instead">Zamiast tego wyślij on-chain</string>
|
||||
<string name="zap_privacy_section">Prywatność Zap</string>
|
||||
<string name="zap_type_section_explainer">Kontroluje sposób wyświetlania Twojej tożsamości podczas wysyłania zapa.</string>
|
||||
<string name="wallet_connect_connect_app">Podłącz portfel</string>
|
||||
@@ -1669,6 +1673,7 @@ Zaplanowane posty z innych kont nie zostaną opublikowane, dopóki to konto jest
|
||||
<string name="wallet_onchain_public_dialog_title">Ten portfel jest publiczny</string>
|
||||
<string name="wallet_onchain_public_dialog_body">Twój adres Taproot jest generowany na podstawie Twojego klucza publicznego Nostr, więc każdy, kto zna Twój klucz npub, może sprawdzić saldo tego portfela oraz historię transakcji w łańcuchu bloków.\n\nAby zachować prywatność, zasilaj ten portfel i wypłacaj z niego środki wyłącznie z kont publicznych, takich jak giełdy. Nigdy nie mieszaj tych środków ze środkami z portfeli offline i traktuj je jako pieniądze, które możesz stracić, ponieważ każdy, kto ma dostęp do Twojego klucza nsec, może je wydać.</string>
|
||||
<string name="wallet_onchain_public_dialog_confirm">Rozumiem</string>
|
||||
<string name="wallet_onchain_copy_dialog_confirm">Skopiuj adres</string>
|
||||
<string name="route_security_filters">Filtry bezpieczeństwa</string>
|
||||
<string name="route_import_follows">Import Obserwowanych</string>
|
||||
<string name="new_post">Nowy post</string>
|
||||
@@ -1734,6 +1739,7 @@ Zaplanowane posty z innych kont nie zostaną opublikowane, dopóki to konto jest
|
||||
<item quantity="many">%1$s, wydarzeń %2$d</item>
|
||||
<item quantity="other">%1$s, %2$d wydarzeń</item>
|
||||
</plurals>
|
||||
<string name="calendar_day_a11y_no_events">%1$s, brak wydarzeń</string>
|
||||
<string name="calendar_day_a11y_today_suffix">dziś</string>
|
||||
<string name="calendar_day_a11y_selected_suffix">zaznaczone</string>
|
||||
<string name="calendar_fab_new_event">Utwórz nowe wydarzenie kalendarza</string>
|
||||
@@ -2173,6 +2179,9 @@ Zaplanowane posty z innych kont nie zostaną opublikowane, dopóki to konto jest
|
||||
<string name="remove_user_from_the_list">Usuń użytkownika z listy</string>
|
||||
<string name="follow_list_item_label">Kategoria profili</string>
|
||||
<string name="members">Uczestnicy</string>
|
||||
<string name="contact_list_containing_label">Lista obserwowanych zawiera %1$d użytkownika(ów):</string>
|
||||
<string name="contact_list_screen_title">Lista obserwowanych</string>
|
||||
<string name="contact_list_screen_title_with_count">Lista obserwowanych (%1$d)</string>
|
||||
<string name="people_list_title">Metadane listy profili</string>
|
||||
<string name="people_list_explainer">Metadane listy obserwowanych są widoczne dla wszystkich użytkowników w sieci Nostr. Tylko Twoi uczestnicy prywatni są zaszyfrowani.</string>
|
||||
<string name="follow_pack_title">Metadane pakietu subskrybentów</string>
|
||||
|
||||
@@ -550,6 +550,7 @@
|
||||
<string name="profile_badges_description">Escolha quais dos selos que você recebeu aparecerão no seu perfil.</string>
|
||||
<string name="profile_badges_empty">Você ainda não recebeu nenhum selo.</string>
|
||||
<string name="pictures">Imagens</string>
|
||||
<string name="calendars">Calendários</string>
|
||||
<string name="shorts">Curtas</string>
|
||||
<string name="public_chats">Chats públicos</string>
|
||||
<string name="follow_packs">Pacotes de seguidos</string>
|
||||
@@ -782,6 +783,10 @@
|
||||
<string name="wallet_connect_manual_config">Avançado: inserir detalhes de conexão manualmente</string>
|
||||
<string name="quick_zap_amounts">Valores Rápidos de Zap</string>
|
||||
<string name="quick_zap_amounts_explainer">Exibido ao pressionar o botão de zap. Toque em um valor para removê-lo. Se deixar vazio, abrirá o diálogo para inserir um valor todas as vezes.</string>
|
||||
<string name="quick_zap_amounts_onchain">Valores rápidos de zap on-chain</string>
|
||||
<string name="quick_zap_amounts_onchain_explainer">Zaps on-chain pagam taxas de mineradores, por isso os valores costumam ser maiores que no Lightning. Toque num valor para removê-lo.</string>
|
||||
<string name="new_amount_in_sats_onchain">Novo valor on-chain em sats</string>
|
||||
<string name="send_onchain_instead">Enviar on-chain em vez disso</string>
|
||||
<string name="zap_privacy_section">Privacidade do Zap</string>
|
||||
<string name="zap_type_section_explainer">Controla como sua identidade é exibida ao enviar um Zap.</string>
|
||||
<string name="wallet_connect_connect_app">Conectar Carteira</string>
|
||||
@@ -1592,6 +1597,8 @@
|
||||
<string name="route_global">Global</string>
|
||||
<string name="route_video">Vídeos Curtos</string>
|
||||
<string name="route_pictures">Imagens</string>
|
||||
<string name="route_calendars">Calendários</string>
|
||||
<string name="route_calendar_collections">Listas de calendário</string>
|
||||
<string name="route_chess">Xadrez</string>
|
||||
<string name="wallet">Carteira</string>
|
||||
<string name="wallet_balance">Saldo</string>
|
||||
@@ -1612,6 +1619,7 @@
|
||||
<string name="wallet_creating_invoice">Criando fatura…</string>
|
||||
<string name="wallet_copy_invoice">Copiar Fatura</string>
|
||||
<string name="wallet_no_transactions">Nenhuma transação ainda</string>
|
||||
<string name="wallet_no_transactions_for_filter">Nenhuma transação corresponde a este filtro</string>
|
||||
<string name="wallet_loading">Carregando…</string>
|
||||
<string name="wallet_incoming">Recebido</string>
|
||||
<string name="wallet_outgoing">Enviado</string>
|
||||
@@ -1640,6 +1648,15 @@
|
||||
<string name="wallet_invalid_uri">URI de conexão NWC inválida</string>
|
||||
<string name="wallet_move_up">Mover para cima</string>
|
||||
<string name="wallet_move_down">Mover para baixo</string>
|
||||
<string name="wallet_onchain_transactions">Transações on-chain</string>
|
||||
<string name="wallet_onchain_no_address">Nenhum endereço on-chain disponível para esta conta.</string>
|
||||
<string name="wallet_onchain_no_backend">Nenhum backend on-chain configurado.</string>
|
||||
<string name="wallet_onchain_pending">Pendente</string>
|
||||
<string name="wallet_onchain_public_chip">Pública</string>
|
||||
<string name="wallet_onchain_public_dialog_title">Esta carteira é pública</string>
|
||||
<string name="wallet_onchain_public_dialog_body">Seu endereço Taproot é derivado da sua chave pública Nostr, então qualquer pessoa que conheça seu npub pode ver o saldo e o histórico de transações desta carteira no blockchain.\n\nPara preservar sua privacidade, abasteça esta carteira a partir de contas não privadas (como corretoras) e envie os fundos de volta para elas. Nunca misture esses fundos com suas carteiras frias e trate-os como dinheiro que você pode perder, pois qualquer um que controle seu nsec pode gastar os fundos.</string>
|
||||
<string name="wallet_onchain_public_dialog_confirm">Entendi</string>
|
||||
<string name="wallet_onchain_copy_dialog_confirm">Copiar endereço</string>
|
||||
<string name="route_security_filters">Filtros de Segurança</string>
|
||||
<string name="route_import_follows">Importar Seguidos</string>
|
||||
<string name="new_post">Novo Post</string>
|
||||
@@ -1652,6 +1669,120 @@
|
||||
<string name="new_zap_poll">Nova Enquete Zap</string>
|
||||
<string name="new_regular_poll">Nova Enquete Regular</string>
|
||||
<string name="new_picture">Nova Foto</string>
|
||||
<string name="new_calendar_event">Novo evento de calendário</string>
|
||||
<string name="edit_calendar_event">Editar evento de calendário</string>
|
||||
<string name="calendar_event_all_day_locked">Bloqueado durante edição — alterar isto criaria um novo evento.</string>
|
||||
<string name="new_calendar_collection">Novo calendário</string>
|
||||
<string name="edit_calendar_collection">Editar calendário</string>
|
||||
<string name="calendar_collection_events_section">Eventos neste calendário (%1$d)</string>
|
||||
<string name="calendar_collection_no_events_yet">Você ainda não criou nenhum evento de calendário.</string>
|
||||
<string name="calendar_view_month">Mês</string>
|
||||
<string name="calendar_view_week">Semana</string>
|
||||
<string name="calendar_view_day">Dia</string>
|
||||
<string name="calendar_section_upcoming">Próximos</string>
|
||||
<string name="calendar_section_past">Passados</string>
|
||||
<string name="calendar_empty_feed">Ainda não há eventos de calendário (futuros ou passados) do feed selecionado.</string>
|
||||
<string name="calendar_empty_collections">Ainda não há listas de calendário.</string>
|
||||
<string name="calendar_empty_feed_title">Seu calendário está vazio</string>
|
||||
<string name="calendar_empty_feed_subtitle">Eventos compartilhados por pessoas que você segue aparecem aqui. Toque no botão + para criar os seus.</string>
|
||||
<string name="calendar_empty_collections_title">Ainda não há listas</string>
|
||||
<string name="calendar_empty_collections_subtitle">Agrupe eventos — uma série de encontros, uma trilha de conferência, o roteiro da sua equipe. Toque em + para criar uma.</string>
|
||||
<string name="calendar_empty_day_title">Nada agendado</string>
|
||||
<string name="calendar_empty_day_subtitle">Sem eventos neste dia. Toque em + para adicionar um.</string>
|
||||
<string name="calendar_empty_week_title">Nada esta semana</string>
|
||||
<string name="calendar_empty_week_subtitle">Nenhum evento nesta semana.</string>
|
||||
<string name="calendar_event_title">Título</string>
|
||||
<string name="calendar_event_summary">Resumo</string>
|
||||
<string name="calendar_event_location">Local</string>
|
||||
<string name="calendar_event_image">URL da imagem</string>
|
||||
<string name="calendar_event_all_day">Evento de dia inteiro</string>
|
||||
<string name="calendar_event_start">Começa</string>
|
||||
<string name="calendar_event_end">Termina</string>
|
||||
<string name="calendar_event_hashtags">Hashtags (separadas por vírgula)</string>
|
||||
<string name="calendar_event_pick_date">Escolher data</string>
|
||||
<string name="calendar_event_pick_time">Escolher hora</string>
|
||||
<string name="calendar_event_invalid">Título e início são obrigatórios.</string>
|
||||
<string name="calendar_event_end_before_start">O término deve ser após o início.</string>
|
||||
<string name="calendar_nav_previous_month">Mês anterior</string>
|
||||
<string name="calendar_nav_next_month">Próximo mês</string>
|
||||
<string name="calendar_nav_previous_week">Semana anterior</string>
|
||||
<string name="calendar_nav_next_week">Próxima semana</string>
|
||||
<string name="calendar_nav_previous_day">Dia anterior</string>
|
||||
<string name="calendar_nav_next_day">Próximo dia</string>
|
||||
<string name="calendar_no_events_today">Sem eventos neste dia</string>
|
||||
<string name="calendar_no_events">Sem eventos</string>
|
||||
<string name="calendar_continues">Continua</string>
|
||||
<string name="calendar_day_of_total">Dia %1$d de %2$d</string>
|
||||
<string name="calendar_add_to_phone_calendar">Adicionar ao calendário do telefone</string>
|
||||
<string name="calendar_nav_jump_to_today">Ir para hoje</string>
|
||||
<plurals name="calendar_day_a11y_events">
|
||||
<item quantity="one">%1$s, %2$d evento</item>
|
||||
<item quantity="other">%1$s, %2$d eventos</item>
|
||||
</plurals>
|
||||
<string name="calendar_day_a11y_no_events">%1$s, sem eventos</string>
|
||||
<string name="calendar_day_a11y_today_suffix">hoje</string>
|
||||
<string name="calendar_day_a11y_selected_suffix">selecionado</string>
|
||||
<string name="calendar_fab_new_event">Criar um novo evento de calendário</string>
|
||||
<string name="calendar_fab_new_collection">Criar um novo calendário</string>
|
||||
<string name="calendar_fab_toggle">Mostrar opções de criação</string>
|
||||
<string name="calendar_untitled">(sem título)</string>
|
||||
<string name="calendar_all_day">Dia inteiro</string>
|
||||
<string name="calendar_rsvp_going_prefixed">✓ Vou</string>
|
||||
<string name="calendar_rsvp_maybe_prefixed">\? Talvez</string>
|
||||
<string name="calendar_rsvp_not_going_prefixed">✗ Não vou</string>
|
||||
<string name="calendar_collection_title">Título</string>
|
||||
<string name="calendar_collection_description">Descrição</string>
|
||||
<string name="calendar_collection_invalid">Um título é obrigatório.</string>
|
||||
<plurals name="calendar_collection_count">
|
||||
<item quantity="one">%1$d evento</item>
|
||||
<item quantity="other">%1$d eventos</item>
|
||||
</plurals>
|
||||
<string name="calendar_collection_empty_members">Nenhum evento neste calendário ainda.</string>
|
||||
<string name="calendar_rsvp_going">Vou</string>
|
||||
<string name="calendar_rsvp_maybe">Talvez</string>
|
||||
<string name="calendar_rsvp_not_going">Não vou</string>
|
||||
<string name="calendar_rsvp_none">Sem RSVPs ainda.</string>
|
||||
<string name="calendar_participants_section">Participantes (%1$d)</string>
|
||||
<string name="calendar_event_in_calendars">Em calendários (%1$d)</string>
|
||||
<string name="calendar_event_in_no_calendars">Ainda não faz parte de nenhum calendário.</string>
|
||||
<string name="calendar_event_loading">Carregando evento…</string>
|
||||
<string name="calendar_relative_ongoing">Acontecendo agora</string>
|
||||
<string name="calendar_relative_ongoing_with_end">%1$s · termina %2$s</string>
|
||||
<string name="calendar_export_share_title">Compartilhar evento de calendário</string>
|
||||
<string name="calendar_export_event">Exportar para calendário (.ics)</string>
|
||||
<string name="calendar_reminder_channel_name">Lembretes de calendário</string>
|
||||
<string name="calendar_reminder_channel_description">Aviso quando um evento do qual você participa está prestes a começar.</string>
|
||||
<string name="calendar_reminder_default_title">Evento de calendário</string>
|
||||
<plurals name="calendar_reminder_body">
|
||||
<item quantity="one">Começa em %1$d minuto</item>
|
||||
<item quantity="other">Começa em %1$d minutos</item>
|
||||
</plurals>
|
||||
<string name="calendar_add_to_calendar_action">Adicionar a um dos seus calendários</string>
|
||||
<string name="calendar_add_to_calendar_title">Adicionar a um calendário</string>
|
||||
<string name="calendar_add_to_calendar_none">Você ainda não criou nenhum calendário.</string>
|
||||
<string name="calendar_collection_delete">Excluir calendário</string>
|
||||
<string name="calendar_collection_delete_confirm_title">Excluir este calendário?</string>
|
||||
<string name="calendar_collection_delete_confirm_message">O calendário será removido. Os eventos dentro dele não serão excluídos.</string>
|
||||
<string name="calendar_event_pick_image">Escolher imagem</string>
|
||||
<string name="calendar_event_image_upload_failed">Falha no envio da imagem</string>
|
||||
<string name="calendar_event_image_upload_failed_body">A imagem escolhida não pôde ser enviada. Tente novamente ou cole uma URL.</string>
|
||||
<string name="calendar_event_participants_section">Participantes (%1$d)</string>
|
||||
<string name="calendar_event_participant_input">Buscar nome, npub ou nip-05</string>
|
||||
<string name="calendar_event_participant_invalid">Insira um npub… válido ou pubkey hex de 64 caracteres.</string>
|
||||
<string name="calendar_event_participant_remove">Remover participante</string>
|
||||
<string name="calendar_reminder_settings_title">Lembretes de calendário</string>
|
||||
<string name="calendar_reminder_settings_enabled_title">Enviar lembretes</string>
|
||||
<string name="calendar_reminder_settings_enabled_subtitle">Uma notificação é disparada quando um evento do qual você participa está prestes a começar.</string>
|
||||
<string name="calendar_reminder_settings_lead_title">Antecedência do lembrete</string>
|
||||
<string name="calendar_reminder_settings_lead_subtitle">Quantos minutos antes do evento você quer ser notificado.</string>
|
||||
<string name="calendar_share_nostr">Compartilhar como link Nostr</string>
|
||||
<string name="calendar_share_nostr_title">Compartilhar link do calendário</string>
|
||||
<string name="calendar_filter_all">Todos os calendários</string>
|
||||
<string name="calendar_filter_sheet_title">Mostrar eventos de…</string>
|
||||
<string name="calendar_filter_no_calendars">Você ainda não criou nenhum calendário.</string>
|
||||
<string name="calendar_open_in_maps">Abrir no mapa</string>
|
||||
<string name="calendar_open_link">Abrir link</string>
|
||||
<string name="route_calendar_event_detail">Detalhes do evento</string>
|
||||
<string name="new_short_video">Novo Vídeo Curto</string>
|
||||
<string name="new_long_video">Novo Vídeo Longo</string>
|
||||
<string name="article_title">Título</string>
|
||||
@@ -1675,6 +1806,8 @@
|
||||
<string name="boost_or_quote_description">Impulsionar ou Citar</string>
|
||||
<string name="like_description">Gostar</string>
|
||||
<string name="zap_description">Zap</string>
|
||||
<string name="onchain_zap_description">Zap Bitcoin on-chain</string>
|
||||
<string name="onchain_zap_pending">Aguardando confirmação</string>
|
||||
<string name="change_reaction">Mudar reações rápidas</string>
|
||||
<string name="bottom_bar_settings">Barra de navegação inferior</string>
|
||||
<string name="bottom_bar_settings_description">Arraste para reordenar. Alterne para adicionar ou remover um item da barra inferior. Com zero itens, a barra inferior fica oculta.</string>
|
||||
@@ -2024,6 +2157,9 @@
|
||||
<string name="remove_user_from_the_list">Remover usuário da lista</string>
|
||||
<string name="follow_list_item_label">Pacote Seguir</string>
|
||||
<string name="members">Membros</string>
|
||||
<string name="contact_list_containing_label">Lista de seguidos contendo %1$d usuário(s):</string>
|
||||
<string name="contact_list_screen_title">Lista de seguidos</string>
|
||||
<string name="contact_list_screen_title_with_count">Lista de seguidos (%1$d)</string>
|
||||
<string name="people_list_title">Metadados da lista de seguidores</string>
|
||||
<string name="people_list_explainer">Os metadados das listas de seguidores podem ser vistos por qualquer pessoa no Nostr. Apenas os membros privados são criptografados.</string>
|
||||
<string name="follow_pack_title">Metadados do pacote de recomendações</string>
|
||||
|
||||
@@ -550,6 +550,7 @@
|
||||
<string name="profile_badges_description">Välj vilka av märkena du fått som ska visas på din profil.</string>
|
||||
<string name="profile_badges_empty">Du har inte fått några märken ännu.</string>
|
||||
<string name="pictures">Bilder</string>
|
||||
<string name="calendars">Kalendrar</string>
|
||||
<string name="shorts">Kortfilmer</string>
|
||||
<string name="public_chats">Offentliga chattar</string>
|
||||
<string name="follow_packs">Följpaket</string>
|
||||
@@ -782,6 +783,10 @@
|
||||
<string name="wallet_connect_manual_config">Avancerat: ange anslutningsinformation manuellt</string>
|
||||
<string name="quick_zap_amounts">Snabba Zap-belopp</string>
|
||||
<string name="quick_zap_amounts_explainer">Visas när du trycker på Zap-knappen. Tryck på ett belopp för att ta bort det. Om du lämnar det tomt öppnas en dialog för att ange ett belopp varje gång.</string>
|
||||
<string name="quick_zap_amounts_onchain">Snabba on-chain-zapbelopp</string>
|
||||
<string name="quick_zap_amounts_onchain_explainer">On-chain-zappar betalar miner-avgifter, så beloppen är vanligtvis större än för Lightning. Tryck på ett belopp för att ta bort det.</string>
|
||||
<string name="new_amount_in_sats_onchain">Nytt on-chain-belopp i sats</string>
|
||||
<string name="send_onchain_instead">Skicka on-chain istället</string>
|
||||
<string name="zap_privacy_section">Zap-sekretess</string>
|
||||
<string name="zap_type_section_explainer">Styr hur din identitet visas när du skickar ett Zap.</string>
|
||||
<string name="wallet_connect_connect_app">Anslut plånbok</string>
|
||||
@@ -1591,6 +1596,8 @@
|
||||
<string name="route_global">Globalt</string>
|
||||
<string name="route_video">Kortfilmer</string>
|
||||
<string name="route_pictures">Bilder</string>
|
||||
<string name="route_calendars">Kalendrar</string>
|
||||
<string name="route_calendar_collections">Kalenderlistor</string>
|
||||
<string name="route_chess">Schack</string>
|
||||
<string name="wallet">Plånbok</string>
|
||||
<string name="wallet_balance">Saldo</string>
|
||||
@@ -1611,6 +1618,7 @@
|
||||
<string name="wallet_creating_invoice">Skapar faktura…</string>
|
||||
<string name="wallet_copy_invoice">Kopiera faktura</string>
|
||||
<string name="wallet_no_transactions">Inga transaktioner ännu</string>
|
||||
<string name="wallet_no_transactions_for_filter">Inga transaktioner matchar detta filter</string>
|
||||
<string name="wallet_loading">Laddar…</string>
|
||||
<string name="wallet_incoming">Mottagen</string>
|
||||
<string name="wallet_outgoing">Skickad</string>
|
||||
@@ -1639,6 +1647,15 @@
|
||||
<string name="wallet_invalid_uri">Ogiltig URI för NWC-anslutning</string>
|
||||
<string name="wallet_move_up">Flytta upp</string>
|
||||
<string name="wallet_move_down">Flytta ner</string>
|
||||
<string name="wallet_onchain_transactions">On-chain-transaktioner</string>
|
||||
<string name="wallet_onchain_no_address">Ingen on-chain-adress tillgänglig för detta konto.</string>
|
||||
<string name="wallet_onchain_no_backend">Ingen on-chain-backend är konfigurerad.</string>
|
||||
<string name="wallet_onchain_pending">Väntar</string>
|
||||
<string name="wallet_onchain_public_chip">Offentlig</string>
|
||||
<string name="wallet_onchain_public_dialog_title">Denna plånbok är offentlig</string>
|
||||
<string name="wallet_onchain_public_dialog_body">Din Taproot-adress härleds från din publika Nostr-nyckel, så alla som känner till din npub kan se denna plånboks saldo och transaktionshistorik på blockchainen.\n\nFör att bevara din integritet, fyll på denna plånbok från icke-privata konton (som börser) och skicka tillbaka medel dit. Blanda aldrig dessa medel med dina kalla plånböcker, och behandla dem som pengar du kan förlora, eftersom alla som har kontroll över din nsec kan spendera medlen.</string>
|
||||
<string name="wallet_onchain_public_dialog_confirm">Uppfattat</string>
|
||||
<string name="wallet_onchain_copy_dialog_confirm">Kopiera adress</string>
|
||||
<string name="route_security_filters">Säkerhetsfilter</string>
|
||||
<string name="route_import_follows">Importera följare</string>
|
||||
<string name="new_post">Nytt inlägg</string>
|
||||
@@ -1651,6 +1668,122 @@
|
||||
<string name="new_zap_poll">Ny zap-omröstning</string>
|
||||
<string name="new_regular_poll">Ny vanlig omröstning</string>
|
||||
<string name="new_picture">Ny bild</string>
|
||||
<string name="new_calendar_event">Ny kalenderhändelse</string>
|
||||
<string name="edit_calendar_event">Redigera kalenderhändelse</string>
|
||||
<string name="calendar_event_all_day_locked">Låst under redigering — att ändra detta skulle skapa en ny händelse istället.</string>
|
||||
<string name="new_calendar_collection">Ny kalender</string>
|
||||
<string name="edit_calendar_collection">Redigera kalender</string>
|
||||
<string name="calendar_collection_events_section">Händelser i denna kalender (%1$d)</string>
|
||||
<string name="calendar_collection_no_events_yet">Du har inte skapat några kalenderhändelser än.</string>
|
||||
<string name="calendar_view_feed">Flöde</string>
|
||||
<string name="calendar_view_month">Månad</string>
|
||||
<string name="calendar_view_week">Vecka</string>
|
||||
<string name="calendar_view_day">Dag</string>
|
||||
<string name="calendar_section_upcoming">Kommande</string>
|
||||
<string name="calendar_section_past">Tidigare</string>
|
||||
<string name="calendar_empty_feed">Inga kommande eller tidigare kalenderhändelser från ditt valda flöde än.</string>
|
||||
<string name="calendar_empty_collections">Inga kalenderlistor än.</string>
|
||||
<string name="calendar_empty_feed_title">Din kalender är tom</string>
|
||||
<string name="calendar_empty_feed_subtitle">Händelser delade av personer du följer dyker upp här. Tryck på +-knappen för att skapa egna.</string>
|
||||
<string name="calendar_empty_collections_title">Inga listor än</string>
|
||||
<string name="calendar_empty_collections_subtitle">Gruppera händelser tillsammans — en träffserie, ett konferensspår, ditt teams färdplan. Tryck på + för att skapa en.</string>
|
||||
<string name="calendar_empty_day_title">Inget schemalagt</string>
|
||||
<string name="calendar_empty_day_subtitle">Inga händelser denna dag. Tryck på + för att lägga till en.</string>
|
||||
<string name="calendar_empty_week_title">Inget den här veckan</string>
|
||||
<string name="calendar_empty_week_subtitle">Inga händelser denna vecka.</string>
|
||||
<string name="calendar_event_title">Titel</string>
|
||||
<string name="calendar_event_summary">Sammanfattning</string>
|
||||
<string name="calendar_event_location">Plats</string>
|
||||
<string name="calendar_event_image">Bild-URL</string>
|
||||
<string name="calendar_event_all_day">Heldagshändelse</string>
|
||||
<string name="calendar_event_start">Börjar</string>
|
||||
<string name="calendar_event_end">Slutar</string>
|
||||
<string name="calendar_event_hashtags">Hashtaggar (kommaseparerade)</string>
|
||||
<string name="calendar_event_pick_date">Välj datum</string>
|
||||
<string name="calendar_event_pick_time">Välj tid</string>
|
||||
<string name="calendar_event_invalid">Titel och starttid krävs.</string>
|
||||
<string name="calendar_event_end_before_start">Slut måste vara efter start.</string>
|
||||
<string name="calendar_nav_previous_month">Föregående månad</string>
|
||||
<string name="calendar_nav_next_month">Nästa månad</string>
|
||||
<string name="calendar_nav_previous_week">Föregående vecka</string>
|
||||
<string name="calendar_nav_next_week">Nästa vecka</string>
|
||||
<string name="calendar_nav_previous_day">Föregående dag</string>
|
||||
<string name="calendar_nav_next_day">Nästa dag</string>
|
||||
<string name="calendar_no_events_today">Inga händelser denna dag</string>
|
||||
<string name="calendar_no_events">Inga händelser</string>
|
||||
<string name="calendar_continues">Fortsätter</string>
|
||||
<string name="calendar_day_of_total">Dag %1$d av %2$d</string>
|
||||
<string name="calendar_add_to_phone_calendar">Lägg till i telefonens kalender</string>
|
||||
<string name="calendar_nav_jump_to_today">Hoppa till idag</string>
|
||||
<plurals name="calendar_day_a11y_events">
|
||||
<item quantity="one">%1$s, %2$d händelse</item>
|
||||
<item quantity="other">%1$s, %2$d händelser</item>
|
||||
</plurals>
|
||||
<string name="calendar_day_a11y_no_events">%1$s, inga händelser</string>
|
||||
<string name="calendar_day_a11y_today_suffix">idag</string>
|
||||
<string name="calendar_day_a11y_selected_suffix">vald</string>
|
||||
<string name="calendar_fab_new_event">Skapa en ny kalenderhändelse</string>
|
||||
<string name="calendar_fab_new_collection">Skapa en ny kalender</string>
|
||||
<string name="calendar_fab_toggle">Visa skapandealternativ</string>
|
||||
<string name="calendar_untitled">(utan titel)</string>
|
||||
<string name="calendar_all_day">Heldag</string>
|
||||
<string name="calendar_rsvp_going_prefixed">✓ Kommer</string>
|
||||
<string name="calendar_rsvp_maybe_prefixed">\? Kanske</string>
|
||||
<string name="calendar_rsvp_not_going_prefixed">✗ Kommer inte</string>
|
||||
<string name="calendar_collection_title">Titel</string>
|
||||
<string name="calendar_collection_description">Beskrivning</string>
|
||||
<string name="calendar_collection_invalid">En titel krävs.</string>
|
||||
<plurals name="calendar_collection_count">
|
||||
<item quantity="one">%1$d händelse</item>
|
||||
<item quantity="other">%1$d händelser</item>
|
||||
</plurals>
|
||||
<string name="calendar_collection_empty_members">Inga händelser i denna kalender än.</string>
|
||||
<string name="calendar_rsvp_going">Kommer</string>
|
||||
<string name="calendar_rsvp_maybe">Kanske</string>
|
||||
<string name="calendar_rsvp_not_going">Kommer inte</string>
|
||||
<string name="calendar_rsvp_section">OSA (%1$d)</string>
|
||||
<string name="calendar_rsvp_none">Inga OSA än.</string>
|
||||
<string name="calendar_participants_section">Deltagare (%1$d)</string>
|
||||
<string name="calendar_event_in_calendars">I kalendrar (%1$d)</string>
|
||||
<string name="calendar_event_in_no_calendars">Inte del av någon kalender än.</string>
|
||||
<string name="calendar_event_loading">Laddar händelse…</string>
|
||||
<string name="calendar_relative_ongoing">Pågår nu</string>
|
||||
<string name="calendar_relative_ongoing_with_end">%1$s · slutar %2$s</string>
|
||||
<string name="calendar_export_share_title">Dela kalenderhändelse</string>
|
||||
<string name="calendar_export_event">Exportera till kalender (.ics)</string>
|
||||
<string name="calendar_reminder_channel_name">Kalenderpåminnelser</string>
|
||||
<string name="calendar_reminder_channel_description">Påminnelse när en händelse du deltar i snart ska börja.</string>
|
||||
<string name="calendar_reminder_default_title">Kalenderhändelse</string>
|
||||
<plurals name="calendar_reminder_body">
|
||||
<item quantity="one">Börjar om %1$d minut</item>
|
||||
<item quantity="other">Börjar om %1$d minuter</item>
|
||||
</plurals>
|
||||
<string name="calendar_add_to_calendar_action">Lägg till i en av dina kalendrar</string>
|
||||
<string name="calendar_add_to_calendar_title">Lägg till i en kalender</string>
|
||||
<string name="calendar_add_to_calendar_none">Du har inte skapat några kalendrar än.</string>
|
||||
<string name="calendar_collection_delete">Radera kalender</string>
|
||||
<string name="calendar_collection_delete_confirm_title">Radera denna kalender?</string>
|
||||
<string name="calendar_collection_delete_confirm_message">Kalenderlistan kommer att tas bort. Händelser inuti den raderas inte.</string>
|
||||
<string name="calendar_event_pick_image">Välj bild</string>
|
||||
<string name="calendar_event_image_upload_failed">Bilduppladdning misslyckades</string>
|
||||
<string name="calendar_event_image_upload_failed_body">Den valda bilden kunde inte laddas upp. Försök igen eller klistra in en URL.</string>
|
||||
<string name="calendar_event_participants_section">Deltagare (%1$d)</string>
|
||||
<string name="calendar_event_participant_input">Sök namn, npub eller nip-05</string>
|
||||
<string name="calendar_event_participant_invalid">Ange en giltig npub… eller 64-teckens hex pubkey.</string>
|
||||
<string name="calendar_event_participant_remove">Ta bort deltagare</string>
|
||||
<string name="calendar_reminder_settings_title">Kalenderpåminnelser</string>
|
||||
<string name="calendar_reminder_settings_enabled_title">Skicka påminnelser</string>
|
||||
<string name="calendar_reminder_settings_enabled_subtitle">En notis visas när en händelse du deltar i snart ska börja.</string>
|
||||
<string name="calendar_reminder_settings_lead_title">Påminnelsetid</string>
|
||||
<string name="calendar_reminder_settings_lead_subtitle">Hur många minuter före händelsen du vill bli notifierad.</string>
|
||||
<string name="calendar_share_nostr">Dela som Nostr-länk</string>
|
||||
<string name="calendar_share_nostr_title">Dela kalenderlänk</string>
|
||||
<string name="calendar_filter_all">Alla kalendrar</string>
|
||||
<string name="calendar_filter_sheet_title">Visa händelser från…</string>
|
||||
<string name="calendar_filter_no_calendars">Du har inte skapat några kalendrar än.</string>
|
||||
<string name="calendar_open_in_maps">Öppna i kartor</string>
|
||||
<string name="calendar_open_link">Öppna länk</string>
|
||||
<string name="route_calendar_event_detail">Händelsedetaljer</string>
|
||||
<string name="new_short_video">Ny kort video</string>
|
||||
<string name="new_long_video">Ny lång video</string>
|
||||
<string name="article_title">Titel</string>
|
||||
@@ -1674,6 +1807,8 @@
|
||||
<string name="boost_or_quote_description">Boosta eller citera</string>
|
||||
<string name="like_description">Gilla</string>
|
||||
<string name="zap_description">Zap</string>
|
||||
<string name="onchain_zap_description">On-chain Bitcoin-zap</string>
|
||||
<string name="onchain_zap_pending">Väntar på bekräftelse</string>
|
||||
<string name="change_reaction">Ändra Snabba Reaktioner</string>
|
||||
<string name="bottom_bar_settings">Nedre navigeringsfält</string>
|
||||
<string name="bottom_bar_settings_description">Dra för att ändra ordning. Växla för att lägga till eller ta bort ett objekt från nedre fältet. Med noll objekt döljs det nedre fältet.</string>
|
||||
@@ -2025,6 +2160,9 @@
|
||||
<string name="remove_user_from_the_list">Ta bort användare från listan</string>
|
||||
<string name="follow_list_item_label">Följpaket</string>
|
||||
<string name="members">Medlemmar</string>
|
||||
<string name="contact_list_containing_label">Följlista som innehåller %1$d användare:</string>
|
||||
<string name="contact_list_screen_title">Följlista</string>
|
||||
<string name="contact_list_screen_title_with_count">Följlista (%1$d)</string>
|
||||
<string name="people_list_title">Metadata för följlista</string>
|
||||
<string name="people_list_explainer">Metadata för följlistor kan ses av alla på Nostr. Endast privata medlemmar är krypterade.</string>
|
||||
<string name="follow_pack_title">Metadata för rekommendationspaket</string>
|
||||
|
||||
@@ -778,6 +778,10 @@
|
||||
<string name="wallet_connect_manual_config">高级:手动输入连接详情</string>
|
||||
<string name="quick_zap_amounts">快速打闪金额</string>
|
||||
<string name="quick_zap_amounts_explainer">按下打闪按钮时显示。点击接即可删除它。 如果留空,每次都会打开对话框来插入金融。</string>
|
||||
<string name="quick_zap_amounts_onchain">链上快速打闪金额</string>
|
||||
<string name="quick_zap_amounts_onchain_explainer">链上打闪支付矿工费,所以金额通常大于 Lightning 网络。点击一个金额去掉它。</string>
|
||||
<string name="new_amount_in_sats_onchain">新的链上金额(sats)</string>
|
||||
<string name="send_onchain_instead">在链上发送</string>
|
||||
<string name="zap_privacy_section">打闪隐私</string>
|
||||
<string name="zap_type_section_explainer">控制发送打闪时如何显示您的身份。</string>
|
||||
<string name="wallet_connect_connect_app">连接钱包</string>
|
||||
@@ -1646,6 +1650,11 @@
|
||||
<string name="wallet_onchain_no_address">此账户没有链上地址可用。</string>
|
||||
<string name="wallet_onchain_no_backend">没有配置链后端。</string>
|
||||
<string name="wallet_onchain_pending">待确认</string>
|
||||
<string name="wallet_onchain_public_chip">公开</string>
|
||||
<string name="wallet_onchain_public_dialog_title">这个钱包是公开的</string>
|
||||
<string name="wallet_onchain_public_dialog_body">您的Taproot地址衍生自您的Nostr公钥, 因此任何知道您的 npub的人都可以在区块链上看到这个钱包的余额和交易历史。\n\n为了保护您的隐私,这个钱包的资金流入和资金流出请使用非私密账户,例如交易所。 永远不要将这些资金与你的冷钱包混合,把它们当作你可能丢失的钱, 因为任何控制你 nsec 的人都可以花这些钱。</string>
|
||||
<string name="wallet_onchain_public_dialog_confirm">明白了</string>
|
||||
<string name="wallet_onchain_copy_dialog_confirm">复制地址</string>
|
||||
<string name="route_security_filters">安全滤镜</string>
|
||||
<string name="route_import_follows">导入关注</string>
|
||||
<string name="new_post">新帖子</string>
|
||||
@@ -1708,6 +1717,7 @@
|
||||
<plurals name="calendar_day_a11y_events">
|
||||
<item quantity="other">%1$s, %2$d 个活动</item>
|
||||
</plurals>
|
||||
<string name="calendar_day_a11y_no_events">%1$s,无活动</string>
|
||||
<string name="calendar_day_a11y_today_suffix">今天</string>
|
||||
<string name="calendar_day_a11y_selected_suffix">已选中</string>
|
||||
<string name="calendar_fab_new_event">创建新的日历活动</string>
|
||||
@@ -2150,6 +2160,9 @@
|
||||
<string name="remove_user_from_the_list">从列表中删除用户</string>
|
||||
<string name="follow_list_item_label">关注包</string>
|
||||
<string name="members">成员</string>
|
||||
<string name="contact_list_containing_label">包含 %1$d 个用户的关注列表:</string>
|
||||
<string name="contact_list_screen_title">关注列表</string>
|
||||
<string name="contact_list_screen_title_with_count">关注列表(%1$d)</string>
|
||||
<string name="people_list_title">关注列表元数据</string>
|
||||
<string name="people_list_explainer">关注列表元数据可以被Nostr上的任何人看到。只有您的私人成员是加密的。</string>
|
||||
<string name="follow_pack_title">关注包元数据</string>
|
||||
|
||||
@@ -1932,10 +1932,10 @@
|
||||
<string name="calendar_add_to_phone_calendar">Add to phone calendar</string>
|
||||
<string name="calendar_nav_jump_to_today">Jump to today</string>
|
||||
<plurals name="calendar_day_a11y_events">
|
||||
<item quantity="zero">%1$s, no events</item>
|
||||
<item quantity="one">%1$s, %2$d event</item>
|
||||
<item quantity="other">%1$s, %2$d events</item>
|
||||
</plurals>
|
||||
<string name="calendar_day_a11y_no_events">%1$s, no events</string>
|
||||
<string name="calendar_day_a11y_today_suffix">today</string>
|
||||
<string name="calendar_day_a11y_selected_suffix">selected</string>
|
||||
<string name="calendar_fab_new_event">Create a new calendar event</string>
|
||||
|
||||
+14
@@ -36,6 +36,7 @@ import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
@@ -96,6 +97,19 @@ class PublicChatListState(
|
||||
emptySet(),
|
||||
)
|
||||
|
||||
val flowSetNote =
|
||||
flowSet
|
||||
.map {
|
||||
it.mapNotNull {
|
||||
cache.checkGetOrCreateNote(it)
|
||||
}
|
||||
}.flowOn(Dispatchers.IO)
|
||||
.stateIn(
|
||||
scope,
|
||||
SharingStarted.Eagerly,
|
||||
emptyList(),
|
||||
)
|
||||
|
||||
suspend fun follow(channel: PublicChatChannel): ChannelListEvent {
|
||||
val publicChatList = getChannelList()
|
||||
|
||||
|
||||
+7
-13
@@ -42,7 +42,6 @@ fun MediaUrlContent.toCoilModel(useLocalBlossomBridge: Boolean): String =
|
||||
bridgeUrl(
|
||||
url = url,
|
||||
useBridge = useLocalBlossomBridge,
|
||||
explicitHash = hash,
|
||||
mimeType = mimeType,
|
||||
authorPubKey = authorPubKey,
|
||||
skipBridge = this is MediaUrlVideo && isLiveStream,
|
||||
@@ -95,7 +94,6 @@ const val DEFAULT_LOCAL_CACHE_BASE = "http://127.0.0.1:24242"
|
||||
private fun bridgeUrl(
|
||||
url: String,
|
||||
useBridge: Boolean,
|
||||
explicitHash: String?,
|
||||
mimeType: String?,
|
||||
authorPubKey: String?,
|
||||
skipBridge: Boolean,
|
||||
@@ -105,19 +103,15 @@ private fun bridgeUrl(
|
||||
if (!url.startsWith("http://", ignoreCase = true) && !url.startsWith("https://", ignoreCase = true)) return url
|
||||
|
||||
// The local Blossom cache fetches `<xs>/<sha>.<ext>` on miss per BUD-01,
|
||||
// which only works when the upstream URL is itself BUD-01 layout. For
|
||||
// non-BUD-01 URLs (e.g. https://i.nostr.build/M5AwJ.gif) the imeta `x`
|
||||
// hash identifies the blob but the upstream server doesn't host it at
|
||||
// /<sha>.<ext>, so trusting only `explicitHash` would point the cache
|
||||
// at a 404. Require the sha to be in the URL path before bridging.
|
||||
val urlSha = extractSha256FromUrlPath(url) ?: return url
|
||||
|
||||
// Prefer the imeta hash when it's a valid sha256 (authoritative casing),
|
||||
// otherwise fall back to what was parsed from the URL.
|
||||
val sha = explicitHash?.lowercase()?.takeIf { sha256HexRegex.matches(it) } ?: urlSha
|
||||
// which only works when the upstream URL is itself BUD-01 layout — the
|
||||
// file at `<xs>/<sha>.<ext>` is the one named in the URL path, not the
|
||||
// imeta `x` hash (which on resizing CDNs may identify a different blob
|
||||
// than the URL filename, e.g. `x` = post-resize, `ox` = original).
|
||||
// Always use the URL's sha; never trust the imeta override.
|
||||
val sha = extractSha256FromUrlPath(url) ?: return url
|
||||
|
||||
val ext = guessExtension(url, mimeType)
|
||||
val serverBase = extractServerBase(url, urlSha) ?: return url
|
||||
val serverBase = extractServerBase(url, sha) ?: return url
|
||||
|
||||
val authors =
|
||||
authorPubKey
|
||||
|
||||
+15
@@ -255,4 +255,19 @@ class MediaUrlContentExtTest {
|
||||
val image = MediaUrlImage(url = url, hash = null)
|
||||
assertEquals(url, image.toCoilModel(useLocalBlossomBridge = true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bridgeOnUsesUrlShaNotImetaWhenTheyDiffer() {
|
||||
// On resizing CDNs the imeta `x` (post-resize) can differ from the
|
||||
// `ox` (original) embedded in the URL. The upstream file is named
|
||||
// after the URL's sha, so the cache request must use that — using
|
||||
// the imeta `x` would point xs= at a non-existent path on miss.
|
||||
val urlSha = "f24026b7281e598973a775adefb1b9a13b9f037a94ac98dd48ccc91b83f4b7b3"
|
||||
val imetaX = "6932a918de1bfae3bf6611794ff54dd677013d22b760a9212117a0bd9079badf"
|
||||
val image = MediaUrlImage(url = "https://image.nostr.build/$urlSha.png", hash = imetaX)
|
||||
assertEquals(
|
||||
"blossom:$urlSha.png?xs=https://image.nostr.build",
|
||||
image.toCoilModel(useLocalBlossomBridge = true),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -175,6 +175,11 @@ class DesktopRelaySubscriptionsCoordinator(
|
||||
kinds = listOf(com.vitorpamplona.quartz.nip18Reposts.RepostEvent.KIND),
|
||||
tags = mapOf("e" to noteIds),
|
||||
),
|
||||
// Replies (kind 1) targeting these notes
|
||||
Filter(
|
||||
kinds = listOf(com.vitorpamplona.quartz.nip10Notes.TextNoteEvent.KIND),
|
||||
tags = mapOf("e" to noteIds),
|
||||
),
|
||||
)
|
||||
|
||||
val listener =
|
||||
|
||||
+35
-2
@@ -74,6 +74,9 @@ import com.vitorpamplona.quartz.nip01Core.tags.references.references
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.content.findHashtags
|
||||
import com.vitorpamplona.quartz.nip10Notes.content.findURLs
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.QEventTag
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.quote
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
|
||||
import com.vitorpamplona.quartz.nip92IMeta.IMetaTag
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -96,8 +99,19 @@ fun ComposeNoteDialog(
|
||||
relayManager: DesktopRelayConnectionManager,
|
||||
account: AccountState.LoggedIn,
|
||||
replyTo: Event? = null,
|
||||
quoteOf: Event? = null,
|
||||
) {
|
||||
var content by remember { mutableStateOf("") }
|
||||
val initialContent =
|
||||
remember(quoteOf) {
|
||||
if (quoteOf != null) {
|
||||
val relays = relayManager.connectedRelays.value.take(3)
|
||||
val nevent = NEvent.create(quoteOf.id, quoteOf.pubKey, quoteOf.kind, relays)
|
||||
"\nnostr:$nevent"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
var content by remember { mutableStateOf(initialContent) }
|
||||
var isPosting by remember { mutableStateOf(false) }
|
||||
var errorMessage by remember { mutableStateOf<String?>(null) }
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -165,7 +179,11 @@ fun ComposeNoteDialog(
|
||||
) {
|
||||
Column(modifier = Modifier.padding(24.dp)) {
|
||||
Text(
|
||||
if (replyTo != null) "Reply" else "New Note",
|
||||
when {
|
||||
replyTo != null -> "Reply"
|
||||
quoteOf != null -> "Quote"
|
||||
else -> "New Note"
|
||||
},
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
@@ -179,6 +197,15 @@ fun ComposeNoteDialog(
|
||||
)
|
||||
}
|
||||
|
||||
quoteOf?.let { quoted ->
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
"Quoting: ${quoted.content.take(50)}${if (quoted.content.length > 50) "..." else ""}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
@@ -357,6 +384,7 @@ fun ComposeNoteDialog(
|
||||
account = account,
|
||||
relayManager = relayManager,
|
||||
replyTo = replyTo,
|
||||
quoteOf = quoteOf,
|
||||
imetaTags = imetaTags,
|
||||
relays = selectedRelays,
|
||||
)
|
||||
@@ -529,6 +557,7 @@ private suspend fun publishNote(
|
||||
account: AccountState.LoggedIn,
|
||||
relayManager: DesktopRelayConnectionManager,
|
||||
replyTo: Event?,
|
||||
quoteOf: Event? = null,
|
||||
imetaTags: List<IMetaTag> = emptyList(),
|
||||
relays: Set<NormalizedRelayUrl>,
|
||||
) {
|
||||
@@ -546,6 +575,10 @@ private suspend fun publishNote(
|
||||
eTag(etag)
|
||||
pTag(PTag(replyTo.pubKey, relayHint = null))
|
||||
}
|
||||
if (quoteOf != null) {
|
||||
quote(QEventTag(quoteOf.id, relayHint = null, authorPubKeyHex = quoteOf.pubKey))
|
||||
pTag(PTag(quoteOf.pubKey, relayHint = null))
|
||||
}
|
||||
hashtags(findHashtags(content))
|
||||
references(findURLs(content))
|
||||
for (imeta in imetaTags) {
|
||||
|
||||
@@ -87,6 +87,7 @@ import com.vitorpamplona.amethyst.desktop.ui.relay.LocalRelayCategories
|
||||
import com.vitorpamplona.amethyst.desktop.ui.relay.Nip65RelayEditor
|
||||
import com.vitorpamplona.amethyst.desktop.viewmodels.DesktopFeedViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||
@@ -153,10 +154,10 @@ fun FeedNoteCard(
|
||||
return
|
||||
}
|
||||
|
||||
val reactionCount = originalNote.countReactions()
|
||||
val replyCount = originalNote.replies.size
|
||||
val repostCount = originalNote.boosts.size
|
||||
val zapAmount = originalNote.zapsAmount
|
||||
val reactionCount = remember(reactionsState) { originalNote.countReactions() }
|
||||
val replyCount = remember(repliesState) { originalNote.replies.size }
|
||||
val repostCount = remember(metadataState) { originalNote.boosts.size }
|
||||
val zapAmount = remember(zapsState) { originalNote.zapsAmount }
|
||||
|
||||
val reposterUser = localCache.getUserIfExists(event.pubKey)
|
||||
val originalUser = localCache.getUserIfExists(originalEvent.pubKey)
|
||||
@@ -170,15 +171,15 @@ fun FeedNoteCard(
|
||||
GenericRepostLayout(
|
||||
baseAuthorPicture = {
|
||||
UserAvatar(
|
||||
userHex = originalEvent.pubKey,
|
||||
pictureUrl = originalUser?.profilePicture(),
|
||||
userHex = event.pubKey,
|
||||
pictureUrl = reposterUser?.profilePicture(),
|
||||
size = 35.dp,
|
||||
)
|
||||
},
|
||||
repostAuthorPicture = {
|
||||
UserAvatar(
|
||||
userHex = event.pubKey,
|
||||
pictureUrl = reposterUser?.profilePicture(),
|
||||
userHex = originalEvent.pubKey,
|
||||
pictureUrl = originalUser?.profilePicture(),
|
||||
size = 35.dp,
|
||||
)
|
||||
},
|
||||
@@ -209,14 +210,15 @@ fun FeedNoteCard(
|
||||
onReplyClick = onReply,
|
||||
onZapFeedback = onZapFeedback,
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
relayHint = originalNote.relayHintUrl(),
|
||||
authorRelayHint = originalNote.author?.bestRelayHint(),
|
||||
note = originalNote,
|
||||
zapCount = originalNote.zaps.size,
|
||||
zapAmountSats = zapAmount.toLong(),
|
||||
zapReceipts = emptyList(),
|
||||
reactionCount = reactionCount,
|
||||
replyCount = replyCount,
|
||||
repostCount = repostCount,
|
||||
onNavigateToThread = onNavigateToThread,
|
||||
onNavigateToProfile = onNavigateToProfile,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -228,10 +230,10 @@ fun FeedNoteCard(
|
||||
val repliesState by flowSet.replies.stateFlow.collectAsState()
|
||||
val zapsState by flowSet.zaps.stateFlow.collectAsState()
|
||||
|
||||
val reactionCount = note.countReactions()
|
||||
val replyCount = note.replies.size
|
||||
val repostCount = note.boosts.size
|
||||
val zapAmount = note.zapsAmount
|
||||
val reactionCount = remember(reactionsState) { note.countReactions() }
|
||||
val replyCount = remember(repliesState) { note.replies.size }
|
||||
val repostCount = remember(metadataState) { note.boosts.size }
|
||||
val zapAmount = remember(zapsState) { note.zapsAmount }
|
||||
|
||||
DisposableEffect(note) {
|
||||
onDispose { note.clearFlow() }
|
||||
@@ -259,14 +261,15 @@ fun FeedNoteCard(
|
||||
onReplyClick = onReply,
|
||||
onZapFeedback = onZapFeedback,
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
relayHint = note.relayHintUrl(),
|
||||
authorRelayHint = note.author?.bestRelayHint(),
|
||||
note = note,
|
||||
zapCount = note.zaps.size,
|
||||
zapAmountSats = zapAmount.toLong(),
|
||||
zapReceipts = emptyList(),
|
||||
reactionCount = reactionCount,
|
||||
replyCount = replyCount,
|
||||
repostCount = repostCount,
|
||||
onNavigateToThread = onNavigateToThread,
|
||||
onNavigateToProfile = onNavigateToProfile,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -409,16 +412,17 @@ fun FeedScreen(
|
||||
onDispose { viewModel.destroy() }
|
||||
}
|
||||
|
||||
// Rescan cache when followedUsers populates (fixes cold-boot race where
|
||||
// the initial scan runs before the contact list arrives from relays)
|
||||
LaunchedEffect(viewModel, followedUsers) {
|
||||
if (feedMode == FeedMode.FOLLOWING && followedUsers.isNotEmpty()) {
|
||||
viewModel.feedState.refreshSuspended()
|
||||
val feedState by viewModel.feedState.feedContent.collectAsState()
|
||||
|
||||
// Force refresh when followedUsers arrives and feed is still empty
|
||||
LaunchedEffect(followedUsers, feedState) {
|
||||
if (followedUsers.isNotEmpty() && feedState is FeedState.Empty) {
|
||||
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
|
||||
viewModel.feedState.refreshSuspended()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val feedState by viewModel.feedState.feedContent.collectAsState()
|
||||
|
||||
// Viewport-aware metadata loading: only fetch for visible notes + buffer
|
||||
// Uses snapshotFlow to avoid per-frame recomposition from scroll observation
|
||||
LaunchedEffect(feedState, subscriptionsCoordinator) {
|
||||
@@ -521,19 +525,32 @@ fun FeedScreen(
|
||||
)
|
||||
}
|
||||
|
||||
// Request interaction subscriptions — keyed on feedMode (stable), not feedState (changes every 250ms)
|
||||
DisposableEffect(feedMode, subscriptionsCoordinator) {
|
||||
val coordinator = subscriptionsCoordinator ?: return@DisposableEffect onDispose {}
|
||||
val relays = relayManager.relayStatuses.value.keys
|
||||
// Initial subscription with whatever notes are visible now
|
||||
val noteIds = viewModel.feedState.visibleNotes().mapNotNull { it.event?.id }
|
||||
val subId =
|
||||
if (noteIds.isNotEmpty()) {
|
||||
coordinator.requestInteractions(noteIds, relays)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
onDispose { subId?.let { coordinator.releaseInteractions(it) } }
|
||||
// Interaction subscriptions (reactions, zaps, reposts, replies) — same pattern as metadata
|
||||
val interactionNoteIds =
|
||||
remember(feedState) {
|
||||
if (feedState !is FeedState.Loaded) return@remember emptyList<String>()
|
||||
viewModel.feedState.visibleNotes().mapNotNull { it.event?.id }
|
||||
}
|
||||
|
||||
rememberSubscription(allRelayUrls, interactionNoteIds, relayManager = relayManager) {
|
||||
if (allRelayUrls.isEmpty() || interactionNoteIds.isEmpty()) return@rememberSubscription null
|
||||
SubscriptionConfig(
|
||||
subId = generateSubId("fetch-interactions"),
|
||||
filters =
|
||||
listOf(
|
||||
FilterBuilders.reactionsForEvents(interactionNoteIds),
|
||||
FilterBuilders.zapsForEvents(interactionNoteIds),
|
||||
FilterBuilders.repostsForEvents(interactionNoteIds),
|
||||
Filter(
|
||||
kinds = listOf(com.vitorpamplona.quartz.nip10Notes.TextNoteEvent.KIND),
|
||||
tags = mapOf("e" to interactionNoteIds),
|
||||
),
|
||||
),
|
||||
relays = allRelayUrls,
|
||||
onEvent = { event, _, relay, _ ->
|
||||
subscriptionsCoordinator?.consumeEvent(event, relay)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
+701
-222
File diff suppressed because it is too large
Load Diff
@@ -53,6 +53,7 @@ import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.richtext.UrlParser
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.EmptyState
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.LoadingState
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
|
||||
@@ -62,12 +63,18 @@ import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache
|
||||
import com.vitorpamplona.amethyst.desktop.feeds.DesktopThreadFilter
|
||||
import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager
|
||||
import com.vitorpamplona.amethyst.desktop.subscriptions.DesktopRelaySubscriptionsCoordinator
|
||||
import com.vitorpamplona.amethyst.desktop.subscriptions.FilterBuilders
|
||||
import com.vitorpamplona.amethyst.desktop.subscriptions.SubscriptionConfig
|
||||
import com.vitorpamplona.amethyst.desktop.subscriptions.createNoteSubscription
|
||||
import com.vitorpamplona.amethyst.desktop.subscriptions.createThreadRepliesSubscription
|
||||
import com.vitorpamplona.amethyst.desktop.subscriptions.generateSubId
|
||||
import com.vitorpamplona.amethyst.desktop.subscriptions.rememberSubscription
|
||||
import com.vitorpamplona.amethyst.desktop.ui.media.LightboxOverlay
|
||||
import com.vitorpamplona.amethyst.desktop.viewmodels.DesktopFeedViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NNote
|
||||
|
||||
/**
|
||||
* Desktop Thread Screen - displays a note and all its replies in a thread view.
|
||||
@@ -177,6 +184,38 @@ fun ThreadScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch quoted notes referenced in thread content
|
||||
val quotedNoteIds =
|
||||
remember(threadNotes) {
|
||||
threadNotes
|
||||
.mapNotNull { it.event }
|
||||
.flatMap { event ->
|
||||
UrlParser()
|
||||
.parseValidUrls(event.content)
|
||||
.bech32s
|
||||
.mapNotNull { bech32 ->
|
||||
when (val entity = Nip19Parser.uriToRoute(bech32)?.entity) {
|
||||
is NNote -> entity.hex
|
||||
is NEvent -> entity.hex
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}.filter { localCache.getNoteIfExists(it)?.event == null }
|
||||
.distinct()
|
||||
}
|
||||
|
||||
rememberSubscription(connectedRelays, quotedNoteIds, relayManager = relayManager) {
|
||||
if (connectedRelays.isEmpty() || quotedNoteIds.isEmpty()) return@rememberSubscription null
|
||||
SubscriptionConfig(
|
||||
subId = generateSubId("thread-quoted"),
|
||||
filters = listOf(FilterBuilders.byIds(quotedNoteIds)),
|
||||
relays = connectedRelays,
|
||||
onEvent = { event, _, relay, _ ->
|
||||
subscriptionsCoordinator?.consumeEvent(event, relay)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Calculate reply level for a note based on e-tags
|
||||
fun calculateLevel(note: Note): Int {
|
||||
val event = note.event ?: return 1
|
||||
|
||||
Reference in New Issue
Block a user