fix(strings): move the cordn and backup strings to commonsUI, and write the rule down

The 2026-09-22 move left Android res/ with only the ~190 strings a platform
API reads synchronously. The cordn UI and the backup-conflict review then
added 447 keys back to it. All of them move with tools/strings-migrate
(1,131 elements across the locales that translate them), and the 18 files
that read them now use Res.string/Res.plurals.

The parts that were not a rename:
- stringRes(context, ...) in the suspend attachment/voice senders becomes
  loadStringRes; adminFailureText becomes suspend (its only caller is already
  inside scope.launch).
- The backup review held labels as @StringRes Int (DiffGroup.label, the
  ReviewRow *Res fields, eventTypeName/Explainer, the tile/hero/lane params).
  They are StringResource / PluralStringResource now.
- rememberPresentation resolved labels inside remember {} through
  LocalResources. Compose resources have no blocking accessor, so the 16
  labels presentationOf writes into item text are resolved in composition
  into a map, and that map is the remember key.
- androidx pluralStringResource -> the compose-resources one.

The rule is now in .claude/CLAUDE.md ("Strings"), commonsUI/ARCHITECTURE.md
and the android-expert and find-missing-translations skills: new strings go
in commonsUI composeResources, Android-only screens included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjQQN9CgWHVtNCSWqnKoqY
This commit is contained in:
Claude
2026-09-26 21:13:01 +00:00
parent 08169f8497
commit 150177f5a0
30 changed files with 2777 additions and 2185 deletions
+42
View File
@@ -285,6 +285,48 @@ Quartz uses expect/actual for platform-specific implementations (e.g. crypto
backed by `secp256k1-kmp-jni-android` on Android and `secp256k1-kmp-jni-jvm` on
JVM). See `/kotlin-multiplatform` for the expect/actual and source-set patterns.
## Strings
**MANDATORY: every new user-visible string goes in `commonsUI` Compose resources**,
`commonsUI/src/commonMain/composeResources/values/strings.xml` — **never** in
`amethyst/src/main/res/values/strings.xml`, even for an Android-only screen. A
screen cannot move to `commonsUI` (or be rendered by Desktop) while its labels
live in Android `res/`, and every key added there has to be migrated back out
later. (The 2026-09-22 move emptied Android `res/` down to ~190 keys; the next two
features put 447 back.)
- Reference it as `Res.string.my_key` / `Res.plurals.my_key`, importing
`com.vitorpamplona.amethyst.commons.resources.Res` **and** the per-key accessor
`com.vitorpamplona.amethyst.commons.resources.my_key` (Compose resources generates
each key as an extension property).
- Read it with `stringRes(Res.string.x)` / `pluralStringRes(Res.plurals.x, n, …)`
in composition — the app-side `ui.stringRes` overloads delegate to the
commonsUI bridge (`commons/ui/StringRes.kt`), so one import serves both kinds.
- Outside composition there is **no blocking accessor**: use `loadStringRes(…)`
from a coroutine (or make the function `suspend`), or resolve the label in
composition and pass it down as a `String`. A `runBlocking` bridge is ruled out.
- Hold a string reference as `StringResource` / `PluralStringResource`, not a
`@StringRes Int`.
- Format arguments must be positional (`%1$s`, `%2$d`) — compose-resources does
not format bare `%s`/`%d`.
- Escaping is not Android's: write `'` and `"` bare (no `\'`), and never wrap a
value in quotes to keep its whitespace. `.claude/hooks/compose_escaping_check.py`
checks this before a push.
The **only** strings that stay in Android `res/` are the ones a platform API
reads synchronously under a deadline, where a `suspend` read cannot run: a
foreground service's `startForeground` notification, a notification channel
created during service startup, PiP `RemoteAction`s, napplet capability labels
read in the sandbox process, and `AndroidManifest.xml` / `res/xml` references.
Most of those are *copies* of a key that also lives in `commonsUI`. See
`commons/plans/2026-09-22-strings-to-compose-resources.md` ("the
synchronous-platform tier"). If you think a new string belongs in that tier,
say why in the PR.
To move keys that already landed in Android `res/`, use
`tools/strings-migrate/migrate.py <keys…>`. It moves every locale byte-for-byte,
so Crowdin sees a pure move. Then repoint `R.string.x` → `Res.string.x`.
## Icons
The Material Symbols font bundled at
+18 -3
View File
@@ -875,18 +875,33 @@ fun ProfileScreen(
### 3. Resource Access
**Strings do not go in `amethyst/src/main/res`.** New user-visible strings live in
`commonsUI/src/commonMain/composeResources/values/strings.xml` and are read as
`Res.string.x`, even on an Android-only screen. See "Strings" in `.claude/CLAUDE.md`
for the rule and the small platform tier that is the only exception.
```kotlin
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.button_clicked
import com.vitorpamplona.amethyst.commons.resources.button_label
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
import com.vitorpamplona.amethyst.ui.stringRes
@Composable
fun LocalizedButton() {
val context = LocalContext.current
val scope = rememberCoroutineScope()
Button(
onClick = {
val message = context.getString(R.string.button_clicked)
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
// No blocking accessor outside composition: read it from a coroutine,
// or resolve it in composition and capture the String.
scope.launch {
Toast.makeText(context, loadStringRes(Res.string.button_clicked), Toast.LENGTH_SHORT).show()
}
}
) {
Text(stringResource(R.string.button_label))
Text(stringRes(Res.string.button_label))
}
}
```
@@ -26,6 +26,13 @@ There are two separate `strings.xml` trees, each with its own default `values/`
| **amethyst** (Android app) | `amethyst/src/main/res/values/strings.xml` | `amethyst/src/main/res/values-<locale>/strings.xml` |
| **commonsUI** (KMP Compose resources, shared by Android + Desktop) | `commonsUI/src/commonMain/composeResources/values/strings.xml` | `commonsUI/src/commonMain/composeResources/values-<locale>/strings.xml` |
**New keys go in the `commonsUI` tree, always.** The amethyst tree is frozen at
the small synchronous-platform tier (foreground-service and notification-channel
text, PiP actions, napplet capability labels, manifest references). If you find
a new feature key in it, it belongs in `commonsUI`. Move it with
`tools/strings-migrate/migrate.py` rather than translating it where it is. See
"Strings" in `.claude/CLAUDE.md`.
The `commonsUI` tree appeared when shared event-renderer composables were extracted out of `amethyst/` into `commons/` — now `commonsUI/` since the UI split (Compose Multiplatform `stringResource`). It is **not** a copy of the amethyst tree — the vast majority of its keys are commons-only; only a small handful overlap. Every diff/count/translate command below works on either tree by swapping the base path — **run the whole technique once per tree** and report them separately (each maps to its own Crowdin file, so the counts should reconcile against two different Crowdin UI numbers).
**Locale-qualifier caveat:** `commons` uses the same region-qualified locale dirs as amethyst for our four targets (`values-cs`, `values-de-rDE`, `values-sv-rSE`, `values-pt-rBR`), but the *full* set of locale dirs differs between trees. Enumerate `values-*` under each tree's own base rather than assuming they match.
@@ -43,17 +43,57 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.model.backups.BackupEventType
import com.vitorpamplona.amethyst.commons.model.backups.ReplaceableBackupConflict
import com.vitorpamplona.amethyst.commons.model.navigation.Route
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.backup_card_accept
import com.vitorpamplona.amethyst.commons.resources.backup_card_and_added
import com.vitorpamplona.amethyst.commons.resources.backup_card_and_blocked
import com.vitorpamplona.amethyst.commons.resources.backup_card_and_followed
import com.vitorpamplona.amethyst.commons.resources.backup_card_and_joined
import com.vitorpamplona.amethyst.commons.resources.backup_card_and_muted
import com.vitorpamplona.amethyst.commons.resources.backup_card_app_settings
import com.vitorpamplona.amethyst.commons.resources.backup_card_concord_emptied
import com.vitorpamplona.amethyst.commons.resources.backup_card_concord_rewritten
import com.vitorpamplona.amethyst.commons.resources.backup_card_dm_relays_lost
import com.vitorpamplona.amethyst.commons.resources.backup_card_feeds_removed
import com.vitorpamplona.amethyst.commons.resources.backup_card_follows_dropped
import com.vitorpamplona.amethyst.commons.resources.backup_card_hashtags_unfollowed
import com.vitorpamplona.amethyst.commons.resources.backup_card_indexer_relays_lost
import com.vitorpamplona.amethyst.commons.resources.backup_card_key_package_relays_lost
import com.vitorpamplona.amethyst.commons.resources.backup_card_leave_chats
import com.vitorpamplona.amethyst.commons.resources.backup_card_leave_communities
import com.vitorpamplona.amethyst.commons.resources.backup_card_leave_groups
import com.vitorpamplona.amethyst.commons.resources.backup_card_leave_rooms
import com.vitorpamplona.amethyst.commons.resources.backup_card_mutes_visible
import com.vitorpamplona.amethyst.commons.resources.backup_card_nip65_lost
import com.vitorpamplona.amethyst.commons.resources.backup_card_nutzap_key_removed
import com.vitorpamplona.amethyst.commons.resources.backup_card_nutzap_key_replaced
import com.vitorpamplona.amethyst.commons.resources.backup_card_nutzap_mints_removed
import com.vitorpamplona.amethyst.commons.resources.backup_card_offers_removed
import com.vitorpamplona.amethyst.commons.resources.backup_card_payment_targets_removed
import com.vitorpamplona.amethyst.commons.resources.backup_card_places_unfollowed
import com.vitorpamplona.amethyst.commons.resources.backup_card_private_outbox_lost
import com.vitorpamplona.amethyst.commons.resources.backup_card_profile_lost_fields
import com.vitorpamplona.amethyst.commons.resources.backup_card_relay_feeds_lost
import com.vitorpamplona.amethyst.commons.resources.backup_card_search_relays_lost
import com.vitorpamplona.amethyst.commons.resources.backup_card_trust_services_changed
import com.vitorpamplona.amethyst.commons.resources.backup_card_trusted_lost
import com.vitorpamplona.amethyst.commons.resources.backup_card_unblocked
import com.vitorpamplona.amethyst.commons.resources.backup_card_wallet_emptied
import com.vitorpamplona.amethyst.commons.resources.backup_card_wallet_rewritten
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_count_added
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_count_changed
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_count_removed
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_title
import com.vitorpamplona.amethyst.commons.resources.backup_review_changed_by_other_app
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.note.timeAgoNoDot
@@ -79,6 +119,9 @@ import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListDiff
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataDiff
import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListDiff
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListDiff
import org.jetbrains.compose.resources.PluralStringResource
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.pluralStringResource
/**
* Cards at the top of Home's feed, one per open [ReplaceableBackupConflict]: the user
@@ -125,34 +168,34 @@ private fun headlineOf(
when (diff) {
// Counted, not "shrank": a version that drops 3 follows and adds 100 still raises
// a conflict, and calling that a shrink contradicts the card's own bar.
is ContactListDiff -> plural(R.plurals.backup_card_follows_dropped, diff.follows.removed.size)
is MuteListDiff -> plural(R.plurals.backup_card_mutes_visible, diff.publicMutes.removed.size)
is MetadataDiff -> plural(R.plurals.backup_card_profile_lost_fields, counts.removed)
is AdvertisedRelayListDiff -> plural(R.plurals.backup_card_nip65_lost, diff.relays.removed.size)
is ContactListDiff -> plural(Res.plurals.backup_card_follows_dropped, diff.follows.removed.size)
is MuteListDiff -> plural(Res.plurals.backup_card_mutes_visible, diff.publicMutes.removed.size)
is MetadataDiff -> plural(Res.plurals.backup_card_profile_lost_fields, counts.removed)
is AdvertisedRelayListDiff -> plural(Res.plurals.backup_card_nip65_lost, diff.relays.removed.size)
is RelayListDiff -> relayListHeadline(conflict.eventType, diff.relays.removed.size)
is ChannelListDiff -> plural(R.plurals.backup_card_leave_chats, diff.channels.removed.size)
is CommunityListDiff -> plural(R.plurals.backup_card_leave_communities, diff.communities.removed.size)
is EphemeralChatListDiff -> plural(R.plurals.backup_card_leave_rooms, diff.rooms.removed.size)
is SimpleGroupListDiff -> plural(R.plurals.backup_card_leave_groups, diff.groups.removed.size)
is FavoriteAlgoFeedsListDiff -> plural(R.plurals.backup_card_feeds_removed, diff.feeds.removed.size)
is HashtagListDiff -> plural(R.plurals.backup_card_hashtags_unfollowed, diff.hashtags.removed.size)
is GeohashListDiff -> plural(R.plurals.backup_card_places_unfollowed, diff.geohashes.removed.size)
is TrustProviderListDiff -> plural(R.plurals.backup_card_trust_services_changed, diff.providers.removed.size + diff.providers.changed.size)
is ChannelListDiff -> plural(Res.plurals.backup_card_leave_chats, diff.channels.removed.size)
is CommunityListDiff -> plural(Res.plurals.backup_card_leave_communities, diff.communities.removed.size)
is EphemeralChatListDiff -> plural(Res.plurals.backup_card_leave_rooms, diff.rooms.removed.size)
is SimpleGroupListDiff -> plural(Res.plurals.backup_card_leave_groups, diff.groups.removed.size)
is FavoriteAlgoFeedsListDiff -> plural(Res.plurals.backup_card_feeds_removed, diff.feeds.removed.size)
is HashtagListDiff -> plural(Res.plurals.backup_card_hashtags_unfollowed, diff.hashtags.removed.size)
is GeohashListDiff -> plural(Res.plurals.backup_card_places_unfollowed, diff.geohashes.removed.size)
is TrustProviderListDiff -> plural(Res.plurals.backup_card_trust_services_changed, diff.providers.removed.size + diff.providers.changed.size)
is NutzapInfoDiff ->
when {
diff.p2pkPubkey?.after == null && diff.p2pkPubkey != null -> stringRes(R.string.backup_card_nutzap_key_removed)
diff.p2pkPubkey != null -> stringRes(R.string.backup_card_nutzap_key_replaced)
else -> plural(R.plurals.backup_card_nutzap_mints_removed, diff.mints.removed.size)
diff.p2pkPubkey?.after == null && diff.p2pkPubkey != null -> stringRes(Res.string.backup_card_nutzap_key_removed)
diff.p2pkPubkey != null -> stringRes(Res.string.backup_card_nutzap_key_replaced)
else -> plural(Res.plurals.backup_card_nutzap_mints_removed, diff.mints.removed.size)
}
is PaymentTargetsDiff -> plural(R.plurals.backup_card_payment_targets_removed, diff.targets.removed.size)
is Bolt12OfferListDiff -> plural(R.plurals.backup_card_offers_removed, diff.offers.removed.size)
is CashuWalletDiff -> encryptedHeadline(R.string.backup_card_wallet_emptied, R.string.backup_card_wallet_rewritten, diff.wallet)
is ConcordCommunityListDiff -> encryptedHeadline(R.string.backup_card_concord_emptied, R.string.backup_card_concord_rewritten, diff.communities)
is AppSpecificDataDiff -> stringRes(R.string.backup_card_app_settings)
is PaymentTargetsDiff -> plural(Res.plurals.backup_card_payment_targets_removed, diff.targets.removed.size)
is Bolt12OfferListDiff -> plural(Res.plurals.backup_card_offers_removed, diff.offers.removed.size)
is CashuWalletDiff -> encryptedHeadline(Res.string.backup_card_wallet_emptied, Res.string.backup_card_wallet_rewritten, diff.wallet)
is ConcordCommunityListDiff -> encryptedHeadline(Res.string.backup_card_concord_emptied, Res.string.backup_card_concord_rewritten, diff.communities)
is AppSpecificDataDiff -> stringRes(Res.string.backup_card_app_settings)
else -> null
}
// A headline counting removed items reads wrong when only encrypted items were lost.
if (specific.isNullOrEmpty()) return stringRes(R.string.backup_conflict_title, stringRes(eventTypeName(conflict.eventType)))
if (specific.isNullOrEmpty()) return stringRes(Res.string.backup_conflict_title, stringRes(eventTypeName(conflict.eventType)))
// Headlines lead with the loss; what the other app added to the same list is said too,
// so a list that dropped 3 and gained 40 doesn't read as if it only shrank. The wording
// follows the headline's: following people, muting, joining chats, blocking relays.
@@ -160,11 +203,11 @@ private fun headlineOf(
if (added <= 0) return specific
val tail =
when {
diff is ContactListDiff -> R.plurals.backup_card_and_followed
diff is MuteListDiff -> R.plurals.backup_card_and_muted
diff is ChannelListDiff || diff is CommunityListDiff || diff is EphemeralChatListDiff || diff is SimpleGroupListDiff -> R.plurals.backup_card_and_joined
conflict.eventType == BackupEventType.BLOCKED_RELAYS -> R.plurals.backup_card_and_blocked
else -> R.plurals.backup_card_and_added
diff is ContactListDiff -> Res.plurals.backup_card_and_followed
diff is MuteListDiff -> Res.plurals.backup_card_and_muted
diff is ChannelListDiff || diff is CommunityListDiff || diff is EphemeralChatListDiff || diff is SimpleGroupListDiff -> Res.plurals.backup_card_and_joined
conflict.eventType == BackupEventType.BLOCKED_RELAYS -> Res.plurals.backup_card_and_blocked
else -> Res.plurals.backup_card_and_added
}
return pluralStringResource(tail, added, specific, added)
}
@@ -208,7 +251,7 @@ private fun relayListHasHeadline(type: BackupEventType): Boolean =
/** The count's plural, or "" when nothing of that kind was removed. */
@Composable
private fun plural(
id: Int,
id: PluralStringResource,
count: Int,
): String = if (count > 0) pluralStringResource(id, count, count) else ""
@@ -218,21 +261,21 @@ private fun relayListHeadline(
removed: Int,
): String =
when (type) {
BackupEventType.DM_RELAYS -> plural(R.plurals.backup_card_dm_relays_lost, removed)
BackupEventType.KEY_PACKAGE_RELAYS -> plural(R.plurals.backup_card_key_package_relays_lost, removed)
BackupEventType.SEARCH_RELAYS -> plural(R.plurals.backup_card_search_relays_lost, removed)
BackupEventType.INDEXER_RELAYS -> plural(R.plurals.backup_card_indexer_relays_lost, removed)
BackupEventType.RELAY_FEEDS -> plural(R.plurals.backup_card_relay_feeds_lost, removed)
BackupEventType.PRIVATE_OUTBOX_RELAYS -> plural(R.plurals.backup_card_private_outbox_lost, removed)
BackupEventType.TRUSTED_RELAYS -> plural(R.plurals.backup_card_trusted_lost, removed)
BackupEventType.BLOCKED_RELAYS -> plural(R.plurals.backup_card_unblocked, removed)
BackupEventType.DM_RELAYS -> plural(Res.plurals.backup_card_dm_relays_lost, removed)
BackupEventType.KEY_PACKAGE_RELAYS -> plural(Res.plurals.backup_card_key_package_relays_lost, removed)
BackupEventType.SEARCH_RELAYS -> plural(Res.plurals.backup_card_search_relays_lost, removed)
BackupEventType.INDEXER_RELAYS -> plural(Res.plurals.backup_card_indexer_relays_lost, removed)
BackupEventType.RELAY_FEEDS -> plural(Res.plurals.backup_card_relay_feeds_lost, removed)
BackupEventType.PRIVATE_OUTBOX_RELAYS -> plural(Res.plurals.backup_card_private_outbox_lost, removed)
BackupEventType.TRUSTED_RELAYS -> plural(Res.plurals.backup_card_trusted_lost, removed)
BackupEventType.BLOCKED_RELAYS -> plural(Res.plurals.backup_card_unblocked, removed)
else -> ""
}
@Composable
private fun encryptedHeadline(
emptied: Int,
rewritten: Int,
emptied: StringResource,
rewritten: StringResource,
change: ContentChange,
): String = if (change == ContentChange.CLEARED) stringRes(emptied) else stringRes(rewritten)
@@ -262,7 +305,7 @@ private fun LeadConflictCard(
Column(Modifier.weight(1f)) {
Text(headlineOf(conflict, counts), style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.Bold, maxLines = 3, overflow = TextOverflow.Ellipsis)
Text(
stringRes(R.string.backup_review_changed_by_other_app, timeAgoNoDot(conflict.cause.createdAt)),
stringRes(Res.string.backup_review_changed_by_other_app, timeAgoNoDot(conflict.cause.createdAt)),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.placeholderText,
)
@@ -296,9 +339,9 @@ private fun LeadConflictCard(
private fun CountChips(counts: ConflictCounts) {
val tones = conflictTones()
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
if (counts.removed > 0) StatusTag(stringRes(R.string.backup_conflict_count_removed, counts.removed.toString()), tones.removed)
if (counts.added > 0) StatusTag(stringRes(R.string.backup_conflict_count_added, counts.added.toString()), tones.added)
if (counts.changed > 0) StatusTag(stringRes(R.string.backup_conflict_count_changed, counts.changed.toString()), tones.changed)
if (counts.removed > 0) StatusTag(stringRes(Res.string.backup_conflict_count_removed, counts.removed.toString()), tones.removed)
if (counts.added > 0) StatusTag(stringRes(Res.string.backup_conflict_count_added, counts.added.toString()), tones.added)
if (counts.changed > 0) StatusTag(stringRes(Res.string.backup_conflict_count_changed, counts.changed.toString()), tones.changed)
}
}
@@ -342,6 +385,6 @@ private fun AcceptButton(onAccept: () -> Unit) {
modifier = Modifier.height(36.dp),
contentPadding = PaddingValues(horizontal = 14.dp),
) {
Text(stringRes(R.string.backup_card_accept), fontWeight = FontWeight.SemiBold)
Text(stringRes(Res.string.backup_card_accept), fontWeight = FontWeight.SemiBold)
}
}
@@ -62,7 +62,6 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.model.backups.ReplaceableBackupConflict
@@ -70,6 +69,67 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
import com.vitorpamplona.amethyst.commons.model.navigation.Route
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.backup_entry_hashtag
import com.vitorpamplona.amethyst.commons.resources.backup_entry_mint
import com.vitorpamplona.amethyst.commons.resources.backup_entry_nutzap_key
import com.vitorpamplona.amethyst.commons.resources.backup_entry_person
import com.vitorpamplona.amethyst.commons.resources.backup_entry_profile_field
import com.vitorpamplona.amethyst.commons.resources.backup_entry_thread
import com.vitorpamplona.amethyst.commons.resources.backup_entry_word
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_about
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_banner
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_birthday
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_bot
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_clink_offer
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_display_name
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_lud06
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_lud16
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_name
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_nip05
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_picture
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_pronouns
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_website
import com.vitorpamplona.amethyst.commons.resources.backup_review_demoted_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_dropped_count
import com.vitorpamplona.amethyst.commons.resources.backup_review_dropped_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_gone
import com.vitorpamplona.amethyst.commons.resources.backup_review_inbox
import com.vitorpamplona.amethyst.commons.resources.backup_review_inbox_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_review_joined
import com.vitorpamplona.amethyst.commons.resources.backup_review_kept_count
import com.vitorpamplona.amethyst.commons.resources.backup_review_key_removed
import com.vitorpamplona.amethyst.commons.resources.backup_review_key_replaced
import com.vitorpamplona.amethyst.commons.resources.backup_review_key_warning
import com.vitorpamplona.amethyst.commons.resources.backup_review_left
import com.vitorpamplona.amethyst.commons.resources.backup_review_left_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_names_and_more
import com.vitorpamplona.amethyst.commons.resources.backup_review_new
import com.vitorpamplona.amethyst.commons.resources.backup_review_new_count
import com.vitorpamplona.amethyst.commons.resources.backup_review_new_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_newly_muted
import com.vitorpamplona.amethyst.commons.resources.backup_review_not_set
import com.vitorpamplona.amethyst.commons.resources.backup_review_nothing_here
import com.vitorpamplona.amethyst.commons.resources.backup_review_outbox
import com.vitorpamplona.amethyst.commons.resources.backup_review_outbox_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_review_private_added_body
import com.vitorpamplona.amethyst.commons.resources.backup_review_private_added_title
import com.vitorpamplona.amethyst.commons.resources.backup_review_private_changed_body
import com.vitorpamplona.amethyst.commons.resources.backup_review_private_changed_title
import com.vitorpamplona.amethyst.commons.resources.backup_review_private_wiped_body
import com.vitorpamplona.amethyst.commons.resources.backup_review_private_wiped_title
import com.vitorpamplona.amethyst.commons.resources.backup_review_renamed
import com.vitorpamplona.amethyst.commons.resources.backup_review_renamed_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_saved
import com.vitorpamplona.amethyst.commons.resources.backup_review_search_people
import com.vitorpamplona.amethyst.commons.resources.backup_review_tab_dropped
import com.vitorpamplona.amethyst.commons.resources.backup_review_tab_edited
import com.vitorpamplona.amethyst.commons.resources.backup_review_tab_new
import com.vitorpamplona.amethyst.commons.resources.backup_review_units_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_unmuted
import com.vitorpamplona.amethyst.commons.resources.backup_review_unmuted_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_review_was_named
import com.vitorpamplona.amethyst.commons.resources.backup_review_words_and_hashtags
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings
@@ -111,6 +171,7 @@ import com.vitorpamplona.quartz.nip61Nutzaps.info.tags.NutzapMintTag
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListDiff
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip65RelayList.tags.AdvertisedRelayType
import org.jetbrains.compose.resources.StringResource
private const val GRID_COLUMNS = 4
private const val PEOPLE_PREVIEW = 6
@@ -168,7 +229,7 @@ private fun LazyListScope.followListItems(
item(key = "follow-hero", contentType = "hero") {
val tones = conflictTones()
Column(Pad.padding(top = 8.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
HeroCounts(stringRes(R.string.backup_review_saved), saved, stringRes(R.string.backup_review_new), new)
HeroCounts(stringRes(Res.string.backup_review_saved), saved, stringRes(Res.string.backup_review_new), new)
SplitBar(
listOf(
BarSegment(saved - dropped, tones.kept),
@@ -177,9 +238,9 @@ private fun LazyListScope.followListItems(
),
)
Row(horizontalArrangement = Arrangement.spacedBy(14.dp)) {
LegendItem(tones.kept, stringRes(R.string.backup_review_kept_count, (saved - dropped).toString()))
LegendItem(tones.removed, stringRes(R.string.backup_review_dropped_count, dropped.toString()))
LegendItem(tones.added, stringRes(R.string.backup_review_new_count, gained.toString()))
LegendItem(tones.kept, stringRes(Res.string.backup_review_kept_count, (saved - dropped).toString()))
LegendItem(tones.removed, stringRes(Res.string.backup_review_dropped_count, dropped.toString()))
LegendItem(tones.added, stringRes(Res.string.backup_review_new_count, gained.toString()))
}
}
}
@@ -188,9 +249,9 @@ private fun LazyListScope.followListItems(
ReviewTabs(
labels =
listOf(
stringRes(R.string.backup_review_tab_dropped, dropped.toString()),
stringRes(R.string.backup_review_tab_new, gained.toString()),
stringRes(R.string.backup_review_tab_edited, edited.toString()),
stringRes(Res.string.backup_review_tab_dropped, dropped.toString()),
stringRes(Res.string.backup_review_tab_new, gained.toString()),
stringRes(Res.string.backup_review_tab_edited, edited.toString()),
),
selected = ui.tab,
onSelect = {
@@ -223,7 +284,7 @@ private fun LazyListScope.followListItems(
if (filtered.isEmpty()) {
item(key = "follow-empty", contentType = "note") {
DetailText(stringRes(R.string.backup_review_nothing_here), Pad.padding(vertical = 24.dp))
DetailText(stringRes(Res.string.backup_review_nothing_here), Pad.padding(vertical = 24.dp))
}
}
}
@@ -359,7 +420,7 @@ private fun PeopleSearch(
singleLine = true,
shape = RoundedCornerShape(14.dp),
leadingIcon = { Icon(symbol = MaterialSymbols.Search, contentDescription = null) },
placeholder = { Text(stringRes(R.string.backup_review_search_people, count.toString())) },
placeholder = { Text(stringRes(Res.string.backup_review_search_people, count.toString())) },
)
}
@@ -388,15 +449,15 @@ private fun LazyListScope.muteListItems(
Icon(symbol = MaterialSymbols.Shield, contentDescription = null, tint = tones.removed, modifier = Modifier.size(44.dp))
Column {
Text(
stringRes(R.string.backup_review_unmuted, removed.size.toString()),
stringRes(Res.string.backup_review_unmuted, removed.size.toString()),
fontSize = 30.sp,
lineHeight = 32.sp,
fontWeight = FontWeight.Bold,
color = tones.removed,
)
Text(stringRes(R.string.backup_review_unmuted_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText)
Text(stringRes(Res.string.backup_review_unmuted_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText)
if (added.isNotEmpty()) {
Text(stringRes(R.string.backup_review_newly_muted, added.size.toString()), style = MaterialTheme.typography.bodySmall, color = tones.added)
Text(stringRes(Res.string.backup_review_newly_muted, added.size.toString()), style = MaterialTheme.typography.bodySmall, color = tones.added)
}
}
}
@@ -406,17 +467,17 @@ private fun LazyListScope.muteListItems(
item(key = "mute-tiles", contentType = "tiles") {
val tones = conflictTones()
Row(Pad.padding(top = 12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
StatTile("−${people.size}", stringRes(R.string.backup_entry_person), tones.removed, Modifier.weight(1f))
StatTile("−${words.size}", stringRes(R.string.backup_entry_word), tones.removed, Modifier.weight(1f))
StatTile("−${hashtags.size}", stringRes(R.string.backup_entry_hashtag), tones.removed, Modifier.weight(1f))
StatTile("−${threads.size}", stringRes(R.string.backup_entry_thread), tones.removed, Modifier.weight(1f))
StatTile("−${people.size}", stringRes(Res.string.backup_entry_person), tones.removed, Modifier.weight(1f))
StatTile("−${words.size}", stringRes(Res.string.backup_entry_word), tones.removed, Modifier.weight(1f))
StatTile("−${hashtags.size}", stringRes(Res.string.backup_entry_hashtag), tones.removed, Modifier.weight(1f))
StatTile("−${threads.size}", stringRes(Res.string.backup_entry_thread), tones.removed, Modifier.weight(1f))
}
}
if (people.isNotEmpty()) {
item(key = "mute-people", contentType = "facepile") {
Column(Pad.padding(top = 18.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
SectionCaption(stringRes(R.string.backup_entry_person))
SectionCaption(stringRes(Res.string.backup_entry_person))
FacePile(people, accountViewModel) { ui.showAllPeople = !ui.showAllPeople }
}
}
@@ -427,7 +488,7 @@ private fun LazyListScope.muteListItems(
item(key = "mute-words", contentType = "pills") {
val tones = conflictTones()
Column(Pad.padding(top = 18.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
SectionCaption(stringRes(R.string.backup_review_words_and_hashtags))
SectionCaption(stringRes(Res.string.backup_review_words_and_hashtags))
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
(words.map { "\"$it\"" } + hashtags).forEach { StruckPill(it, tones.removed) }
}
@@ -436,7 +497,7 @@ private fun LazyListScope.muteListItems(
}
if (threads.isNotEmpty()) {
item(key = "mute-threads-caption", contentType = "caption") { SectionCaption(stringRes(R.string.backup_entry_thread), Pad.padding(top = 18.dp)) }
item(key = "mute-threads-caption", contentType = "caption") { SectionCaption(stringRes(Res.string.backup_entry_thread), Pad.padding(top = 18.dp)) }
items(threads, key = { "mute-thread-$it" }, contentType = { "note" }) { id ->
LoadNote(id) { note ->
if (note != null) {
@@ -463,7 +524,7 @@ private fun FacePile(
val names = people.take(2).map { observedName(it, accountViewModel) }
val summary =
if (people.size > 2) {
stringRes(R.string.backup_review_names_and_more, names.joinToString(", "), (people.size - 2).toString())
stringRes(Res.string.backup_review_names_and_more, names.joinToString(", "), (people.size - 2).toString())
} else {
names.joinToString(", ")
}
@@ -564,9 +625,9 @@ internal fun PrivateItemsCard(
val tones = conflictTones()
val (title, body) =
when (change) {
ContentChange.CLEARED -> R.string.backup_review_private_wiped_title to R.string.backup_review_private_wiped_body
ContentChange.ADDED -> R.string.backup_review_private_added_title to R.string.backup_review_private_added_body
else -> R.string.backup_review_private_changed_title to R.string.backup_review_private_changed_body
ContentChange.CLEARED -> Res.string.backup_review_private_wiped_title to Res.string.backup_review_private_wiped_body
ContentChange.ADDED -> Res.string.backup_review_private_added_title to Res.string.backup_review_private_added_body
else -> Res.string.backup_review_private_changed_title to Res.string.backup_review_private_changed_body
}
Row(
modifier
@@ -592,28 +653,28 @@ internal fun PrivateItemsCard(
// ---------------------------------------------------------------------------------------
private class ProfileField(
val labelRes: Int,
val labelRes: StringResource,
val change: ValueChange<String>,
val isImage: Boolean = false,
)
private fun profileFields(diff: MetadataDiff): List<ProfileField> =
listOfNotNull(
diff.picture?.let { ProfileField(R.string.backup_profile_field_picture, it, isImage = true) },
diff.banner?.let { ProfileField(R.string.backup_profile_field_banner, it, isImage = true) },
diff.name?.let { ProfileField(R.string.backup_profile_field_name, it) },
diff.displayName?.let { ProfileField(R.string.backup_profile_field_display_name, it) },
diff.about?.let { ProfileField(R.string.backup_profile_field_about, it) },
diff.nip05?.let { ProfileField(R.string.backup_profile_field_nip05, it) },
diff.website?.let { ProfileField(R.string.backup_profile_field_website, it) },
diff.lud16?.let { ProfileField(R.string.backup_profile_field_lud16, it) },
diff.lud06?.let { ProfileField(R.string.backup_profile_field_lud06, it) },
diff.clinkOffer?.let { ProfileField(R.string.backup_profile_field_clink_offer, it) },
diff.pronouns?.let { ProfileField(R.string.backup_profile_field_pronouns, it) },
diff.bot?.let { ProfileField(R.string.backup_profile_field_bot, ValueChange(it.before?.toString(), it.after?.toString())) },
diff.picture?.let { ProfileField(Res.string.backup_profile_field_picture, it, isImage = true) },
diff.banner?.let { ProfileField(Res.string.backup_profile_field_banner, it, isImage = true) },
diff.name?.let { ProfileField(Res.string.backup_profile_field_name, it) },
diff.displayName?.let { ProfileField(Res.string.backup_profile_field_display_name, it) },
diff.about?.let { ProfileField(Res.string.backup_profile_field_about, it) },
diff.nip05?.let { ProfileField(Res.string.backup_profile_field_nip05, it) },
diff.website?.let { ProfileField(Res.string.backup_profile_field_website, it) },
diff.lud16?.let { ProfileField(Res.string.backup_profile_field_lud16, it) },
diff.lud06?.let { ProfileField(Res.string.backup_profile_field_lud06, it) },
diff.clinkOffer?.let { ProfileField(Res.string.backup_profile_field_clink_offer, it) },
diff.pronouns?.let { ProfileField(Res.string.backup_profile_field_pronouns, it) },
diff.bot?.let { ProfileField(Res.string.backup_profile_field_bot, ValueChange(it.before?.toString(), it.after?.toString())) },
diff.birthday?.let {
ProfileField(
R.string.backup_profile_field_birthday,
Res.string.backup_profile_field_birthday,
ValueChange(
it.before?.let { b -> listOfNotNull(b.year, b.month, b.day).joinToString("-") },
it.after?.let { a -> listOfNotNull(a.year, a.month, a.day).joinToString("-") },
@@ -636,8 +697,8 @@ private fun LazyListScope.profileItems(
item(key = "profile-cards", contentType = "profile-cards") {
val tones = conflictTones()
Row(Pad.padding(top = 8.dp), horizontalArrangement = Arrangement.spacedBy(10.dp)) {
MiniProfileCard(saved, pubKey, stringRes(R.string.backup_review_saved), MaterialTheme.colorScheme.outlineVariant, accountViewModel, Modifier.weight(1f))
MiniProfileCard(incoming, pubKey, stringRes(R.string.backup_review_new), tones.removed.copy(alpha = 0.5f), accountViewModel, Modifier.weight(1f))
MiniProfileCard(saved, pubKey, stringRes(Res.string.backup_review_saved), MaterialTheme.colorScheme.outlineVariant, accountViewModel, Modifier.weight(1f))
MiniProfileCard(incoming, pubKey, stringRes(Res.string.backup_review_new), tones.removed.copy(alpha = 0.5f), accountViewModel, Modifier.weight(1f))
}
}
@@ -656,7 +717,7 @@ private fun LazyListScope.profileItems(
}
}
// Linked identities and fields this app doesn't model use the generic sections.
genericDiffItems(buildRows(presentation, skipGroups = setOf(R.string.backup_entry_profile_field)), accountViewModel, nav)
genericDiffItems(buildRows(presentation, skipGroups = setOf(Res.string.backup_entry_profile_field)), accountViewModel, nav)
}
@Composable
@@ -735,10 +796,10 @@ private fun FlowRowDiff(
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(6.dp), itemVerticalAlignment = Alignment.CenterVertically) {
if (before != null) DiffChip(clip(before), tones.removed, struck = true)
when {
after == null -> StatusTag(stringRes(R.string.backup_review_gone), tones.removed)
after == null -> StatusTag(stringRes(Res.string.backup_review_gone), tones.removed)
before == null -> {
DiffChip(clip(after), tones.added, struck = false)
StatusTag(stringRes(R.string.backup_review_new_tag), tones.added)
StatusTag(stringRes(Res.string.backup_review_new_tag), tones.added)
}
else -> {
Text("→", color = MaterialTheme.colorScheme.placeholderText)
@@ -779,7 +840,7 @@ private fun ImageThumb(
contentAlignment = Alignment.Center,
) {
if (url == null) {
Text(stringRes(R.string.backup_review_not_set), style = MaterialTheme.typography.labelSmall, color = color)
Text(stringRes(Res.string.backup_review_not_set), style = MaterialTheme.typography.labelSmall, color = color)
} else {
MyAsyncImage(
imageUrl = url,
@@ -843,14 +904,14 @@ private fun LazyListScope.nip65Items(
val outbox = lane(saved, new, writes)
val inbox = lane(saved, new, reads)
laneItems("outbox", stringRes = R.string.backup_review_outbox, subRes = R.string.backup_review_outbox_explainer, rows = outbox, nav = nav)
laneItems("inbox", stringRes = R.string.backup_review_inbox, subRes = R.string.backup_review_inbox_explainer, rows = inbox, nav = nav)
laneItems("outbox", stringRes = Res.string.backup_review_outbox, subRes = Res.string.backup_review_outbox_explainer, rows = outbox, nav = nav)
laneItems("inbox", stringRes = Res.string.backup_review_inbox, subRes = Res.string.backup_review_inbox_explainer, rows = inbox, nav = nav)
}
private fun LazyListScope.laneItems(
key: String,
stringRes: Int,
subRes: Int,
stringRes: StringResource,
subRes: StringResource,
rows: List<LaneRow>,
nav: INav,
) {
@@ -873,9 +934,9 @@ private fun LaneRelayRow(
val (color, tag) =
when (row.state) {
LaneState.KEPT -> tones.kept to null
LaneState.DROPPED -> tones.removed to R.string.backup_review_dropped_tag
LaneState.ADDED -> tones.added to R.string.backup_review_new_tag
LaneState.DEMOTED -> tones.changed to R.string.backup_review_demoted_tag
LaneState.DROPPED -> tones.removed to Res.string.backup_review_dropped_tag
LaneState.ADDED -> tones.added to Res.string.backup_review_new_tag
LaneState.DEMOTED -> tones.changed to Res.string.backup_review_demoted_tag
}
Row(
Pad
@@ -933,12 +994,12 @@ private fun LazyListScope.nutzapItems(
) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Icon(symbol = MaterialSymbols.Key, contentDescription = null, tint = tones.changed)
Text(stringRes(if (key.after == null) R.string.backup_review_key_removed else R.string.backup_review_key_replaced), style = MaterialTheme.typography.titleSmall)
Text(stringRes(if (key.after == null) Res.string.backup_review_key_removed else Res.string.backup_review_key_replaced), style = MaterialTheme.typography.titleSmall)
}
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
KeyColumn(key.before, stringRes(R.string.backup_review_saved), tones.kept)
KeyColumn(key.before, stringRes(Res.string.backup_review_saved), tones.kept)
Text("→", fontSize = 24.sp, color = MaterialTheme.colorScheme.placeholderText, modifier = Modifier.padding(horizontal = 18.dp))
KeyColumn(key.after, stringRes(R.string.backup_review_new), tones.changed)
KeyColumn(key.after, stringRes(Res.string.backup_review_new), tones.changed)
}
if (key.after != null) {
Row(
@@ -950,7 +1011,7 @@ private fun LazyListScope.nutzapItems(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Icon(symbol = MaterialSymbols.Warning, contentDescription = null, tint = tones.changed, modifier = Modifier.size(18.dp))
Text(stringRes(R.string.backup_review_key_warning), style = MaterialTheme.typography.bodySmall, color = tones.changed)
Text(stringRes(Res.string.backup_review_key_warning), style = MaterialTheme.typography.bodySmall, color = tones.changed)
}
}
}
@@ -976,10 +1037,10 @@ private fun LazyListScope.nutzapItems(
} + diff.mints.added.map { Triple(it.mintUrl, it.units.joinToString(), LaneState.ADDED) }
if (mintRows.isNotEmpty()) {
item(key = "nutzap-mints", contentType = "caption") { SectionCaption(stringRes(R.string.backup_entry_mint), Pad.padding(top = 18.dp, bottom = 6.dp)) }
item(key = "nutzap-mints", contentType = "caption") { SectionCaption(stringRes(Res.string.backup_entry_mint), Pad.padding(top = 18.dp, bottom = 6.dp)) }
items(mintRows, key = { "mint-" + it.first }, contentType = { "mint" }) { (url, units, state) -> MintRow(url, units, state) }
}
genericDiffItems(buildRows(presentation, skipGroups = setOf(R.string.backup_entry_mint, R.string.backup_entry_nutzap_key)), accountViewModel, nav)
genericDiffItems(buildRows(presentation, skipGroups = setOf(Res.string.backup_entry_mint, Res.string.backup_entry_nutzap_key)), accountViewModel, nav)
}
@Composable
@@ -993,7 +1054,7 @@ private fun KeyColumn(
KeyFingerprint(hex, color)
} else {
Box(Modifier.size(96.dp).clip(RoundedCornerShape(18.dp)).border(2.dp, color, RoundedCornerShape(18.dp)), contentAlignment = Alignment.Center) {
Text(stringRes(R.string.backup_review_not_set), style = MaterialTheme.typography.labelSmall, color = color)
Text(stringRes(Res.string.backup_review_not_set), style = MaterialTheme.typography.labelSmall, color = color)
}
}
StatusTag(label, color)
@@ -1010,9 +1071,9 @@ private fun MintRow(
val (color, tag) =
when (state) {
LaneState.KEPT -> MaterialTheme.colorScheme.onSurface to null
LaneState.DROPPED -> tones.removed to R.string.backup_review_dropped_tag
LaneState.ADDED -> tones.added to R.string.backup_review_new_tag
LaneState.DEMOTED -> tones.changed to R.string.backup_review_units_tag
LaneState.DROPPED -> tones.removed to Res.string.backup_review_dropped_tag
LaneState.ADDED -> tones.added to Res.string.backup_review_new_tag
LaneState.DEMOTED -> tones.changed to Res.string.backup_review_units_tag
}
Row(
Pad
@@ -1083,21 +1144,21 @@ private fun LazyListScope.groupItems(
StatTile(
diff.groups.removed.size
.toString(),
stringRes(R.string.backup_review_left),
stringRes(Res.string.backup_review_left),
tones.removed,
Modifier.weight(1f),
)
StatTile(
diff.groups.added.size
.toString(),
stringRes(R.string.backup_review_joined),
stringRes(Res.string.backup_review_joined),
tones.added,
Modifier.weight(1f),
)
StatTile(
diff.groups.changed.size
.toString(),
stringRes(R.string.backup_review_renamed),
stringRes(Res.string.backup_review_renamed),
tones.changed,
Modifier.weight(1f),
)
@@ -1165,7 +1226,7 @@ private fun GroupTileContent(
Column {
Text(name, style = MaterialTheme.typography.titleSmall, maxLines = 1, overflow = TextOverflow.Ellipsis)
Text(
tile.oldName?.let { stringRes(R.string.backup_review_was_named, it) } ?: tile.group.relayUrl
tile.oldName?.let { stringRes(Res.string.backup_review_was_named, it) } ?: tile.group.relayUrl
.removePrefix("wss://")
.removeSuffix("/"),
style = MaterialTheme.typography.labelSmall,
@@ -1176,9 +1237,9 @@ private fun GroupTileContent(
}
}
when (tile.state) {
LaneState.DROPPED -> StatusTag(stringRes(R.string.backup_review_left_tag), tones.removed, Modifier.align(Alignment.TopEnd))
LaneState.ADDED -> StatusTag(stringRes(R.string.backup_review_new_tag), tones.added, Modifier.align(Alignment.TopEnd))
LaneState.DEMOTED -> StatusTag(stringRes(R.string.backup_review_renamed_tag), tones.changed, Modifier.align(Alignment.TopEnd))
LaneState.DROPPED -> StatusTag(stringRes(Res.string.backup_review_left_tag), tones.removed, Modifier.align(Alignment.TopEnd))
LaneState.ADDED -> StatusTag(stringRes(Res.string.backup_review_new_tag), tones.added, Modifier.align(Alignment.TopEnd))
LaneState.DEMOTED -> StatusTag(stringRes(Res.string.backup_review_renamed_tag), tones.changed, Modifier.align(Alignment.TopEnd))
LaneState.KEPT -> {}
}
}
@@ -48,13 +48,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
@@ -63,6 +61,53 @@ import com.vitorpamplona.amethyst.commons.model.backups.ReplaceableBackupConflic
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
import com.vitorpamplona.amethyst.commons.model.navigation.Route
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.backup_review_allowed_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_concord_body
import com.vitorpamplona.amethyst.commons.resources.backup_review_demoted_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_dropped_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_encrypted_added
import com.vitorpamplona.amethyst.commons.resources.backup_review_encrypted_changed
import com.vitorpamplona.amethyst.commons.resources.backup_review_encrypted_cleared
import com.vitorpamplona.amethyst.commons.resources.backup_review_feed
import com.vitorpamplona.amethyst.commons.resources.backup_review_hashtags_you_follow
import com.vitorpamplona.amethyst.commons.resources.backup_review_joined
import com.vitorpamplona.amethyst.commons.resources.backup_review_kept
import com.vitorpamplona.amethyst.commons.resources.backup_review_left
import com.vitorpamplona.amethyst.commons.resources.backup_review_left_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_listed_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_moved_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_new
import com.vitorpamplona.amethyst.commons.resources.backup_review_new_count_caption
import com.vitorpamplona.amethyst.commons.resources.backup_review_new_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_newly_blocked
import com.vitorpamplona.amethyst.commons.resources.backup_review_no_longer_blocked
import com.vitorpamplona.amethyst.commons.resources.backup_review_offer
import com.vitorpamplona.amethyst.commons.resources.backup_review_offers_gone
import com.vitorpamplona.amethyst.commons.resources.backup_review_payment_targets_gone
import com.vitorpamplona.amethyst.commons.resources.backup_review_payments_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_review_places_gone
import com.vitorpamplona.amethyst.commons.resources.backup_review_places_you_follow
import com.vitorpamplona.amethyst.commons.resources.backup_review_receiving_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_saved
import com.vitorpamplona.amethyst.commons.resources.backup_review_searching_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_service
import com.vitorpamplona.amethyst.commons.resources.backup_review_service_followers
import com.vitorpamplona.amethyst.commons.resources.backup_review_service_rank
import com.vitorpamplona.amethyst.commons.resources.backup_review_service_topics
import com.vitorpamplona.amethyst.commons.resources.backup_review_services_changed
import com.vitorpamplona.amethyst.commons.resources.backup_review_services_changed_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_review_still_blocked
import com.vitorpamplona.amethyst.commons.resources.backup_review_storing_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_topics_gone
import com.vitorpamplona.amethyst.commons.resources.backup_review_trust_warning
import com.vitorpamplona.amethyst.commons.resources.backup_review_trusted_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_unblocked
import com.vitorpamplona.amethyst.commons.resources.backup_review_unblocked_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_review_untrusted_tag
import com.vitorpamplona.amethyst.commons.resources.backup_review_wallet_body
import com.vitorpamplona.amethyst.commons.resources.backup_type_communities
import com.vitorpamplona.amethyst.commons.resources.backup_type_public_chats
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings
@@ -118,6 +163,9 @@ import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListEve
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListDiff
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListEvent
import org.jetbrains.compose.resources.PluralStringResource
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.pluralStringResource
/**
* Per-event layouts for the account's other lists: each relay list framed by what it is
@@ -142,8 +190,8 @@ internal fun LazyListScope.listDiffItems(
is TrustProviderListDiff -> trustProviderItems(diff, conflict, accountViewModel, nav)
is PaymentTargetsDiff -> paymentTargetItems(diff, conflict)
is Bolt12OfferListDiff -> offerItems(diff, conflict)
is CashuWalletDiff -> encryptedOnlyItems(MaterialSymbols.AccountBalanceWallet, diff.wallet, R.string.backup_review_wallet_body)
is ConcordCommunityListDiff -> encryptedOnlyItems(MaterialSymbols.Groups, diff.communities, R.string.backup_review_concord_body)
is CashuWalletDiff -> encryptedOnlyItems(MaterialSymbols.AccountBalanceWallet, diff.wallet, Res.string.backup_review_wallet_body)
is ConcordCommunityListDiff -> encryptedOnlyItems(MaterialSymbols.Groups, diff.communities, Res.string.backup_review_concord_body)
else -> return false
}
return true
@@ -193,15 +241,15 @@ private fun fateColor(fate: ItemFate): Color {
private fun <T> LazyListScope.countTiles(
items: List<Pair<T, ItemFate>>,
removedCaption: Int,
addedCaption: Int,
removedCaption: StringResource,
addedCaption: StringResource,
) {
item(key = "count-tiles", contentType = "tiles") {
val tones = conflictTones()
Row(Pad.padding(top = 8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
StatTile(items.count { it.second == ItemFate.DROPPED }.toString(), stringRes(removedCaption), tones.removed, Modifier.weight(1f))
StatTile(items.count { it.second == ItemFate.ADDED }.toString(), stringRes(addedCaption), tones.added, Modifier.weight(1f))
StatTile(items.count { it.second == ItemFate.KEPT || it.second == ItemFate.CHANGED }.toString(), stringRes(R.string.backup_review_kept), tones.kept, Modifier.weight(1f))
StatTile(items.count { it.second == ItemFate.KEPT || it.second == ItemFate.CHANGED }.toString(), stringRes(Res.string.backup_review_kept), tones.kept, Modifier.weight(1f))
}
}
}
@@ -218,20 +266,20 @@ private fun LazyListScope.privateItemsCard(change: ContentChange) {
/** How a relay list's rows are presented: its icon and what a kept or dropped relay is called. */
private class RelayFraming(
val icon: MaterialSymbol,
val keptTag: Int,
val droppedTag: Int = R.string.backup_review_dropped_tag,
val keptTag: StringResource,
val droppedTag: StringResource = Res.string.backup_review_dropped_tag,
)
private fun framingOf(type: BackupEventType): RelayFraming =
when (type) {
BackupEventType.DM_RELAYS -> RelayFraming(MaterialSymbols.Mail, R.string.backup_review_receiving_tag)
BackupEventType.KEY_PACKAGE_RELAYS -> RelayFraming(MaterialSymbols.Key, R.string.backup_review_listed_tag)
BackupEventType.SEARCH_RELAYS -> RelayFraming(MaterialSymbols.Search, R.string.backup_review_searching_tag)
BackupEventType.INDEXER_RELAYS -> RelayFraming(MaterialSymbols.Dns, R.string.backup_review_listed_tag)
BackupEventType.RELAY_FEEDS -> RelayFraming(MaterialSymbols.CellTower, R.string.backup_review_listed_tag)
BackupEventType.PRIVATE_OUTBOX_RELAYS -> RelayFraming(MaterialSymbols.Lock, R.string.backup_review_storing_tag)
BackupEventType.TRUSTED_RELAYS -> RelayFraming(MaterialSymbols.Shield, R.string.backup_review_trusted_tag, R.string.backup_review_untrusted_tag)
else -> RelayFraming(MaterialSymbols.Dns, R.string.backup_review_listed_tag)
BackupEventType.DM_RELAYS -> RelayFraming(MaterialSymbols.Mail, Res.string.backup_review_receiving_tag)
BackupEventType.KEY_PACKAGE_RELAYS -> RelayFraming(MaterialSymbols.Key, Res.string.backup_review_listed_tag)
BackupEventType.SEARCH_RELAYS -> RelayFraming(MaterialSymbols.Search, Res.string.backup_review_searching_tag)
BackupEventType.INDEXER_RELAYS -> RelayFraming(MaterialSymbols.Dns, Res.string.backup_review_listed_tag)
BackupEventType.RELAY_FEEDS -> RelayFraming(MaterialSymbols.CellTower, Res.string.backup_review_listed_tag)
BackupEventType.PRIVATE_OUTBOX_RELAYS -> RelayFraming(MaterialSymbols.Lock, Res.string.backup_review_storing_tag)
BackupEventType.TRUSTED_RELAYS -> RelayFraming(MaterialSymbols.Shield, Res.string.backup_review_trusted_tag, Res.string.backup_review_untrusted_tag)
else -> RelayFraming(MaterialSymbols.Dns, Res.string.backup_review_listed_tag)
}
private fun savedRelaysOf(event: Event): List<NormalizedRelayUrl> =
@@ -277,8 +325,8 @@ private fun RelayFateRow(
when (fate) {
ItemFate.KEPT -> framing.keptTag
ItemFate.DROPPED -> framing.droppedTag
ItemFate.ADDED -> R.string.backup_review_new_tag
ItemFate.CHANGED -> R.string.backup_review_demoted_tag
ItemFate.ADDED -> Res.string.backup_review_new_tag
ItemFate.CHANGED -> Res.string.backup_review_demoted_tag
}
Row(
Pad
@@ -320,14 +368,14 @@ private fun LazyListScope.blockedRelayItems(
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Icon(symbol = MaterialSymbols.LockOpen, contentDescription = null, tint = tones.changed, modifier = Modifier.size(44.dp))
Column {
Text(stringRes(R.string.backup_review_unblocked, unblocked.size.toString()), fontSize = 30.sp, lineHeight = 32.sp, fontWeight = FontWeight.Bold, color = tones.changed)
Text(stringRes(R.string.backup_review_unblocked_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText)
Text(stringRes(Res.string.backup_review_unblocked, unblocked.size.toString()), fontSize = 30.sp, lineHeight = 32.sp, fontWeight = FontWeight.Bold, color = tones.changed)
Text(stringRes(Res.string.backup_review_unblocked_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText)
}
}
}
}
if (unblocked.isNotEmpty()) {
item(key = "unblocked-caption", contentType = "caption") { SectionCaption(stringRes(R.string.backup_review_no_longer_blocked), Pad.padding(top = 18.dp)) }
item(key = "unblocked-caption", contentType = "caption") { SectionCaption(stringRes(Res.string.backup_review_no_longer_blocked), Pad.padding(top = 18.dp)) }
items(unblocked, key = { "unblocked-" + it.url }, contentType = { "relay-row" }) { url ->
val tones = conflictTones()
Row(
@@ -346,12 +394,12 @@ private fun LazyListScope.blockedRelayItems(
Icon(symbol = MaterialSymbols.LockOpen, contentDescription = null, tint = tones.changed, modifier = Modifier.size(18.dp))
}
Text(url.url.removePrefix("wss://").removeSuffix("/"), modifier = Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.SemiBold, maxLines = 1, overflow = TextOverflow.Ellipsis)
StatusTag(stringRes(R.string.backup_review_allowed_tag), tones.changed)
StatusTag(stringRes(Res.string.backup_review_allowed_tag), tones.changed)
}
}
}
if (newlyBlocked.isNotEmpty()) {
item(key = "newly-blocked-caption", contentType = "caption") { SectionCaption(stringRes(R.string.backup_review_newly_blocked), Pad.padding(top = 18.dp)) }
item(key = "newly-blocked-caption", contentType = "caption") { SectionCaption(stringRes(Res.string.backup_review_newly_blocked), Pad.padding(top = 18.dp)) }
items(newlyBlocked, key = { "newly-blocked-" + it.url }, contentType = { "relay-row" }) { url ->
RelayFateRow(url, ItemFate.ADDED, framingOf(BackupEventType.BLOCKED_RELAYS), nav)
}
@@ -359,7 +407,7 @@ private fun LazyListScope.blockedRelayItems(
if (stillBlocked.isNotEmpty()) {
item(key = "still-blocked", contentType = "chips") {
Column(Pad.padding(top = 18.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
SectionCaption(stringRes(R.string.backup_review_still_blocked))
SectionCaption(stringRes(Res.string.backup_review_still_blocked))
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
stillBlocked.forEach { url ->
Row(
@@ -392,7 +440,7 @@ private fun <T> LazyListScope.spaceGrid(
key: (T) -> String,
content: @Composable (T, ItemFate, Modifier) -> Unit,
) {
countTiles(items, R.string.backup_review_left, R.string.backup_review_joined)
countTiles(items, Res.string.backup_review_left, Res.string.backup_review_joined)
items(items.chunked(2), key = { keyPrefix + key(it.first().first) }, contentType = { "space-row" }) { row ->
Row(Pad.padding(top = 10.dp).fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) {
row.forEach { (item, fate) -> content(item, fate, Modifier.weight(1f)) }
@@ -436,9 +484,9 @@ private fun SpaceTile(
}
val tag =
when (fate) {
ItemFate.DROPPED -> R.string.backup_review_left_tag to tones.removed
ItemFate.ADDED -> R.string.backup_review_new_tag to tones.added
ItemFate.CHANGED -> R.string.backup_review_moved_tag to tones.changed
ItemFate.DROPPED -> Res.string.backup_review_left_tag to tones.removed
ItemFate.ADDED -> Res.string.backup_review_new_tag to tones.added
ItemFate.CHANGED -> Res.string.backup_review_moved_tag to tones.changed
ItemFate.KEPT -> null
}
tag?.let { (res, color) -> StatusTag(stringRes(res), color, Modifier.align(Alignment.TopEnd)) }
@@ -459,7 +507,7 @@ private fun LazyListScope.publicChatItems(
val current = state?.channel ?: channel
SpaceTile(
name = current.toBestDisplayName(),
subtitle = stringRes(R.string.backup_type_public_chats),
subtitle = stringRes(Res.string.backup_type_public_chats),
robot = tag.eventId,
picture = channel.profilePicture(),
fate = fate,
@@ -525,7 +573,7 @@ private fun AddressableSpaceTile(
}
SpaceTile(
name = name ?: address.dTag.ifBlank { address.toValue().toShortDisplay() },
subtitle = stringRes(if (address.kind == AppDefinitionEvent.KIND) R.string.backup_review_feed else R.string.backup_type_communities),
subtitle = stringRes(if (address.kind == AppDefinitionEvent.KIND) Res.string.backup_review_feed else Res.string.backup_type_communities),
robot = address.toValue(),
picture = picture,
fate = fate,
@@ -543,7 +591,7 @@ private fun LazyListScope.ephemeralRoomItems(
) {
val saved = (conflict.saved as? EphemeralChatListEvent)?.publicRooms().orEmpty()
val rooms = fates(saved, diff.rooms) { it }
countTiles(rooms, R.string.backup_review_left, R.string.backup_review_joined)
countTiles(rooms, Res.string.backup_review_left, Res.string.backup_review_joined)
items(rooms, key = { "room-" + it.first.toKey() }, contentType = { "room-row" }) { (room, fate) -> RoomRow(room, fate, nav) }
privateItemsCard(diff.privateItems)
}
@@ -582,8 +630,8 @@ private fun RoomRow(
)
}
when (fate) {
ItemFate.DROPPED -> StatusTag(stringRes(R.string.backup_review_left_tag), color)
ItemFate.ADDED -> StatusTag(stringRes(R.string.backup_review_new_tag), color)
ItemFate.DROPPED -> StatusTag(stringRes(Res.string.backup_review_left_tag), color)
ItemFate.ADDED -> StatusTag(stringRes(Res.string.backup_review_new_tag), color)
else -> {}
}
}
@@ -602,11 +650,11 @@ private fun LazyListScope.topicItems(
nav: INav,
) {
val topics = fates(saved, diff) { it.lowercase() }
countTiles(topics, if (isPlace) R.string.backup_review_places_gone else R.string.backup_review_topics_gone, R.string.backup_review_new_count_caption)
countTiles(topics, if (isPlace) Res.string.backup_review_places_gone else Res.string.backup_review_topics_gone, Res.string.backup_review_new_count_caption)
item(key = "topic-cloud", contentType = "cloud") {
val tones = conflictTones()
Column(Pad.padding(top = 18.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
SectionCaption(stringRes(if (isPlace) R.string.backup_review_places_you_follow else R.string.backup_review_hashtags_you_follow))
SectionCaption(stringRes(if (isPlace) Res.string.backup_review_places_you_follow else Res.string.backup_review_hashtags_you_follow))
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), itemVerticalAlignment = Alignment.CenterVertically) {
topics.forEach { (topic, fate) ->
val color = fateColor(fate)
@@ -706,15 +754,15 @@ private fun LazyListScope.trustProviderItems(
Icon(symbol = MaterialSymbols.Group, contentDescription = null, tint = tones.changed)
}
Column {
Text(stringRes(R.string.backup_review_services_changed, affected.toString(), services.size.toString()), fontSize = 24.sp, lineHeight = 28.sp, fontWeight = FontWeight.Bold)
Text(stringRes(R.string.backup_review_services_changed_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText)
Text(stringRes(Res.string.backup_review_services_changed, affected.toString(), services.size.toString()), fontSize = 24.sp, lineHeight = 28.sp, fontWeight = FontWeight.Bold)
Text(stringRes(Res.string.backup_review_services_changed_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText)
}
}
}
}
item(key = "trust-header", contentType = "table-header") {
Row(Pad.padding(top = 16.dp, bottom = 4.dp).padding(horizontal = 12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
listOf(R.string.backup_review_service, R.string.backup_review_saved, R.string.backup_review_new).forEachIndexed { index, res ->
listOf(Res.string.backup_review_service, Res.string.backup_review_saved, Res.string.backup_review_new).forEachIndexed { index, res ->
Text(stringRes(res).uppercase(), Modifier.weight(if (index == 0) 1.1f else 1f), style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.placeholderText)
}
}
@@ -732,7 +780,7 @@ private fun LazyListScope.trustProviderItems(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Icon(symbol = MaterialSymbols.Warning, contentDescription = null, tint = tones.changed, modifier = Modifier.size(18.dp))
Text(stringRes(R.string.backup_review_trust_warning), style = MaterialTheme.typography.bodySmall, color = tones.changed)
Text(stringRes(Res.string.backup_review_trust_warning), style = MaterialTheme.typography.bodySmall, color = tones.changed)
}
}
privateItemsCard(diff.privateItems)
@@ -771,9 +819,9 @@ private fun ServiceTableRow(
@Composable
private fun serviceLabel(type: String): String =
when (type) {
"rank" -> stringRes(R.string.backup_review_service_rank)
"followers" -> stringRes(R.string.backup_review_service_followers)
"t" -> stringRes(R.string.backup_review_service_topics)
"rank" -> stringRes(Res.string.backup_review_service_rank)
"followers" -> stringRes(Res.string.backup_review_service_followers)
"t" -> stringRes(Res.string.backup_review_service_topics)
else -> type.replace('_', ' ').replaceFirstChar { it.uppercase() }
}
@@ -826,7 +874,7 @@ private fun LazyListScope.paymentTargetItems(
) {
val saved = (conflict.saved as? PaymentTargetsEvent)?.paymentTargets().orEmpty()
val targets = fates(saved, diff.targets) { it }
moneyHero(targets.count { it.second == ItemFate.DROPPED }, R.plurals.backup_review_payment_targets_gone)
moneyHero(targets.count { it.second == ItemFate.DROPPED }, Res.plurals.backup_review_payment_targets_gone)
items(targets, key = { "target-" + it.first.type + ":" + it.first.authority }, contentType = { "money-row" }) { (target, fate) -> PaymentTargetRow(target, fate) }
}
@@ -836,15 +884,15 @@ private fun LazyListScope.offerItems(
) {
val saved = (conflict.saved as? Bolt12OfferListEvent)?.offers().orEmpty()
val offers = fates(saved, diff.offers) { it }
moneyHero(offers.count { it.second == ItemFate.DROPPED }, R.plurals.backup_review_offers_gone)
moneyHero(offers.count { it.second == ItemFate.DROPPED }, Res.plurals.backup_review_offers_gone)
items(offers, key = { "offer-" + it.first }, contentType = { "money-row" }) { (offer, fate) ->
MoneyRow(stringRes(R.string.backup_review_offer), offer.toShortDisplay(prefixSize = 4), fate)
MoneyRow(stringRes(Res.string.backup_review_offer), offer.toShortDisplay(prefixSize = 4), fate)
}
}
private fun LazyListScope.moneyHero(
dropped: Int,
headline: Int,
headline: PluralStringResource,
) {
item(key = "money-hero", contentType = "hero") {
val tones = conflictTones()
@@ -856,7 +904,7 @@ private fun LazyListScope.moneyHero(
}
Column {
Text(pluralStringResource(headline, dropped, dropped), fontSize = 24.sp, lineHeight = 28.sp, fontWeight = FontWeight.Bold, color = color)
Text(stringRes(R.string.backup_review_payments_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText)
Text(stringRes(Res.string.backup_review_payments_explainer), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.placeholderText)
}
}
}
@@ -902,8 +950,8 @@ private fun MoneyRow(
)
}
when (fate) {
ItemFate.DROPPED -> StatusTag(stringRes(R.string.backup_review_dropped_tag), color)
ItemFate.ADDED -> StatusTag(stringRes(R.string.backup_review_new_tag), color)
ItemFate.DROPPED -> StatusTag(stringRes(Res.string.backup_review_dropped_tag), color)
ItemFate.ADDED -> StatusTag(stringRes(Res.string.backup_review_new_tag), color)
else -> {}
}
}
@@ -916,15 +964,15 @@ private fun MoneyRow(
private fun LazyListScope.encryptedOnlyItems(
icon: MaterialSymbol,
change: ContentChange,
body: Int,
body: StringResource,
) {
item(key = "encrypted-hero", contentType = "hero") {
val tones = conflictTones()
val (color, headline) =
when (change) {
ContentChange.CLEARED -> tones.removed to R.string.backup_review_encrypted_cleared
ContentChange.ADDED -> tones.added to R.string.backup_review_encrypted_added
else -> tones.changed to R.string.backup_review_encrypted_changed
ContentChange.CLEARED -> tones.removed to Res.string.backup_review_encrypted_cleared
ContentChange.ADDED -> tones.added to Res.string.backup_review_encrypted_added
else -> tones.changed to Res.string.backup_review_encrypted_changed
}
Column(Pad.padding(top = 8.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) {
TintedPanel(color.copy(alpha = 0.12f)) {
@@ -23,9 +23,98 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.backups
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalResources
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.model.backups.BackupEventType
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_relay_read_only
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_relay_read_write
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_relay_write_only
import com.vitorpamplona.amethyst.commons.resources.backup_entry_algo_feed
import com.vitorpamplona.amethyst.commons.resources.backup_entry_chat_room
import com.vitorpamplona.amethyst.commons.resources.backup_entry_community
import com.vitorpamplona.amethyst.commons.resources.backup_entry_hashtag
import com.vitorpamplona.amethyst.commons.resources.backup_entry_identity_claim
import com.vitorpamplona.amethyst.commons.resources.backup_entry_location
import com.vitorpamplona.amethyst.commons.resources.backup_entry_mint
import com.vitorpamplona.amethyst.commons.resources.backup_entry_nutzap_key
import com.vitorpamplona.amethyst.commons.resources.backup_entry_offer
import com.vitorpamplona.amethyst.commons.resources.backup_entry_other
import com.vitorpamplona.amethyst.commons.resources.backup_entry_payment_target
import com.vitorpamplona.amethyst.commons.resources.backup_entry_person
import com.vitorpamplona.amethyst.commons.resources.backup_entry_profile_field
import com.vitorpamplona.amethyst.commons.resources.backup_entry_public_chat
import com.vitorpamplona.amethyst.commons.resources.backup_entry_relay
import com.vitorpamplona.amethyst.commons.resources.backup_entry_relay_group
import com.vitorpamplona.amethyst.commons.resources.backup_entry_thread
import com.vitorpamplona.amethyst.commons.resources.backup_entry_trust_provider
import com.vitorpamplona.amethyst.commons.resources.backup_entry_word
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_about
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_banner
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_birthday
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_bot
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_clink_offer
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_display_name
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_lud06
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_lud16
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_name
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_nip05
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_picture
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_pronouns
import com.vitorpamplona.amethyst.commons.resources.backup_profile_field_website
import com.vitorpamplona.amethyst.commons.resources.backup_type_app_settings
import com.vitorpamplona.amethyst.commons.resources.backup_type_app_settings_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_blocked_relays
import com.vitorpamplona.amethyst.commons.resources.backup_type_blocked_relays_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_bolt12_offers
import com.vitorpamplona.amethyst.commons.resources.backup_type_bolt12_offers_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_cashu_wallet
import com.vitorpamplona.amethyst.commons.resources.backup_type_cashu_wallet_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_communities
import com.vitorpamplona.amethyst.commons.resources.backup_type_communities_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_concord_communities
import com.vitorpamplona.amethyst.commons.resources.backup_type_concord_communities_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_dm_relays
import com.vitorpamplona.amethyst.commons.resources.backup_type_dm_relays_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_ephemeral_chats
import com.vitorpamplona.amethyst.commons.resources.backup_type_ephemeral_chats_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_favorite_algo_feeds
import com.vitorpamplona.amethyst.commons.resources.backup_type_favorite_algo_feeds_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_follow_list
import com.vitorpamplona.amethyst.commons.resources.backup_type_follow_list_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_geohashes
import com.vitorpamplona.amethyst.commons.resources.backup_type_geohashes_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_hashtags
import com.vitorpamplona.amethyst.commons.resources.backup_type_hashtags_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_indexer_relays
import com.vitorpamplona.amethyst.commons.resources.backup_type_indexer_relays_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_key_package_relays
import com.vitorpamplona.amethyst.commons.resources.backup_type_key_package_relays_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_mute_list
import com.vitorpamplona.amethyst.commons.resources.backup_type_mute_list_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_nutzap_info
import com.vitorpamplona.amethyst.commons.resources.backup_type_nutzap_info_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_other
import com.vitorpamplona.amethyst.commons.resources.backup_type_other_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_outbox_inbox_relays
import com.vitorpamplona.amethyst.commons.resources.backup_type_outbox_inbox_relays_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_payment_targets
import com.vitorpamplona.amethyst.commons.resources.backup_type_payment_targets_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_private_outbox_relays
import com.vitorpamplona.amethyst.commons.resources.backup_type_private_outbox_relays_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_profile
import com.vitorpamplona.amethyst.commons.resources.backup_type_profile_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_public_chats
import com.vitorpamplona.amethyst.commons.resources.backup_type_public_chats_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_relay_feeds
import com.vitorpamplona.amethyst.commons.resources.backup_type_relay_feeds_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_relay_groups
import com.vitorpamplona.amethyst.commons.resources.backup_type_relay_groups_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_search_relays
import com.vitorpamplona.amethyst.commons.resources.backup_type_search_relays_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_trust_providers
import com.vitorpamplona.amethyst.commons.resources.backup_type_trust_providers_explainer
import com.vitorpamplona.amethyst.commons.resources.backup_type_trusted_relays
import com.vitorpamplona.amethyst.commons.resources.backup_type_trusted_relays_explainer
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityListDiff
import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId
import com.vitorpamplona.quartz.experimental.ephemChat.list.EphemeralChatListDiff
@@ -61,6 +150,7 @@ import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListDiff
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataDiff
import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListDiff
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.offer.Bolt12OfferListDiff
import org.jetbrains.compose.resources.StringResource
private const val MAX_VALUE_LENGTH = 80
@@ -168,7 +258,7 @@ sealed interface ReviewItem {
/** One labelled group of differences (people, relays, profile fields…) of a conflict. */
@Immutable
class DiffGroup(
val label: Int,
val label: StringResource,
val removed: List<ReviewItem>,
val added: List<ReviewItem>,
val changed: List<ReviewItem>,
@@ -189,7 +279,7 @@ class DiffPresentation(
/** A group from a [ListDiff] of an event's parsed items. */
private fun <T> listGroup(
label: Int,
label: StringResource,
diff: ListDiff<T>,
item: (T) -> ReviewItem,
change: (ItemChange<T>) -> ReviewItem,
@@ -197,7 +287,7 @@ private fun <T> listGroup(
/** A group whose items can't change, only be added or removed (hashtags, plain relays…). */
private fun <T> listGroup(
label: Int,
label: StringResource,
diff: ListDiff<T>,
item: (T) -> ReviewItem,
) = listGroup(label, diff, item) { item(it.after) }
@@ -212,7 +302,7 @@ private inline fun <reified T> ListDiff<*>.only() =
/** A group from single-valued fields: a field set only after is added, only before is removed. */
private fun fieldGroup(
label: Int,
label: StringResource,
fields: List<Pair<String, ValueChange<String>?>>,
): DiffGroup {
val removed = mutableListOf<ReviewItem>()
@@ -242,14 +332,38 @@ private fun arrow(
*/
@Composable
fun rememberPresentation(diff: EventDiff): DiffPresentation {
val resources = LocalResources.current
return remember(diff, resources) { presentationOf(diff) { resources.getString(it) } }
// Compose resources resolve only in composition (or suspended), so the few labels
// [presentationOf] writes into item text are resolved here and handed in. The map
// compares by value, so a recomposition with the same locale keeps the cached result.
val labels = PRESENTATION_LABELS.associateWith { stringRes(it) }
return remember(diff, labels) { presentationOf(diff) { labels.getValue(it) } }
}
/** Every label [presentationOf] resolves through its `str` parameter. */
private val PRESENTATION_LABELS =
listOf(
Res.string.backup_profile_field_name,
Res.string.backup_profile_field_display_name,
Res.string.backup_profile_field_about,
Res.string.backup_profile_field_picture,
Res.string.backup_profile_field_banner,
Res.string.backup_profile_field_website,
Res.string.backup_profile_field_nip05,
Res.string.backup_profile_field_lud16,
Res.string.backup_profile_field_lud06,
Res.string.backup_profile_field_clink_offer,
Res.string.backup_profile_field_pronouns,
Res.string.backup_profile_field_birthday,
Res.string.backup_profile_field_bot,
Res.string.backup_conflict_relay_read_write,
Res.string.backup_conflict_relay_read_only,
Res.string.backup_conflict_relay_write_only,
)
/** Turns each event's own diff into typed, labelled groups. [str] resolves the labels. */
fun presentationOf(
diff: EventDiff,
str: (Int) -> String,
str: (StringResource) -> String,
): DiffPresentation {
val (groups, content) =
when (diff) {
@@ -258,31 +372,31 @@ fun presentationOf(
val bot = diff.bot?.let { ValueChange(it.before?.toString(), it.after?.toString()) }
listOf(
fieldGroup(
R.string.backup_entry_profile_field,
Res.string.backup_entry_profile_field,
listOf(
str(R.string.backup_profile_field_name) to diff.name,
str(R.string.backup_profile_field_display_name) to diff.displayName,
str(R.string.backup_profile_field_about) to diff.about,
str(R.string.backup_profile_field_picture) to diff.picture,
str(R.string.backup_profile_field_banner) to diff.banner,
str(R.string.backup_profile_field_website) to diff.website,
str(R.string.backup_profile_field_nip05) to diff.nip05,
str(R.string.backup_profile_field_lud16) to diff.lud16,
str(R.string.backup_profile_field_lud06) to diff.lud06,
str(R.string.backup_profile_field_clink_offer) to diff.clinkOffer,
str(R.string.backup_profile_field_pronouns) to diff.pronouns,
str(R.string.backup_profile_field_birthday) to birthday,
str(R.string.backup_profile_field_bot) to bot,
str(Res.string.backup_profile_field_name) to diff.name,
str(Res.string.backup_profile_field_display_name) to diff.displayName,
str(Res.string.backup_profile_field_about) to diff.about,
str(Res.string.backup_profile_field_picture) to diff.picture,
str(Res.string.backup_profile_field_banner) to diff.banner,
str(Res.string.backup_profile_field_website) to diff.website,
str(Res.string.backup_profile_field_nip05) to diff.nip05,
str(Res.string.backup_profile_field_lud16) to diff.lud16,
str(Res.string.backup_profile_field_lud06) to diff.lud06,
str(Res.string.backup_profile_field_clink_offer) to diff.clinkOffer,
str(Res.string.backup_profile_field_pronouns) to diff.pronouns,
str(Res.string.backup_profile_field_birthday) to birthday,
str(Res.string.backup_profile_field_bot) to bot,
),
),
listGroup(
R.string.backup_entry_identity_claim,
Res.string.backup_entry_identity_claim,
diff.identityClaims,
{ ReviewItem.Text(it.platformIdentity()) },
{ ReviewItem.Text(it.after.platformIdentity(), arrow(it.before.proof, it.after.proof)) },
),
listGroup(
R.string.backup_entry_other,
Res.string.backup_entry_other,
diff.otherFields,
{ ReviewItem.Text(it.first, clip(it.second)) },
{ ReviewItem.Text(it.after.first, clip(it.before.second) + " → " + clip(it.after.second)) },
@@ -292,7 +406,7 @@ fun presentationOf(
is ContactListDiff ->
listOf(
listGroup(
R.string.backup_entry_person,
Res.string.backup_entry_person,
diff.follows,
{ ReviewItem.Person(it.pubKey, it.petname) },
{
@@ -308,32 +422,32 @@ fun presentationOf(
) to ContentChange.NONE
is MuteListDiff ->
listOf(
listGroup(R.string.backup_entry_person, diff.publicMutes.only<UserTag>(), { ReviewItem.Person(it.pubKey) }),
listGroup(R.string.backup_entry_word, diff.publicMutes.only<WordTag>(), { ReviewItem.Text("\"" + it.word + "\"") }),
listGroup(R.string.backup_entry_hashtag, diff.publicMutes.only<HashtagTag>(), { ReviewItem.Text("#" + it.hashtag) }),
listGroup(R.string.backup_entry_thread, diff.publicMutes.only<EventTag>(), { ReviewItem.Thread(it.eventId) }),
listGroup(Res.string.backup_entry_person, diff.publicMutes.only<UserTag>(), { ReviewItem.Person(it.pubKey) }),
listGroup(Res.string.backup_entry_word, diff.publicMutes.only<WordTag>(), { ReviewItem.Text("\"" + it.word + "\"") }),
listGroup(Res.string.backup_entry_hashtag, diff.publicMutes.only<HashtagTag>(), { ReviewItem.Text("#" + it.hashtag) }),
listGroup(Res.string.backup_entry_thread, diff.publicMutes.only<EventTag>(), { ReviewItem.Thread(it.eventId) }),
) to diff.privateItems
is AdvertisedRelayListDiff -> {
val types =
mapOf(
AdvertisedRelayType.BOTH to str(R.string.backup_conflict_relay_read_write),
AdvertisedRelayType.READ to str(R.string.backup_conflict_relay_read_only),
AdvertisedRelayType.WRITE to str(R.string.backup_conflict_relay_write_only),
AdvertisedRelayType.BOTH to str(Res.string.backup_conflict_relay_read_write),
AdvertisedRelayType.READ to str(Res.string.backup_conflict_relay_read_only),
AdvertisedRelayType.WRITE to str(Res.string.backup_conflict_relay_write_only),
)
listOf(
listGroup(
R.string.backup_entry_relay,
Res.string.backup_entry_relay,
diff.relays,
{ ReviewItem.Relay(it.relayUrl, types[it.type]) },
{ ReviewItem.Relay(it.after.relayUrl, arrow(types[it.before.type], types[it.after.type])) },
),
) to ContentChange.NONE
}
is RelayListDiff -> listOf(listGroup(R.string.backup_entry_relay, diff.relays, { ReviewItem.Relay(it) })) to diff.privateRelays
is RelayListDiff -> listOf(listGroup(Res.string.backup_entry_relay, diff.relays, { ReviewItem.Relay(it) })) to diff.privateRelays
is ChannelListDiff ->
listOf(
listGroup(
R.string.backup_entry_public_chat,
Res.string.backup_entry_public_chat,
diff.channels,
{ ReviewItem.PublicChat(it.eventId) },
{ ReviewItem.PublicChat(it.after.eventId, arrow(it.before.relay?.url, it.after.relay?.url)) },
@@ -342,28 +456,28 @@ fun presentationOf(
is CommunityListDiff ->
listOf(
listGroup(
R.string.backup_entry_community,
Res.string.backup_entry_community,
diff.communities,
{ ReviewItem.Addressable(it.address) },
{ ReviewItem.Addressable(it.after.address, arrow(it.before.relayHint?.url, it.after.relayHint?.url)) },
),
) to diff.privateItems
is HashtagListDiff -> listOf(listGroup(R.string.backup_entry_hashtag, diff.hashtags, { ReviewItem.Text("#$it") })) to diff.privateItems
is GeohashListDiff -> listOf(listGroup(R.string.backup_entry_location, diff.geohashes, { ReviewItem.Text(it) })) to diff.privateItems
is HashtagListDiff -> listOf(listGroup(Res.string.backup_entry_hashtag, diff.hashtags, { ReviewItem.Text("#$it") })) to diff.privateItems
is GeohashListDiff -> listOf(listGroup(Res.string.backup_entry_location, diff.geohashes, { ReviewItem.Text(it) })) to diff.privateItems
is FavoriteAlgoFeedsListDiff ->
listOf(
listGroup(
R.string.backup_entry_algo_feed,
Res.string.backup_entry_algo_feed,
diff.feeds,
{ ReviewItem.Addressable(it.address) },
{ ReviewItem.Addressable(it.after.address, arrow(it.before.relayHint?.url, it.after.relayHint?.url)) },
),
) to diff.privateItems
is EphemeralChatListDiff -> listOf(listGroup(R.string.backup_entry_chat_room, diff.rooms, { ReviewItem.ChatRoom(it) })) to diff.privateItems
is EphemeralChatListDiff -> listOf(listGroup(Res.string.backup_entry_chat_room, diff.rooms, { ReviewItem.ChatRoom(it) })) to diff.privateItems
is SimpleGroupListDiff ->
listOf(
listGroup(
R.string.backup_entry_relay_group,
Res.string.backup_entry_relay_group,
diff.groups,
{ ReviewItem.Text(it.name ?: it.groupId, it.relayUrl) },
{ ReviewItem.Text(it.after.name ?: it.after.groupId, arrow(it.before.name, it.after.name)) },
@@ -372,7 +486,7 @@ fun presentationOf(
is TrustProviderListDiff ->
listOf(
listGroup(
R.string.backup_entry_trust_provider,
Res.string.backup_entry_trust_provider,
diff.providers,
{ ReviewItem.Person(it.pubkey, it.service.type) },
{ ReviewItem.Person(it.after.pubkey, it.after.service.type + ": " + arrow(it.before.relayUrl.url, it.after.relayUrl.url)) },
@@ -381,19 +495,19 @@ fun presentationOf(
is NutzapInfoDiff ->
listOf(
listGroup(
R.string.backup_entry_mint,
Res.string.backup_entry_mint,
diff.mints,
{ ReviewItem.Text(it.mintUrl, it.units.joinToString().ifEmpty { null }) },
{ ReviewItem.Text(it.after.mintUrl, arrow(it.before.units.joinToString(), it.after.units.joinToString())) },
),
listGroup(R.string.backup_entry_relay, diff.relays, { ReviewItem.Relay(it) }),
listGroup(Res.string.backup_entry_relay, diff.relays, { ReviewItem.Relay(it) }),
nutzapKeyGroup(diff.p2pkPubkey),
) to ContentChange.NONE
is PaymentTargetsDiff ->
listOf(
listGroup(R.string.backup_entry_payment_target, diff.targets, { ReviewItem.Text(it.type, clip(it.authority)) }),
listGroup(Res.string.backup_entry_payment_target, diff.targets, { ReviewItem.Text(it.type, clip(it.authority)) }),
) to ContentChange.NONE
is Bolt12OfferListDiff -> listOf(listGroup(R.string.backup_entry_offer, diff.offers, { ReviewItem.Text(clip(it)) })) to ContentChange.NONE
is Bolt12OfferListDiff -> listOf(listGroup(Res.string.backup_entry_offer, diff.offers, { ReviewItem.Text(clip(it)) })) to ContentChange.NONE
is CashuWalletDiff -> emptyList<DiffGroup>() to diff.wallet
is ConcordCommunityListDiff -> emptyList<DiffGroup>() to diff.communities
is AppSpecificDataDiff -> emptyList<DiffGroup>() to diff.data
@@ -404,13 +518,13 @@ fun presentationOf(
private fun nutzapKeyGroup(change: ValueChange<HexKey>?): DiffGroup {
val none = emptyList<ReviewItem>()
if (change == null) return DiffGroup(R.string.backup_entry_nutzap_key, none, none, none)
if (change == null) return DiffGroup(Res.string.backup_entry_nutzap_key, none, none, none)
val before = change.before
val after = change.after
return when {
before != null && after == null -> DiffGroup(R.string.backup_entry_nutzap_key, listOf(ReviewItem.Text(before)), none, none)
before == null && after != null -> DiffGroup(R.string.backup_entry_nutzap_key, none, listOf(ReviewItem.Text(after)), none)
else -> DiffGroup(R.string.backup_entry_nutzap_key, none, none, listOf(ReviewItem.Text(arrow(before, after))))
before != null && after == null -> DiffGroup(Res.string.backup_entry_nutzap_key, listOf(ReviewItem.Text(before)), none, none)
before == null && after != null -> DiffGroup(Res.string.backup_entry_nutzap_key, none, listOf(ReviewItem.Text(after)), none)
else -> DiffGroup(Res.string.backup_entry_nutzap_key, none, none, listOf(ReviewItem.Text(arrow(before, after))))
}
}
@@ -422,64 +536,64 @@ fun clip(text: String?): String {
return if (oneLine.length > MAX_VALUE_LENGTH) oneLine.take(MAX_VALUE_LENGTH) + "…" else oneLine
}
fun eventTypeName(type: BackupEventType): Int =
fun eventTypeName(type: BackupEventType): StringResource =
when (type) {
BackupEventType.PROFILE -> R.string.backup_type_profile
BackupEventType.FOLLOW_LIST -> R.string.backup_type_follow_list
BackupEventType.MUTE_LIST -> R.string.backup_type_mute_list
BackupEventType.OUTBOX_INBOX_RELAYS -> R.string.backup_type_outbox_inbox_relays
BackupEventType.DM_RELAYS -> R.string.backup_type_dm_relays
BackupEventType.KEY_PACKAGE_RELAYS -> R.string.backup_type_key_package_relays
BackupEventType.SEARCH_RELAYS -> R.string.backup_type_search_relays
BackupEventType.INDEXER_RELAYS -> R.string.backup_type_indexer_relays
BackupEventType.RELAY_FEEDS -> R.string.backup_type_relay_feeds
BackupEventType.BLOCKED_RELAYS -> R.string.backup_type_blocked_relays
BackupEventType.TRUSTED_RELAYS -> R.string.backup_type_trusted_relays
BackupEventType.PRIVATE_OUTBOX_RELAYS -> R.string.backup_type_private_outbox_relays
BackupEventType.APP_SETTINGS -> R.string.backup_type_app_settings
BackupEventType.PUBLIC_CHATS -> R.string.backup_type_public_chats
BackupEventType.COMMUNITIES -> R.string.backup_type_communities
BackupEventType.HASHTAGS -> R.string.backup_type_hashtags
BackupEventType.GEOHASHES -> R.string.backup_type_geohashes
BackupEventType.FAVORITE_ALGO_FEEDS -> R.string.backup_type_favorite_algo_feeds
BackupEventType.EPHEMERAL_CHATS -> R.string.backup_type_ephemeral_chats
BackupEventType.RELAY_GROUPS -> R.string.backup_type_relay_groups
BackupEventType.CONCORD_COMMUNITIES -> R.string.backup_type_concord_communities
BackupEventType.TRUST_PROVIDERS -> R.string.backup_type_trust_providers
BackupEventType.CASHU_WALLET -> R.string.backup_type_cashu_wallet
BackupEventType.NUTZAP_INFO -> R.string.backup_type_nutzap_info
BackupEventType.PAYMENT_TARGETS -> R.string.backup_type_payment_targets
BackupEventType.BOLT12_OFFERS -> R.string.backup_type_bolt12_offers
BackupEventType.OTHER -> R.string.backup_type_other
BackupEventType.PROFILE -> Res.string.backup_type_profile
BackupEventType.FOLLOW_LIST -> Res.string.backup_type_follow_list
BackupEventType.MUTE_LIST -> Res.string.backup_type_mute_list
BackupEventType.OUTBOX_INBOX_RELAYS -> Res.string.backup_type_outbox_inbox_relays
BackupEventType.DM_RELAYS -> Res.string.backup_type_dm_relays
BackupEventType.KEY_PACKAGE_RELAYS -> Res.string.backup_type_key_package_relays
BackupEventType.SEARCH_RELAYS -> Res.string.backup_type_search_relays
BackupEventType.INDEXER_RELAYS -> Res.string.backup_type_indexer_relays
BackupEventType.RELAY_FEEDS -> Res.string.backup_type_relay_feeds
BackupEventType.BLOCKED_RELAYS -> Res.string.backup_type_blocked_relays
BackupEventType.TRUSTED_RELAYS -> Res.string.backup_type_trusted_relays
BackupEventType.PRIVATE_OUTBOX_RELAYS -> Res.string.backup_type_private_outbox_relays
BackupEventType.APP_SETTINGS -> Res.string.backup_type_app_settings
BackupEventType.PUBLIC_CHATS -> Res.string.backup_type_public_chats
BackupEventType.COMMUNITIES -> Res.string.backup_type_communities
BackupEventType.HASHTAGS -> Res.string.backup_type_hashtags
BackupEventType.GEOHASHES -> Res.string.backup_type_geohashes
BackupEventType.FAVORITE_ALGO_FEEDS -> Res.string.backup_type_favorite_algo_feeds
BackupEventType.EPHEMERAL_CHATS -> Res.string.backup_type_ephemeral_chats
BackupEventType.RELAY_GROUPS -> Res.string.backup_type_relay_groups
BackupEventType.CONCORD_COMMUNITIES -> Res.string.backup_type_concord_communities
BackupEventType.TRUST_PROVIDERS -> Res.string.backup_type_trust_providers
BackupEventType.CASHU_WALLET -> Res.string.backup_type_cashu_wallet
BackupEventType.NUTZAP_INFO -> Res.string.backup_type_nutzap_info
BackupEventType.PAYMENT_TARGETS -> Res.string.backup_type_payment_targets
BackupEventType.BOLT12_OFFERS -> Res.string.backup_type_bolt12_offers
BackupEventType.OTHER -> Res.string.backup_type_other
}
fun eventTypeExplainer(type: BackupEventType): Int =
fun eventTypeExplainer(type: BackupEventType): StringResource =
when (type) {
BackupEventType.PROFILE -> R.string.backup_type_profile_explainer
BackupEventType.FOLLOW_LIST -> R.string.backup_type_follow_list_explainer
BackupEventType.MUTE_LIST -> R.string.backup_type_mute_list_explainer
BackupEventType.OUTBOX_INBOX_RELAYS -> R.string.backup_type_outbox_inbox_relays_explainer
BackupEventType.DM_RELAYS -> R.string.backup_type_dm_relays_explainer
BackupEventType.KEY_PACKAGE_RELAYS -> R.string.backup_type_key_package_relays_explainer
BackupEventType.SEARCH_RELAYS -> R.string.backup_type_search_relays_explainer
BackupEventType.INDEXER_RELAYS -> R.string.backup_type_indexer_relays_explainer
BackupEventType.RELAY_FEEDS -> R.string.backup_type_relay_feeds_explainer
BackupEventType.BLOCKED_RELAYS -> R.string.backup_type_blocked_relays_explainer
BackupEventType.TRUSTED_RELAYS -> R.string.backup_type_trusted_relays_explainer
BackupEventType.PRIVATE_OUTBOX_RELAYS -> R.string.backup_type_private_outbox_relays_explainer
BackupEventType.APP_SETTINGS -> R.string.backup_type_app_settings_explainer
BackupEventType.PUBLIC_CHATS -> R.string.backup_type_public_chats_explainer
BackupEventType.COMMUNITIES -> R.string.backup_type_communities_explainer
BackupEventType.HASHTAGS -> R.string.backup_type_hashtags_explainer
BackupEventType.GEOHASHES -> R.string.backup_type_geohashes_explainer
BackupEventType.FAVORITE_ALGO_FEEDS -> R.string.backup_type_favorite_algo_feeds_explainer
BackupEventType.EPHEMERAL_CHATS -> R.string.backup_type_ephemeral_chats_explainer
BackupEventType.RELAY_GROUPS -> R.string.backup_type_relay_groups_explainer
BackupEventType.CONCORD_COMMUNITIES -> R.string.backup_type_concord_communities_explainer
BackupEventType.TRUST_PROVIDERS -> R.string.backup_type_trust_providers_explainer
BackupEventType.CASHU_WALLET -> R.string.backup_type_cashu_wallet_explainer
BackupEventType.NUTZAP_INFO -> R.string.backup_type_nutzap_info_explainer
BackupEventType.PAYMENT_TARGETS -> R.string.backup_type_payment_targets_explainer
BackupEventType.BOLT12_OFFERS -> R.string.backup_type_bolt12_offers_explainer
BackupEventType.OTHER -> R.string.backup_type_other_explainer
BackupEventType.PROFILE -> Res.string.backup_type_profile_explainer
BackupEventType.FOLLOW_LIST -> Res.string.backup_type_follow_list_explainer
BackupEventType.MUTE_LIST -> Res.string.backup_type_mute_list_explainer
BackupEventType.OUTBOX_INBOX_RELAYS -> Res.string.backup_type_outbox_inbox_relays_explainer
BackupEventType.DM_RELAYS -> Res.string.backup_type_dm_relays_explainer
BackupEventType.KEY_PACKAGE_RELAYS -> Res.string.backup_type_key_package_relays_explainer
BackupEventType.SEARCH_RELAYS -> Res.string.backup_type_search_relays_explainer
BackupEventType.INDEXER_RELAYS -> Res.string.backup_type_indexer_relays_explainer
BackupEventType.RELAY_FEEDS -> Res.string.backup_type_relay_feeds_explainer
BackupEventType.BLOCKED_RELAYS -> Res.string.backup_type_blocked_relays_explainer
BackupEventType.TRUSTED_RELAYS -> Res.string.backup_type_trusted_relays_explainer
BackupEventType.PRIVATE_OUTBOX_RELAYS -> Res.string.backup_type_private_outbox_relays_explainer
BackupEventType.APP_SETTINGS -> Res.string.backup_type_app_settings_explainer
BackupEventType.PUBLIC_CHATS -> Res.string.backup_type_public_chats_explainer
BackupEventType.COMMUNITIES -> Res.string.backup_type_communities_explainer
BackupEventType.HASHTAGS -> Res.string.backup_type_hashtags_explainer
BackupEventType.GEOHASHES -> Res.string.backup_type_geohashes_explainer
BackupEventType.FAVORITE_ALGO_FEEDS -> Res.string.backup_type_favorite_algo_feeds_explainer
BackupEventType.EPHEMERAL_CHATS -> Res.string.backup_type_ephemeral_chats_explainer
BackupEventType.RELAY_GROUPS -> Res.string.backup_type_relay_groups_explainer
BackupEventType.CONCORD_COMMUNITIES -> Res.string.backup_type_concord_communities_explainer
BackupEventType.TRUST_PROVIDERS -> Res.string.backup_type_trust_providers_explainer
BackupEventType.CASHU_WALLET -> Res.string.backup_type_cashu_wallet_explainer
BackupEventType.NUTZAP_INFO -> Res.string.backup_type_nutzap_info_explainer
BackupEventType.PAYMENT_TARGETS -> Res.string.backup_type_payment_targets_explainer
BackupEventType.BOLT12_OFFERS -> Res.string.backup_type_bolt12_offers_explainer
BackupEventType.OTHER -> Res.string.backup_type_other_explainer
}
@@ -59,11 +59,34 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.model.backups.BackupEventType
import com.vitorpamplona.amethyst.commons.model.backups.ReplaceableBackupConflict
import com.vitorpamplona.amethyst.commons.model.navigation.Route
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.backup_action_block_again
import com.vitorpamplona.amethyst.commons.resources.backup_action_keep_count
import com.vitorpamplona.amethyst.commons.resources.backup_action_rejoin
import com.vitorpamplona.amethyst.commons.resources.backup_action_remute
import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_count
import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_feeds
import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_places
import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_profile
import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_providers
import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_relays
import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_topics
import com.vitorpamplona.amethyst.commons.resources.backup_action_restore_wallet
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_added
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_changed
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_keep_new
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_private_added
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_private_changed
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_private_cleared
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_removed
import com.vitorpamplona.amethyst.commons.resources.backup_conflict_restore_mine
import com.vitorpamplona.amethyst.commons.resources.backup_review_changed_by_other_app
import com.vitorpamplona.amethyst.commons.resources.backup_review_title_cleared
import com.vitorpamplona.amethyst.commons.resources.backup_review_title_updated
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton
@@ -97,22 +120,23 @@ import com.vitorpamplona.quartz.nip60Cashu.wallet.CashuWalletDiff
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListDiff
import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListDiff
import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListDiff
import org.jetbrains.compose.resources.StringResource
/** A row of the review list, flattened so hundreds of entries scroll lazily. */
@Immutable
internal sealed interface ReviewRow {
class Section(
val titleRes: Int,
val titleRes: StringResource,
val count: Int,
val tone: Tone,
) : ReviewRow
class Note(
val textRes: Int,
val textRes: StringResource,
) : ReviewRow
class Group(
val labelRes: Int,
val labelRes: StringResource,
) : ReviewRow
class Entry(
@@ -124,15 +148,15 @@ internal enum class Tone { REMOVED, ADDED, CHANGED }
internal fun buildRows(
presentation: DiffPresentation,
skipGroups: Set<Int> = emptySet(),
skipGroups: Set<StringResource> = emptySet(),
): List<ReviewRow> {
val rows = mutableListOf<ReviewRow>()
fun section(
titleRes: Int,
titleRes: StringResource,
count: Int,
tone: Tone,
contentNote: Int?,
contentNote: StringResource?,
items: (DiffGroup) -> List<ReviewItem>,
) {
if (count == 0 && contentNote == null) return
@@ -149,9 +173,9 @@ internal fun buildRows(
}
val content = presentation.content
section(R.string.backup_conflict_removed, presentation.removedCount, Tone.REMOVED, R.string.backup_conflict_private_cleared.takeIf { content == ContentChange.CLEARED }) { it.removed }
section(R.string.backup_conflict_added, presentation.addedCount, Tone.ADDED, R.string.backup_conflict_private_added.takeIf { content == ContentChange.ADDED }) { it.added }
section(R.string.backup_conflict_changed, presentation.changedCount, Tone.CHANGED, R.string.backup_conflict_private_changed.takeIf { content == ContentChange.CHANGED }) { it.changed }
section(Res.string.backup_conflict_removed, presentation.removedCount, Tone.REMOVED, Res.string.backup_conflict_private_cleared.takeIf { content == ContentChange.CLEARED }) { it.removed }
section(Res.string.backup_conflict_added, presentation.addedCount, Tone.ADDED, Res.string.backup_conflict_private_added.takeIf { content == ContentChange.ADDED }) { it.added }
section(Res.string.backup_conflict_changed, presentation.changedCount, Tone.CHANGED, Res.string.backup_conflict_private_changed.takeIf { content == ContentChange.CHANGED }) { it.changed }
return rows
}
@@ -268,7 +292,7 @@ internal fun LazyListScope.genericDiffItems(
}
@Composable
internal fun GroupLabel(labelRes: Int) {
internal fun GroupLabel(labelRes: StringResource) {
Text(
text = stringRes(labelRes),
style = MaterialTheme.typography.labelLarge,
@@ -339,7 +363,7 @@ internal fun eventTitle(conflict: ReplaceableBackupConflict): String {
val name = stringRes(eventTypeName(conflict.eventType)).replaceFirstChar { it.uppercase() }
// Says what happened to it, not just what it is: "Community list updated".
val emptied = conflict.incoming.tags.isEmpty() && conflict.incoming.content.isEmpty()
return stringRes(if (emptied) R.string.backup_review_title_cleared else R.string.backup_review_title_updated, name)
return stringRes(if (emptied) Res.string.backup_review_title_cleared else Res.string.backup_review_title_updated, name)
}
/** The two choices, worded for what they do to this event: "Keep 120" / "Restore 523". */
@@ -348,48 +372,48 @@ private fun actionLabels(conflict: ReplaceableBackupConflict): Pair<String, Stri
when (val diff = conflict.diff) {
is ContactListDiff -> {
val (saved, new) = conflict.followCounts
stringRes(R.string.backup_action_keep_count, new.toString()) to stringRes(R.string.backup_action_restore_count, saved.toString())
stringRes(Res.string.backup_action_keep_count, new.toString()) to stringRes(Res.string.backup_action_restore_count, saved.toString())
}
is MuteListDiff -> {
val removed = diff.publicMutes.removed.size
stringRes(R.string.backup_conflict_keep_new) to
if (removed > 0) stringRes(R.string.backup_action_remute, removed.toString()) else stringRes(R.string.backup_conflict_restore_mine)
stringRes(Res.string.backup_conflict_keep_new) to
if (removed > 0) stringRes(Res.string.backup_action_remute, removed.toString()) else stringRes(Res.string.backup_conflict_restore_mine)
}
is SimpleGroupListDiff -> {
val removed = diff.groups.removed.size
stringRes(R.string.backup_conflict_keep_new) to
if (removed > 0) stringRes(R.string.backup_action_rejoin, removed.toString()) else stringRes(R.string.backup_conflict_restore_mine)
stringRes(Res.string.backup_conflict_keep_new) to
if (removed > 0) stringRes(Res.string.backup_action_rejoin, removed.toString()) else stringRes(Res.string.backup_conflict_restore_mine)
}
is MetadataDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_profile)
is MetadataDiff -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_profile)
is RelayListDiff ->
if (conflict.eventType == BackupEventType.BLOCKED_RELAYS && diff.relays.removed.isNotEmpty()) {
stringRes(R.string.backup_conflict_keep_new) to
stringRes(Res.string.backup_conflict_keep_new) to
stringRes(
R.string.backup_action_block_again,
Res.string.backup_action_block_again,
diff.relays.removed.size
.toString(),
)
} else {
stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_relays)
stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_relays)
}
is AdvertisedRelayListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_relays)
is AdvertisedRelayListDiff -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_relays)
is ChannelListDiff -> rejoinLabels(diff.channels.removed.size)
is CommunityListDiff -> rejoinLabels(diff.communities.removed.size)
is EphemeralChatListDiff -> rejoinLabels(diff.rooms.removed.size)
is FavoriteAlgoFeedsListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_feeds)
is HashtagListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_topics)
is FavoriteAlgoFeedsListDiff -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_feeds)
is HashtagListDiff -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_topics)
// Geohashes are places, not topics: the screen counts "places gone" under "Followed
// locations", so the button has to agree with the page it sits on.
is GeohashListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_places)
is TrustProviderListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_providers)
is CashuWalletDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_wallet)
else -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_conflict_restore_mine)
is GeohashListDiff -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_places)
is TrustProviderListDiff -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_providers)
is CashuWalletDiff -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_action_restore_wallet)
else -> stringRes(Res.string.backup_conflict_keep_new) to stringRes(Res.string.backup_conflict_restore_mine)
}
@Composable
private fun rejoinLabels(removed: Int): Pair<String, String> =
stringRes(R.string.backup_conflict_keep_new) to
if (removed > 0) stringRes(R.string.backup_action_rejoin, removed.toString()) else stringRes(R.string.backup_conflict_restore_mine)
stringRes(Res.string.backup_conflict_keep_new) to
if (removed > 0) stringRes(Res.string.backup_action_rejoin, removed.toString()) else stringRes(Res.string.backup_conflict_restore_mine)
/** When the other app changed it, and what the event is for. */
@Composable
@@ -399,7 +423,7 @@ private fun BackupConflictIntro(
) {
Column(modifier, verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text(
text = stringRes(R.string.backup_review_changed_by_other_app, timeAgoNoDot(conflict.cause.createdAt)),
text = stringRes(Res.string.backup_review_changed_by_other_app, timeAgoNoDot(conflict.cause.createdAt)),
style = MaterialTheme.typography.labelLarge,
color = conflictTones().removed,
)
@@ -42,12 +42,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.chats.ui.ThinSendButton
import com.vitorpamplona.amethyst.commons.model.cordnGroups.CordnGroupChatroom
import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState
import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiSuggestionState
import com.vitorpamplona.amethyst.commons.nip30CustomEmojis.ui.ShowEmojiSuggestionList
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.cordn_composer_hint
import com.vitorpamplona.amethyst.commons.ui.text.currentWord
import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord
import com.vitorpamplona.amethyst.commons.ui.theme.EditFieldBorder
@@ -233,7 +234,7 @@ internal fun CordnComposer(
shape = EditFieldBorder,
placeholder = {
Text(
text = stringRes(R.string.cordn_composer_hint),
text = stringRes(Res.string.cordn_composer_hint),
color = MaterialTheme.colorScheme.placeholderText,
)
},
@@ -55,11 +55,9 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.cordn.CoordinatorConfig
import com.vitorpamplona.amethyst.commons.cordn.DiscoveredCoordinator
import com.vitorpamplona.amethyst.commons.cordn.GroupExposure
@@ -69,6 +67,55 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.model.navigation.Route
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.back
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_failed
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_none
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_seen
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_seen_on
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_unheard
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_hide_older
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_relays_more
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_show_all
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_show_fewer
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_show_older
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_stale_note
import com.vitorpamplona.amethyst.commons.resources.cordn_create_action
import com.vitorpamplona.amethyst.commons.resources.cordn_create_action_invite
import com.vitorpamplona.amethyst.commons.resources.cordn_create_admin_count
import com.vitorpamplona.amethyst.commons.resources.cordn_create_admin_only_me_on
import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_change
import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_discover
import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_hide
import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_new
import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_pubkey
import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_relays
import com.vitorpamplona.amethyst.commons.resources.cordn_create_coverage_all
import com.vitorpamplona.amethyst.commons.resources.cordn_create_coverage_partial
import com.vitorpamplona.amethyst.commons.resources.cordn_create_coverage_unknown
import com.vitorpamplona.amethyst.commons.resources.cordn_create_description
import com.vitorpamplona.amethyst.commons.resources.cordn_create_egalitarian
import com.vitorpamplona.amethyst.commons.resources.cordn_create_egalitarian_note
import com.vitorpamplona.amethyst.commons.resources.cordn_create_explainer
import com.vitorpamplona.amethyst.commons.resources.cordn_create_failed
import com.vitorpamplona.amethyst.commons.resources.cordn_create_name
import com.vitorpamplona.amethyst.commons.resources.cordn_create_no_coordinator
import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_explainer
import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_failed
import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_link
import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_no_welcome
import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_open
import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_skipped
import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_title
import com.vitorpamplona.amethyst.commons.resources.cordn_create_outcome_waiting
import com.vitorpamplona.amethyst.commons.resources.cordn_create_people_none
import com.vitorpamplona.amethyst.commons.resources.cordn_create_step_admins
import com.vitorpamplona.amethyst.commons.resources.cordn_create_step_name
import com.vitorpamplona.amethyst.commons.resources.cordn_create_step_people
import com.vitorpamplona.amethyst.commons.resources.cordn_create_step_where
import com.vitorpamplona.amethyst.commons.resources.cordn_create_title
import com.vitorpamplona.amethyst.commons.resources.cordn_create_unreachable_count
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail
import com.vitorpamplona.amethyst.commons.resources.cordn_member_count
import com.vitorpamplona.amethyst.commons.ui.components.EmptyState
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.model.cordn.CordnCoverage
@@ -131,7 +178,7 @@ fun CordnCreateGroupScreen(
Icon(MaterialSymbols.AutoMirrored.ArrowBack, contentDescription = stringRes(Res.string.back))
}
},
title = { Text(stringRes(R.string.cordn_create_title)) },
title = { Text(stringRes(Res.string.cordn_create_title)) },
)
},
) { padding ->
@@ -139,8 +186,8 @@ fun CordnCreateGroupScreen(
// The app's own empty state, centred and titled, rather than a
// sentence stranded in the top-left corner.
EmptyState(
title = stringRes(R.string.cordn_group_unavailable),
description = stringRes(R.string.cordn_group_unavailable_detail),
title = stringRes(Res.string.cordn_group_unavailable),
description = stringRes(Res.string.cordn_group_unavailable_detail),
modifier = Modifier.padding(padding),
)
return@Scaffold
@@ -164,14 +211,14 @@ fun CordnCreateGroupScreen(
val draft = rememberCordnGroupDraft(accountViewModel)
var creation by remember { mutableStateOf<CordnGroupCreation?>(null) }
var error by remember { mutableStateOf<String?>(null) }
val failureFallback = stringRes(R.string.cordn_create_failed)
val failureFallback = stringRes(Res.string.cordn_create_failed)
var busy by remember { mutableStateOf(false) }
// Coordinators this account has never used, from their CEP-6
// announcements. Not added to the account by looking: picking one here
// is what commits to it, and `createGroup` opens the session.
var discovering by remember { mutableStateOf(false) }
val discoverFailed = stringRes(R.string.cordn_coordinators_discover_failed)
val discoverFailed = stringRes(Res.string.cordn_coordinators_discover_failed)
// Offers this account already holds are not offers; they are the choices
// above, and listing them twice would let the same coordinator be picked
@@ -222,29 +269,29 @@ fun CordnCreateGroupScreen(
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Text(
text = stringRes(R.string.cordn_create_explainer),
text = stringRes(Res.string.cordn_create_explainer),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
StepHeading(1, stringRes(R.string.cordn_create_step_name))
StepHeading(1, stringRes(Res.string.cordn_create_step_name))
OutlinedTextField(
value = draft.name,
onValueChange = { draft.name = it },
label = { Text(stringRes(R.string.cordn_create_name)) },
label = { Text(stringRes(Res.string.cordn_create_name)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
OutlinedTextField(
value = draft.description,
onValueChange = { draft.description = it },
label = { Text(stringRes(R.string.cordn_create_description)) },
label = { Text(stringRes(Res.string.cordn_create_description)) },
singleLine = false,
modifier = Modifier.fillMaxWidth(),
)
StepHeading(2, stringRes(R.string.cordn_create_step_people))
StepHeading(2, stringRes(Res.string.cordn_create_step_people))
RosterSummary(
roster = draft.roster,
@@ -254,7 +301,7 @@ fun CordnCreateGroupScreen(
nav = nav,
)
StepHeading(3, stringRes(R.string.cordn_create_step_where))
StepHeading(3, stringRes(Res.string.cordn_create_step_where))
// Collapsed to the one coordinator that will be used, because with a
// draft.roster in hand there is usually nothing left to decide. The whole
@@ -326,9 +373,9 @@ fun CordnCreateGroupScreen(
TextButton(onClick = { draft.showAllLive = !draft.showAllLive }) {
Text(
if (draft.showAllLive) {
stringRes(R.string.cordn_coordinators_show_fewer)
stringRes(Res.string.cordn_coordinators_show_fewer)
} else {
stringRes(R.string.cordn_coordinators_show_all, live.size)
stringRes(Res.string.cordn_coordinators_show_all, live.size)
},
)
}
@@ -338,9 +385,9 @@ fun CordnCreateGroupScreen(
TextButton(onClick = { draft.showStale = !draft.showStale }) {
Text(
if (draft.showStale) {
stringRes(R.string.cordn_coordinators_hide_older)
stringRes(Res.string.cordn_coordinators_hide_older)
} else {
stringRes(R.string.cordn_coordinators_show_older, stale.size)
stringRes(Res.string.cordn_coordinators_show_older, stale.size)
},
)
}
@@ -348,7 +395,7 @@ fun CordnCreateGroupScreen(
if (draft.showStale && stale.isNotEmpty()) {
Text(
text = stringRes(R.string.cordn_coordinators_stale_note),
text = stringRes(Res.string.cordn_coordinators_stale_note),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -372,7 +419,7 @@ fun CordnCreateGroupScreen(
}
CoordinatorChoice(
label = stringRes(R.string.cordn_create_coordinator_new),
label = stringRes(Res.string.cordn_create_coordinator_new),
pubKey = null,
selected = draft.selected == null,
onSelect = {
@@ -402,20 +449,20 @@ fun CordnCreateGroupScreen(
},
enabled = !discovering && !busy,
) {
Text(stringRes(R.string.cordn_create_coordinator_discover))
Text(stringRes(Res.string.cordn_create_coordinator_discover))
}
draft.discovered?.takeIf { offers.isEmpty() }?.let { result ->
Text(
text =
if (result.unreachable.isEmpty()) {
stringRes(R.string.cordn_coordinators_discover_none)
stringRes(Res.string.cordn_coordinators_discover_none)
} else {
// "Nobody is announcing" and "we were not told" are
// different answers and only one of them is final.
// Reporting the first for the second sends someone
// off to paste a pubkey by hand over a timeout.
stringRes(R.string.cordn_coordinators_discover_unheard, result.unreachable.size)
stringRes(Res.string.cordn_coordinators_discover_unheard, result.unreachable.size)
},
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -433,7 +480,7 @@ fun CordnCreateGroupScreen(
draft.userPicked = true
error = null
},
label = { Text(stringRes(R.string.cordn_create_coordinator_pubkey)) },
label = { Text(stringRes(Res.string.cordn_create_coordinator_pubkey)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
@@ -444,7 +491,7 @@ fun CordnCreateGroupScreen(
draft.userPicked = true
error = null
},
label = { Text(stringRes(R.string.cordn_create_coordinator_relays)) },
label = { Text(stringRes(Res.string.cordn_create_coordinator_relays)) },
// A coordinator has no address beyond its pubkey (§8.5), so
// the relays are how it is reached and more than one is
// ordinary. One per line rather than comma-separated,
@@ -456,7 +503,7 @@ fun CordnCreateGroupScreen(
}
}
StepHeading(4, stringRes(R.string.cordn_create_step_admins))
StepHeading(4, stringRes(Res.string.cordn_create_step_admins))
// spec/01.md §5.3: leaving the admin list empty is not "set it up
// later", it is choosing draft.egalitarian permanently -- so it is offered
@@ -472,15 +519,15 @@ fun CordnCreateGroupScreen(
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Column(Modifier.weight(1f)) {
Text(stringRes(R.string.cordn_create_egalitarian), style = MaterialTheme.typography.bodyMedium)
Text(stringRes(Res.string.cordn_create_egalitarian), style = MaterialTheme.typography.bodyMedium)
Text(
text =
if (draft.egalitarian) {
stringRes(R.string.cordn_create_egalitarian_note)
stringRes(Res.string.cordn_create_egalitarian_note)
} else if (draft.coAdmins.isEmpty()) {
stringRes(R.string.cordn_create_admin_only_me_on)
stringRes(Res.string.cordn_create_admin_only_me_on)
} else {
pluralStringRes(LocalContext.current, R.plurals.cordn_create_admin_count, draft.coAdmins.size + 1, draft.coAdmins.size + 1)
pluralStringRes(Res.plurals.cordn_create_admin_count, draft.coAdmins.size + 1, draft.coAdmins.size + 1)
},
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -561,9 +608,9 @@ fun CordnCreateGroupScreen(
) {
Text(
if (draft.roster.isEmpty()) {
stringRes(R.string.cordn_create_action)
stringRes(Res.string.cordn_create_action)
} else {
pluralStringRes(LocalContext.current, R.plurals.cordn_create_action_invite, draft.roster.size, draft.roster.size)
pluralStringRes(Res.plurals.cordn_create_action_invite, draft.roster.size, draft.roster.size)
},
)
}
@@ -691,9 +738,9 @@ private fun resolvedName(
@Composable
private fun announcedLabel(announcedAt: Long): String =
if (TimeUtils.now() - announcedAt > TimeUtils.ONE_MONTH) {
stringRes(R.string.cordn_coordinators_discover_seen_on, timeAgoNoDot(announcedAt).trim())
stringRes(Res.string.cordn_coordinators_discover_seen_on, timeAgoNoDot(announcedAt).trim())
} else {
stringRes(R.string.cordn_coordinators_discover_seen, timeAgoNoDot(announcedAt).trim())
stringRes(Res.string.cordn_coordinators_discover_seen, timeAgoNoDot(announcedAt).trim())
}
/** The hosts it answers on, the first two and a count of the rest. */
@@ -705,7 +752,7 @@ private fun relayLabel(relays: List<NormalizedRelayUrl>): String? {
return if (hosts.size <= 2) {
shown
} else {
stringRes(R.string.cordn_coordinators_relays_more, shown, hosts.size - 2)
stringRes(Res.string.cordn_coordinators_relays_more, shown, hosts.size - 2)
}
}
@@ -791,7 +838,7 @@ private fun RosterSummary(
) {
if (roster.isEmpty()) {
Text(
text = stringRes(R.string.cordn_create_people_none),
text = stringRes(Res.string.cordn_create_people_none),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.weight(1f),
@@ -804,7 +851,7 @@ private fun RosterSummary(
}
Column(Modifier.weight(1f)) {
Text(
text = pluralStringRes(LocalContext.current, R.plurals.cordn_member_count, roster.size + 1, roster.size + 1),
text = pluralStringRes(Res.plurals.cordn_member_count, roster.size + 1, roster.size + 1),
style = MaterialTheme.typography.bodyMedium,
)
// Counted over coordinators that answered, so an unreachable one
@@ -812,7 +859,7 @@ private fun RosterSummary(
val unreachable = roster.count { member -> coverage.values.none { it.answered && member in it.reachable } }
if (unreachable > 0 && coverage.values.any { it.answered }) {
Text(
text = pluralStringRes(LocalContext.current, R.plurals.cordn_create_unreachable_count, unreachable, unreachable),
text = pluralStringRes(Res.plurals.cordn_create_unreachable_count, unreachable, unreachable),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.error,
)
@@ -855,7 +902,7 @@ private fun CoordinatorSummary(
) {
if (config == null) {
Text(
text = stringRes(R.string.cordn_create_no_coordinator),
text = stringRes(Res.string.cordn_create_no_coordinator),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.weight(1f),
@@ -879,9 +926,9 @@ private fun CoordinatorSummary(
Text(
text =
when {
coverage == null || !coverage.answered -> stringRes(R.string.cordn_create_coverage_unknown)
reached == rosterSize -> stringRes(R.string.cordn_create_coverage_all, rosterSize)
else -> stringRes(R.string.cordn_create_coverage_partial, reached ?: 0, rosterSize)
coverage == null || !coverage.answered -> stringRes(Res.string.cordn_create_coverage_unknown)
reached == rosterSize -> stringRes(Res.string.cordn_create_coverage_all, rosterSize)
else -> stringRes(Res.string.cordn_create_coverage_partial, reached ?: 0, rosterSize)
},
style = MaterialTheme.typography.labelSmall,
color =
@@ -899,9 +946,9 @@ private fun CoordinatorSummary(
TextButton(onClick = onToggle) {
Text(
if (expanded) {
stringRes(R.string.cordn_create_coordinator_hide)
stringRes(Res.string.cordn_create_coordinator_hide)
} else {
stringRes(R.string.cordn_create_coordinator_change)
stringRes(Res.string.cordn_create_coordinator_change)
},
)
}
@@ -934,11 +981,11 @@ private fun CordnCreationOutcome(
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringRes(R.string.cordn_create_outcome_title)) },
title = { Text(stringRes(Res.string.cordn_create_outcome_title)) },
text = {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
Text(
text = stringRes(R.string.cordn_create_outcome_explainer),
text = stringRes(Res.string.cordn_create_outcome_explainer),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -960,10 +1007,10 @@ private fun CordnCreationOutcome(
Text(
text =
when {
outcome == null -> stringRes(R.string.cordn_create_outcome_skipped)
outcome.sent -> stringRes(R.string.cordn_create_outcome_waiting)
outcome.joinedWithoutWelcome -> stringRes(R.string.cordn_create_outcome_no_welcome)
else -> outcome.failure?.message ?: stringRes(R.string.cordn_create_outcome_failed)
outcome == null -> stringRes(Res.string.cordn_create_outcome_skipped)
outcome.sent -> stringRes(Res.string.cordn_create_outcome_waiting)
outcome.joinedWithoutWelcome -> stringRes(Res.string.cordn_create_outcome_no_welcome)
else -> outcome.failure?.message ?: stringRes(Res.string.cordn_create_outcome_failed)
},
style = MaterialTheme.typography.labelSmall,
color =
@@ -979,12 +1026,12 @@ private fun CordnCreationOutcome(
// open themselves -- which needs the group to exist,
// and now it does.
if (outcome == null) {
TextButton(onClick = open) { Text(stringRes(R.string.cordn_create_outcome_link)) }
TextButton(onClick = open) { Text(stringRes(Res.string.cordn_create_outcome_link)) }
}
}
}
}
},
confirmButton = { TextButton(onClick = open) { Text(stringRes(R.string.cordn_create_outcome_open)) } },
confirmButton = { TextButton(onClick = open) { Text(stringRes(Res.string.cordn_create_outcome_open)) } },
)
}
@@ -47,15 +47,31 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.model.User
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.cordn_create_add_member
import com.vitorpamplona.amethyst.commons.resources.cordn_create_admin
import com.vitorpamplona.amethyst.commons.resources.cordn_create_people_done
import com.vitorpamplona.amethyst.commons.resources.cordn_create_people_empty
import com.vitorpamplona.amethyst.commons.resources.cordn_create_people_note
import com.vitorpamplona.amethyst.commons.resources.cordn_create_reach_count
import com.vitorpamplona.amethyst.commons.resources.cordn_create_reach_none
import com.vitorpamplona.amethyst.commons.resources.cordn_create_reach_unknown
import com.vitorpamplona.amethyst.commons.resources.cordn_create_step_people
import com.vitorpamplona.amethyst.commons.resources.cordn_create_you
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail
import com.vitorpamplona.amethyst.commons.resources.cordn_info_add_member_none
import com.vitorpamplona.amethyst.commons.resources.cordn_info_add_member_placeholder
import com.vitorpamplona.amethyst.commons.resources.cordn_info_has_key_package
import com.vitorpamplona.amethyst.commons.resources.cordn_info_remove_member
import com.vitorpamplona.amethyst.commons.resources.cordn_member_count
import com.vitorpamplona.amethyst.commons.ui.components.EmptyState
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton
@@ -101,12 +117,12 @@ fun CordnCreateMembersScreen(
val draft = rememberCordnGroupDraft(accountViewModel)
Scaffold(
topBar = { TopBarWithBackButton(stringRes(R.string.cordn_create_step_people), nav) },
topBar = { TopBarWithBackButton(stringRes(Res.string.cordn_create_step_people), nav) },
) { padding ->
if (runtime == null) {
EmptyState(
title = stringRes(R.string.cordn_group_unavailable),
description = stringRes(R.string.cordn_group_unavailable_detail),
title = stringRes(Res.string.cordn_group_unavailable),
description = stringRes(Res.string.cordn_group_unavailable_detail),
modifier = Modifier.padding(padding),
)
return@Scaffold
@@ -137,7 +153,7 @@ fun CordnCreateMembersScreen(
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Text(
text = stringRes(R.string.cordn_create_people_note),
text = stringRes(Res.string.cordn_create_people_note),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -148,8 +164,8 @@ fun CordnCreateMembersScreen(
search = it
if (it.length > 2) userSuggestions.processCurrentWord(it) else userSuggestions.reset()
},
label = { Text(stringRes(R.string.cordn_create_add_member)) },
placeholder = { Text(stringRes(R.string.cordn_info_add_member_placeholder)) },
label = { Text(stringRes(Res.string.cordn_create_add_member)) },
placeholder = { Text(stringRes(Res.string.cordn_info_add_member_placeholder)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
@@ -168,7 +184,7 @@ fun CordnCreateMembersScreen(
modifier = SuggestionListDefaultHeightChat,
onEmpty = {
Text(
text = stringRes(R.string.cordn_info_add_member_none),
text = stringRes(Res.string.cordn_info_add_member_none),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
@@ -182,7 +198,7 @@ fun CordnCreateMembersScreen(
if (user.pubkeyHex in reachable) {
Icon(
symbol = MaterialSymbols.Key,
contentDescription = stringRes(R.string.cordn_info_has_key_package),
contentDescription = stringRes(Res.string.cordn_info_has_key_package),
modifier = Modifier.size(16.dp),
tint = MaterialTheme.colorScheme.primary,
)
@@ -194,7 +210,7 @@ fun CordnCreateMembersScreen(
}) {
Icon(
symbol = MaterialSymbols.PersonAdd,
contentDescription = stringRes(R.string.cordn_create_add_member),
contentDescription = stringRes(Res.string.cordn_create_add_member),
tint = MaterialTheme.colorScheme.primary,
)
}
@@ -206,7 +222,7 @@ fun CordnCreateMembersScreen(
HorizontalDivider()
Text(
text = pluralStringRes(LocalContext.current, R.plurals.cordn_member_count, draft.roster.size + 1, draft.roster.size + 1),
text = pluralStringRes(Res.plurals.cordn_member_count, draft.roster.size + 1, draft.roster.size + 1),
style = MaterialTheme.typography.titleSmall,
)
@@ -230,7 +246,7 @@ fun CordnCreateMembersScreen(
if (draft.roster.isEmpty()) {
Text(
text = stringRes(R.string.cordn_create_people_empty),
text = stringRes(Res.string.cordn_create_people_empty),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -240,7 +256,7 @@ fun CordnCreateMembersScreen(
onClick = { nav.popBack() },
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
) {
Text(stringRes(R.string.cordn_create_people_done))
Text(stringRes(Res.string.cordn_create_people_done))
}
}
}
@@ -263,14 +279,14 @@ private fun CreatorRow(
Column(Modifier.weight(1f)) {
Text(observeUserNameByHex(me, accountViewModel), style = MaterialTheme.typography.bodyMedium)
Text(
text = stringRes(R.string.cordn_create_you),
text = stringRes(Res.string.cordn_create_you),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
if (!egalitarian) {
Text(
text = stringRes(R.string.cordn_create_admin),
text = stringRes(Res.string.cordn_create_admin),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.primary,
)
@@ -304,9 +320,9 @@ private fun MemberRow(
Text(
text =
when {
answered == 0 -> stringRes(R.string.cordn_create_reach_unknown)
reaching > 0 -> pluralStringRes(LocalContext.current, R.plurals.cordn_create_reach_count, reaching, reaching)
else -> stringRes(R.string.cordn_create_reach_none)
answered == 0 -> stringRes(Res.string.cordn_create_reach_unknown)
reaching > 0 -> pluralStringRes(Res.plurals.cordn_create_reach_count, reaching, reaching)
else -> stringRes(Res.string.cordn_create_reach_none)
},
style = MaterialTheme.typography.labelSmall,
color =
@@ -323,7 +339,7 @@ private fun MemberRow(
if (!egalitarian) {
TextButton(onClick = onToggleAdmin) {
Text(
text = stringRes(R.string.cordn_create_admin),
text = stringRes(Res.string.cordn_create_admin),
style = MaterialTheme.typography.labelMedium,
color = if (isAdmin) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -333,7 +349,7 @@ private fun MemberRow(
IconButton(onClick = onRemove) {
Icon(
symbol = MaterialSymbols.PersonRemove,
contentDescription = stringRes(R.string.cordn_info_remove_member),
contentDescription = stringRes(Res.string.cordn_info_remove_member),
modifier = Modifier.size(20.dp),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -69,7 +69,6 @@ import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.cordn.CordnGroupManager
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
@@ -78,10 +77,33 @@ import com.vitorpamplona.amethyst.commons.model.navigation.Route
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.back
import com.vitorpamplona.amethyst.commons.resources.cancel
import com.vitorpamplona.amethyst.commons.resources.cordn_action_editing
import com.vitorpamplona.amethyst.commons.resources.cordn_action_unpin
import com.vitorpamplona.amethyst.commons.resources.cordn_chat_empty_description
import com.vitorpamplona.amethyst.commons.resources.cordn_chat_empty_title
import com.vitorpamplona.amethyst.commons.resources.cordn_group_info
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable
import com.vitorpamplona.amethyst.commons.resources.cordn_group_untitled
import com.vitorpamplona.amethyst.commons.resources.cordn_media_download_failed
import com.vitorpamplona.amethyst.commons.resources.cordn_media_no_server
import com.vitorpamplona.amethyst.commons.resources.cordn_media_unreadable
import com.vitorpamplona.amethyst.commons.resources.cordn_media_upload_failed
import com.vitorpamplona.amethyst.commons.resources.cordn_member_count
import com.vitorpamplona.amethyst.commons.resources.cordn_message_deleted
import com.vitorpamplona.amethyst.commons.resources.cordn_pinned_by
import com.vitorpamplona.amethyst.commons.resources.cordn_pinned_count
import com.vitorpamplona.amethyst.commons.resources.cordn_pinned_next
import com.vitorpamplona.amethyst.commons.resources.cordn_pinned_previous
import com.vitorpamplona.amethyst.commons.resources.cordn_pinned_show_all
import com.vitorpamplona.amethyst.commons.resources.cordn_pinned_title
import com.vitorpamplona.amethyst.commons.resources.cordn_send_failed
import com.vitorpamplona.amethyst.commons.resources.cordn_send_no_session
import com.vitorpamplona.amethyst.commons.resources.cordn_voice_record
import com.vitorpamplona.amethyst.commons.resources.cordn_voice_stop
import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlImage
import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlVideo
import com.vitorpamplona.amethyst.commons.ui.components.EmptyState
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
@@ -152,7 +174,7 @@ fun CordnGroupChatScreen(
// (it was forgotten, or never opened) and not an error to throw at the
// user as a blank screen.
Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.Center) {
Text(stringRes(R.string.cordn_group_unavailable), style = MaterialTheme.typography.bodyLarge)
Text(stringRes(Res.string.cordn_group_unavailable), style = MaterialTheme.typography.bodyLarge)
}
return
}
@@ -209,9 +231,9 @@ private fun CordnGroupChat(
// and simply never arrived.
var sendError by remember { mutableStateOf<String?>(null) }
val context = LocalContext.current
val uploadFailed = stringRes(R.string.cordn_media_upload_failed)
val sendFailed = stringRes(R.string.cordn_send_failed)
val noSession = stringRes(R.string.cordn_send_no_session)
val uploadFailed = stringRes(Res.string.cordn_media_upload_failed)
val sendFailed = stringRes(Res.string.cordn_send_failed)
val noSession = stringRes(Res.string.cordn_send_no_session)
fun manager() =
accountViewModel.account.cordnRuntime
@@ -359,8 +381,8 @@ private fun CordnGroupChat(
// action that actually needed it.
if (rows.isEmpty()) {
EmptyState(
title = stringRes(R.string.cordn_chat_empty_title),
description = stringRes(R.string.cordn_chat_empty_description),
title = stringRes(Res.string.cordn_chat_empty_title),
description = stringRes(Res.string.cordn_chat_empty_description),
modifier = Modifier.weight(1f),
)
}
@@ -548,7 +570,7 @@ private fun CordnGroupChat(
}
if (editing != null) {
ComposerBanner(
label = stringRes(R.string.cordn_action_editing),
label = stringRes(Res.string.cordn_action_editing),
onCancel = {
editing = null
room.draft.value = ""
@@ -712,7 +734,7 @@ private fun PinnedRibbon(
IconButton(onClick = { index = (at - 1 + pinned.size) % pinned.size }, modifier = Modifier.size(28.dp)) {
Icon(
symbol = MaterialSymbols.AutoMirrored.KeyboardArrowLeft,
contentDescription = stringRes(R.string.cordn_pinned_previous),
contentDescription = stringRes(Res.string.cordn_pinned_previous),
modifier = Modifier.size(18.dp),
)
}
@@ -724,7 +746,7 @@ private fun PinnedRibbon(
IconButton(onClick = { index = (at + 1) % pinned.size }, modifier = Modifier.size(28.dp)) {
Icon(
symbol = MaterialSymbols.AutoMirrored.KeyboardArrowRight,
contentDescription = stringRes(R.string.cordn_pinned_next),
contentDescription = stringRes(Res.string.cordn_pinned_next),
modifier = Modifier.size(18.dp),
)
}
@@ -733,7 +755,7 @@ private fun PinnedRibbon(
IconButton(onClick = { showingAll = true }, modifier = Modifier.size(28.dp)) {
Icon(
symbol = MaterialSymbols.AutoMirrored.List,
contentDescription = stringRes(R.string.cordn_pinned_show_all),
contentDescription = stringRes(Res.string.cordn_pinned_show_all),
modifier = Modifier.size(18.dp),
)
}
@@ -786,7 +808,7 @@ private fun PinnedLine(
// points at.
text =
if (annotations.isDeleted(message.envelope.id)) {
stringRes(R.string.cordn_message_deleted)
stringRes(Res.string.cordn_message_deleted)
} else {
annotations.contentOf(message.envelope.id).orEmpty()
},
@@ -822,7 +844,7 @@ private fun AllPinnedDialog(
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(MaterialSymbols.PushPin, contentDescription = null, modifier = Modifier.size(18.dp))
Text(
text = stringRes(R.string.cordn_pinned_title),
text = stringRes(Res.string.cordn_pinned_title),
modifier = Modifier.padding(start = 8.dp),
)
}
@@ -830,7 +852,7 @@ private fun AllPinnedDialog(
text = {
Column {
Text(
text = pluralStringRes(LocalContext.current, R.plurals.cordn_pinned_count, pinned.size, pinned.size),
text = pluralStringRes(Res.plurals.cordn_pinned_count, pinned.size, pinned.size),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -861,7 +883,7 @@ private fun AllPinnedDialog(
modifier = Modifier.weight(1f).padding(start = 8.dp),
)
TextButton(onClick = { onUnpin(message) }) {
Text(stringRes(R.string.cordn_action_unpin), style = MaterialTheme.typography.labelSmall)
Text(stringRes(Res.string.cordn_action_unpin), style = MaterialTheme.typography.labelSmall)
}
}
@@ -869,7 +891,7 @@ private fun AllPinnedDialog(
Text(
text =
stringRes(
R.string.cordn_pinned_by,
Res.string.cordn_pinned_by,
observeUserNameByHex(pinnedBy, accountViewModel),
),
style = MaterialTheme.typography.labelSmall,
@@ -880,7 +902,7 @@ private fun AllPinnedDialog(
Text(
text =
if (annotations.isDeleted(message.envelope.id)) {
stringRes(R.string.cordn_message_deleted)
stringRes(Res.string.cordn_message_deleted)
} else {
annotations.contentOf(message.envelope.id).orEmpty()
},
@@ -944,7 +966,7 @@ private fun CordnChatTopBar(
Text(title, maxLines = 1, overflow = TextOverflow.Ellipsis)
if (members.isNotEmpty()) {
Text(
text = pluralStringRes(LocalContext.current, R.plurals.cordn_member_count, members.size, members.size),
text = pluralStringRes(Res.plurals.cordn_member_count, members.size, members.size),
style = MaterialTheme.typography.bodySmall,
)
}
@@ -963,7 +985,7 @@ private fun CordnChatTopBar(
// GroupAdd rather than Info, as in Marmot: the same screen, and the thing
// people come to it for is adding someone.
IconButton(onClick = onInfo) {
Icon(MaterialSymbols.GroupAdd, contentDescription = stringRes(R.string.cordn_group_info))
Icon(MaterialSymbols.GroupAdd, contentDescription = stringRes(Res.string.cordn_group_info))
}
},
)
@@ -995,10 +1017,10 @@ private suspend fun sendAttachment(
// a reason a person can act on.
val session =
accountViewModel.account.cordnRuntime?.sessionOrNull(room.coordinatorPubKey)
?: throw CordnAttachmentException(stringRes(context, R.string.cordn_send_no_session))
?: throw CordnAttachmentException(loadStringRes(Res.string.cordn_send_no_session))
val group =
session.manager.group(room.gid)
?: throw CordnAttachmentException(stringRes(context, R.string.cordn_send_no_session))
?: throw CordnAttachmentException(loadStringRes(Res.string.cordn_send_no_session))
// The gallery inside the dialog can delete what was picked, which leaves an empty
// orchestrator rather than a null one. canPost() now refuses that, but indexing it
@@ -1052,14 +1074,14 @@ private suspend fun sendAttachment(
val name = withContext(Dispatchers.IO) { resolveDisplayName(context, uri) }
val bytes =
withContext(Dispatchers.IO) { context.contentResolver.openInputStream(finalUri)?.use { it.readBytes() } }
?: throw CordnAttachmentException(stringRes(context, R.string.cordn_media_unreadable))
?: throw CordnAttachmentException(loadStringRes(Res.string.cordn_media_unreadable))
// Null means the chosen host has no base URL, which is a setting the person can
// change — the one failure here that is entirely actionable.
val tag =
CordnMediaService(accountViewModel.account)
.upload(group, bytes, mime, name, context, state.selectedServer.baseUrl)
?: throw CordnAttachmentException(stringRes(context, R.string.cordn_media_no_server))
?: throw CordnAttachmentException(loadStringRes(Res.string.cordn_media_no_server))
// Into the room as well, for the same reason every other send is: an
// attachment of your own echoes back as an Echo and would otherwise be
@@ -1140,7 +1162,7 @@ internal fun CordnAttachment(
if (mediaKey == null) {
Text(
text = stringRes(R.string.cordn_media_download_failed),
text = stringRes(Res.string.cordn_media_download_failed),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.error,
)
@@ -1254,7 +1276,7 @@ internal fun VoiceNoteButton(
) {
Icon(
symbol = if (recording) MaterialSymbols.Stop else MaterialSymbols.Mic,
contentDescription = stringRes(if (recording) R.string.cordn_voice_stop else R.string.cordn_voice_record),
contentDescription = stringRes(if (recording) Res.string.cordn_voice_stop else Res.string.cordn_voice_record),
// Matches the attach icon beside it; red only while recording,
// which is the one state worth pulling the eye.
tint = if (recording) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.placeholderText,
@@ -1284,10 +1306,10 @@ private suspend fun sendVoiceNote(
// note that goes nowhere and says nothing is the same bug twice.
val session =
accountViewModel.account.cordnRuntime?.sessionOrNull(room.coordinatorPubKey)
?: throw CordnAttachmentException(stringRes(context, R.string.cordn_send_no_session))
?: throw CordnAttachmentException(loadStringRes(Res.string.cordn_send_no_session))
val group =
session.manager.group(room.gid)
?: throw CordnAttachmentException(stringRes(context, R.string.cordn_send_no_session))
?: throw CordnAttachmentException(loadStringRes(Res.string.cordn_send_no_session))
try {
val bytes = withContext(Dispatchers.IO) { recording.file.readBytes() }
@@ -1304,7 +1326,7 @@ private suspend fun sendVoiceNote(
// is what makes the bubble match the preview.
waveform = recording.amplitudes,
)
?: throw CordnAttachmentException(stringRes(context, R.string.cordn_media_no_server))
?: throw CordnAttachmentException(loadStringRes(Res.string.cordn_media_no_server))
// Into the room as well, for the same reason every other send is: an
// attachment of your own echoes back as an Echo and would otherwise be
// invisible to the person who sent it.
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup
import android.content.Context
import android.util.Log
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable
@@ -58,12 +57,10 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.cordn.CordnGroupException
import com.vitorpamplona.amethyst.commons.cordn.GroupExposure
import com.vitorpamplona.amethyst.commons.cordn.ui.CordnExposureCard
@@ -76,8 +73,58 @@ import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.back
import com.vitorpamplona.amethyst.commons.resources.cancel
import com.vitorpamplona.amethyst.commons.resources.copy_npub_to_clipboard
import com.vitorpamplona.amethyst.commons.resources.cordn_admin_action_failed
import com.vitorpamplona.amethyst.commons.resources.cordn_admin_coordinator_silent
import com.vitorpamplona.amethyst.commons.resources.cordn_admin_no_key_package
import com.vitorpamplona.amethyst.commons.resources.cordn_admin_no_self_remove
import com.vitorpamplona.amethyst.commons.resources.cordn_admin_no_welcome
import com.vitorpamplona.amethyst.commons.resources.cordn_admin_not_a_member
import com.vitorpamplona.amethyst.commons.resources.cordn_admin_someone
import com.vitorpamplona.amethyst.commons.resources.cordn_admin_wrong_key_package
import com.vitorpamplona.amethyst.commons.resources.cordn_create_description
import com.vitorpamplona.amethyst.commons.resources.cordn_create_name
import com.vitorpamplona.amethyst.commons.resources.cordn_exposure_close
import com.vitorpamplona.amethyst.commons.resources.cordn_exposure_open
import com.vitorpamplona.amethyst.commons.resources.cordn_group_info
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail
import com.vitorpamplona.amethyst.commons.resources.cordn_group_untitled
import com.vitorpamplona.amethyst.commons.resources.cordn_info_add_member
import com.vitorpamplona.amethyst.commons.resources.cordn_info_add_member_none
import com.vitorpamplona.amethyst.commons.resources.cordn_info_add_member_note
import com.vitorpamplona.amethyst.commons.resources.cordn_info_add_member_placeholder
import com.vitorpamplona.amethyst.commons.resources.cordn_info_admin_badge
import com.vitorpamplona.amethyst.commons.resources.cordn_info_admins_kept
import com.vitorpamplona.amethyst.commons.resources.cordn_info_coordinator
import com.vitorpamplona.amethyst.commons.resources.cordn_info_coordinator_key
import com.vitorpamplona.amethyst.commons.resources.cordn_info_coordinator_nprofile
import com.vitorpamplona.amethyst.commons.resources.cordn_info_copy_nprofile
import com.vitorpamplona.amethyst.commons.resources.cordn_info_edit_details
import com.vitorpamplona.amethyst.commons.resources.cordn_info_egalitarian
import com.vitorpamplona.amethyst.commons.resources.cordn_info_epoch
import com.vitorpamplona.amethyst.commons.resources.cordn_info_gid
import com.vitorpamplona.amethyst.commons.resources.cordn_info_has_key_package
import com.vitorpamplona.amethyst.commons.resources.cordn_info_members
import com.vitorpamplona.amethyst.commons.resources.cordn_info_remove_confirm_body
import com.vitorpamplona.amethyst.commons.resources.cordn_info_remove_confirm_title
import com.vitorpamplona.amethyst.commons.resources.cordn_info_remove_member
import com.vitorpamplona.amethyst.commons.resources.cordn_info_save
import com.vitorpamplona.amethyst.commons.resources.cordn_info_technical
import com.vitorpamplona.amethyst.commons.resources.cordn_member_count
import com.vitorpamplona.amethyst.commons.resources.cordn_requests_accept
import com.vitorpamplona.amethyst.commons.resources.cordn_requests_admin_only
import com.vitorpamplona.amethyst.commons.resources.cordn_requests_check
import com.vitorpamplona.amethyst.commons.resources.cordn_requests_decline
import com.vitorpamplona.amethyst.commons.resources.cordn_requests_failed
import com.vitorpamplona.amethyst.commons.resources.cordn_requests_none
import com.vitorpamplona.amethyst.commons.resources.cordn_requests_title
import com.vitorpamplona.amethyst.commons.resources.cordn_send_no_session
import com.vitorpamplona.amethyst.commons.resources.cordn_share_copied
import com.vitorpamplona.amethyst.commons.resources.cordn_share_copy
import com.vitorpamplona.amethyst.commons.resources.cordn_share_explainer
import com.vitorpamplona.amethyst.commons.resources.cordn_share_title
import com.vitorpamplona.amethyst.commons.ui.components.EmptyState
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightChat
import com.vitorpamplona.amethyst.commons.util.toShortDisplay
@@ -124,8 +171,8 @@ fun CordnGroupInfoScreen(
// The app's own empty state, centred and titled, rather than a
// sentence stranded in the top-left corner.
EmptyState(
title = stringRes(R.string.cordn_group_unavailable),
description = stringRes(R.string.cordn_group_unavailable_detail),
title = stringRes(Res.string.cordn_group_unavailable),
description = stringRes(Res.string.cordn_group_unavailable_detail),
modifier = Modifier.padding(padding),
)
}
@@ -151,7 +198,7 @@ private fun InfoTopBar(
Icon(MaterialSymbols.AutoMirrored.ArrowBack, contentDescription = stringRes(Res.string.back))
}
},
title = { Text(stringRes(R.string.cordn_group_info)) },
title = { Text(stringRes(Res.string.cordn_group_info)) },
actions = actions,
)
}
@@ -208,8 +255,8 @@ private fun CordnGroupInfo(
DisposableEffect(Unit) {
onDispose { userSuggestions.reset() }
}
val failed = stringRes(R.string.cordn_admin_action_failed)
val noSession = stringRes(R.string.cordn_send_no_session)
val failed = stringRes(Res.string.cordn_admin_action_failed)
val noSession = stringRes(Res.string.cordn_send_no_session)
// Computed, never asserted: hardcoding these made the card look like a
// disclosure while reporting the same four values for every group.
@@ -236,7 +283,6 @@ private fun CordnGroupInfo(
* roster, and a rename stayed the old name. The runtime's own methods pair
* each commit with that refresh.
*/
val context = LocalContext.current
fun runAdmin(
who: String? = null,
@@ -257,7 +303,7 @@ private fun CordnGroupInfo(
// and gids in full, so the screen that had just shown a face
// answered with 64 hex characters.
Log.w("CordnGroupInfo", "admin action failed in ${room.gid}: ${e.message}", e)
adminError = adminFailureText(context, e, who, failed)
adminError = adminFailureText(e, who, failed)
} finally {
busy = false
}
@@ -274,7 +320,7 @@ private fun CordnGroupInfo(
IconButton(onClick = { renaming = true }, enabled = !busy) {
Icon(
symbol = MaterialSymbols.Edit,
contentDescription = stringRes(R.string.cordn_info_edit_details),
contentDescription = stringRes(Res.string.cordn_info_edit_details),
)
}
}
@@ -303,7 +349,7 @@ private fun CordnGroupInfo(
}
// A subtitle, not a labelled row above a list that is itself the count.
Text(
text = pluralStringRes(LocalContext.current, R.plurals.cordn_member_count, members.size, members.size),
text = pluralStringRes(Res.plurals.cordn_member_count, members.size, members.size),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 4.dp),
@@ -321,7 +367,7 @@ private fun CordnGroupInfo(
// ContextVM addresses it with ordinary p-tags, so it has a kind 0 like
// anyone else and the app can already resolve it to a name, an avatar
// and a profile to tap through to.
Text(stringRes(R.string.cordn_info_coordinator), style = MaterialTheme.typography.titleMedium)
Text(stringRes(Res.string.cordn_info_coordinator), style = MaterialTheme.typography.titleMedium)
Row(
Modifier.fillMaxWidth().padding(top = 8.dp),
@@ -347,7 +393,7 @@ private fun CordnGroupInfo(
IconButton(onClick = { showExposure = true }, enabled = exposure != null) {
Icon(
symbol = MaterialSymbols.Info,
contentDescription = stringRes(R.string.cordn_exposure_open),
contentDescription = stringRes(Res.string.cordn_exposure_open),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
@@ -355,7 +401,7 @@ private fun CordnGroupInfo(
HorizontalDivider(Modifier.padding(vertical = 16.dp))
Text(stringRes(R.string.cordn_info_members), style = MaterialTheme.typography.titleMedium)
Text(stringRes(Res.string.cordn_info_members), style = MaterialTheme.typography.titleMedium)
// Said once, and only when it is true. An empty admin set is not "none
// configured" -- spec/01.md makes it permanently egalitarian, so the
@@ -364,7 +410,7 @@ private fun CordnGroupInfo(
// a count of them adds nothing.
if (admins.isEmpty()) {
Text(
text = stringRes(R.string.cordn_info_egalitarian),
text = stringRes(Res.string.cordn_info_egalitarian),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 2.dp, bottom = 4.dp),
@@ -385,7 +431,7 @@ private fun CordnGroupInfo(
)
if (member in admins) {
Text(
text = stringRes(R.string.cordn_info_admin_badge),
text = stringRes(Res.string.cordn_info_admin_badge),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.primary,
)
@@ -398,7 +444,7 @@ private fun CordnGroupInfo(
IconButton(onClick = { removing = member }, enabled = !busy) {
Icon(
symbol = MaterialSymbols.PersonRemove,
contentDescription = stringRes(R.string.cordn_info_remove_member),
contentDescription = stringRes(Res.string.cordn_info_remove_member),
modifier = Modifier.size(20.dp),
tint = MaterialTheme.colorScheme.error,
)
@@ -476,9 +522,9 @@ private fun CordnGroupInfo(
// would be a setting nobody should be nudged into.
AlertDialog(
onDismissRequest = { removing = null },
title = { Text(stringRes(R.string.cordn_info_remove_confirm_title)) },
title = { Text(stringRes(Res.string.cordn_info_remove_confirm_title)) },
text = {
Text(stringRes(R.string.cordn_info_remove_confirm_body, removingName))
Text(stringRes(Res.string.cordn_info_remove_confirm_body, removingName))
},
confirmButton = {
TextButton(onClick = {
@@ -486,7 +532,7 @@ private fun CordnGroupInfo(
runAdmin(removingName) { it.removeMember(coordinatorPubKey, room.gid, target) }
}) {
Text(
text = stringRes(R.string.cordn_info_remove_member),
text = stringRes(Res.string.cordn_info_remove_member),
color = MaterialTheme.colorScheme.error,
)
}
@@ -518,7 +564,7 @@ private fun CordnGroupInfo(
text = { CordnExposureCard(it) },
confirmButton = {
TextButton(onClick = { showExposure = false }) {
Text(stringRes(R.string.cordn_exposure_close))
Text(stringRes(Res.string.cordn_exposure_close))
}
},
)
@@ -577,15 +623,15 @@ private fun CordnAddMember(
OutlinedTextField(
value = search,
onValueChange = onSearchChange,
label = { Text(stringRes(R.string.cordn_info_add_member)) },
placeholder = { Text(stringRes(R.string.cordn_info_add_member_placeholder)) },
label = { Text(stringRes(Res.string.cordn_info_add_member)) },
placeholder = { Text(stringRes(Res.string.cordn_info_add_member_placeholder)) },
singleLine = true,
enabled = !busy,
modifier = Modifier.fillMaxWidth(),
)
Text(
text = stringRes(R.string.cordn_info_add_member_note),
text = stringRes(Res.string.cordn_info_add_member_note),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 4.dp),
@@ -601,7 +647,7 @@ private fun CordnAddMember(
modifier = SuggestionListDefaultHeightChat,
onEmpty = {
Text(
text = stringRes(R.string.cordn_info_add_member_none),
text = stringRes(Res.string.cordn_info_add_member_none),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
@@ -612,7 +658,7 @@ private fun CordnAddMember(
if (reachable?.contains(user.pubkeyHex) == true) {
Icon(
symbol = MaterialSymbols.Key,
contentDescription = stringRes(R.string.cordn_info_has_key_package),
contentDescription = stringRes(Res.string.cordn_info_has_key_package),
modifier = Modifier.size(16.dp),
tint = MaterialTheme.colorScheme.primary,
)
@@ -620,7 +666,7 @@ private fun CordnAddMember(
IconButton(onClick = { onInvite(user) }) {
Icon(
symbol = MaterialSymbols.PersonAdd,
contentDescription = stringRes(R.string.cordn_info_add_member),
contentDescription = stringRes(Res.string.cordn_info_add_member),
tint = MaterialTheme.colorScheme.primary,
)
}
@@ -643,20 +689,19 @@ private fun CordnAddMember(
* better than silence — and the caller has logged the throwable in full either
* way.
*/
private fun adminFailureText(
context: Context,
private suspend fun adminFailureText(
e: Throwable,
who: String?,
fallback: String,
): String {
val name = who ?: stringRes(context, R.string.cordn_admin_someone)
val name = who ?: loadStringRes(Res.string.cordn_admin_someone)
return when (e) {
is CordnGroupException ->
when (e.reason) {
CordnGroupException.Reason.NO_KEY_PACKAGE -> stringRes(context, R.string.cordn_admin_no_key_package, name)
CordnGroupException.Reason.WRONG_KEY_PACKAGE_OWNER -> stringRes(context, R.string.cordn_admin_wrong_key_package, name)
CordnGroupException.Reason.NO_WELCOME -> stringRes(context, R.string.cordn_admin_no_welcome, name)
CordnGroupException.Reason.NOT_A_MEMBER -> stringRes(context, R.string.cordn_admin_not_a_member, name)
CordnGroupException.Reason.NO_KEY_PACKAGE -> loadStringRes(Res.string.cordn_admin_no_key_package, name)
CordnGroupException.Reason.WRONG_KEY_PACKAGE_OWNER -> loadStringRes(Res.string.cordn_admin_wrong_key_package, name)
CordnGroupException.Reason.NO_WELCOME -> loadStringRes(Res.string.cordn_admin_no_welcome, name)
CordnGroupException.Reason.NOT_A_MEMBER -> loadStringRes(Res.string.cordn_admin_not_a_member, name)
CordnGroupException.Reason.OTHER -> e.message ?: fallback
}
@@ -664,14 +709,14 @@ private fun adminFailureText(
// roster and the button disagreed rather than that anyone tried.
is IllegalArgumentException ->
if (e.message?.contains("self-removal") == true) {
stringRes(context, R.string.cordn_admin_no_self_remove)
loadStringRes(Res.string.cordn_admin_no_self_remove)
} else {
e.message ?: fallback
}
// A coordinator that does not answer is the single commonest failure
// here and says nothing useful in its own words.
is TimeoutCancellationException -> stringRes(context, R.string.cordn_admin_coordinator_silent)
is TimeoutCancellationException -> loadStringRes(Res.string.cordn_admin_coordinator_silent)
else -> e.message ?: fallback
}
@@ -701,7 +746,7 @@ private fun TechnicalDetails(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringRes(R.string.cordn_info_technical),
text = stringRes(Res.string.cordn_info_technical),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.weight(1f),
)
@@ -725,22 +770,22 @@ private fun TechnicalDetails(
val coordinator = remember(coordinatorPubKey) { LocalCache.getOrCreateUser(coordinatorPubKey) }
CopyableKeyRow(
label = stringRes(R.string.cordn_info_coordinator_key),
label = stringRes(Res.string.cordn_info_coordinator_key),
shown = coordinator.pubkeyDisplayHex(),
copied = coordinator.pubkeyNpub(),
copyDescription = stringRes(Res.string.copy_npub_to_clipboard),
)
CopyableKeyRow(
label = stringRes(R.string.cordn_info_coordinator_nprofile),
label = stringRes(Res.string.cordn_info_coordinator_nprofile),
shown = coordinator.toNProfile().toShortDisplay(6),
copied = coordinator.toNProfile(),
copyDescription = stringRes(R.string.cordn_info_copy_nprofile),
copyDescription = stringRes(Res.string.cordn_info_copy_nprofile),
)
SelectionContainer {
Column {
InfoRow(stringRes(R.string.cordn_info_gid), gid)
InfoRow(stringRes(R.string.cordn_info_epoch), epoch.toString())
InfoRow(stringRes(Res.string.cordn_info_gid), gid)
InfoRow(stringRes(Res.string.cordn_info_epoch), epoch.toString())
}
}
}
@@ -835,7 +880,7 @@ private fun JoinRequests(
) {
val runtime = accountViewModel.account.cordnRuntime ?: return
val scope = rememberCoroutineScope()
val failed = stringRes(R.string.cordn_requests_failed)
val failed = stringRes(Res.string.cordn_requests_failed)
var requests by remember { mutableStateOf<List<JoinRequest>?>(null) }
var busy by remember { mutableStateOf(false) }
@@ -853,15 +898,15 @@ private fun JoinRequests(
}
}
Text(stringRes(R.string.cordn_requests_title), style = MaterialTheme.typography.titleMedium)
Text(stringRes(Res.string.cordn_requests_title), style = MaterialTheme.typography.titleMedium)
Text(
text = stringRes(R.string.cordn_requests_admin_only),
text = stringRes(Res.string.cordn_requests_admin_only),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
OutlinedButton(onClick = { scope.launch { reload() } }, enabled = !busy) {
Text(stringRes(R.string.cordn_requests_check))
Text(stringRes(Res.string.cordn_requests_check))
}
error?.let { Text(it, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error) }
@@ -870,7 +915,7 @@ private fun JoinRequests(
if (loaded != null && !busy) {
if (loaded.isEmpty()) {
Text(
text = stringRes(R.string.cordn_requests_none),
text = stringRes(Res.string.cordn_requests_none),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -899,7 +944,7 @@ private fun JoinRequests(
reload()
}
}) {
Text(stringRes(R.string.cordn_requests_accept))
Text(stringRes(Res.string.cordn_requests_accept))
}
TextButton(onClick = {
scope.launch {
@@ -911,7 +956,7 @@ private fun JoinRequests(
reload()
}
}) {
Text(stringRes(R.string.cordn_requests_decline))
Text(stringRes(Res.string.cordn_requests_decline))
}
}
}
@@ -949,9 +994,9 @@ private fun ShareGroup(
val clipboard = LocalClipboardManager.current
var copied by remember { mutableStateOf(false) }
Text(stringRes(R.string.cordn_share_title), style = MaterialTheme.typography.titleMedium)
Text(stringRes(Res.string.cordn_share_title), style = MaterialTheme.typography.titleMedium)
Text(
text = stringRes(R.string.cordn_share_explainer),
text = stringRes(Res.string.cordn_share_explainer),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -968,7 +1013,7 @@ private fun ShareGroup(
},
modifier = Modifier.padding(top = 8.dp),
) {
Text(stringRes(if (copied) R.string.cordn_share_copied else R.string.cordn_share_copy))
Text(stringRes(if (copied) Res.string.cordn_share_copied else Res.string.cordn_share_copy))
}
}
@@ -993,26 +1038,26 @@ private fun EditGroupDetailsDialog(
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringRes(R.string.cordn_info_edit_details)) },
title = { Text(stringRes(Res.string.cordn_info_edit_details)) },
text = {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedTextField(
value = draftName,
onValueChange = { draftName = it },
label = { Text(stringRes(R.string.cordn_create_name)) },
label = { Text(stringRes(Res.string.cordn_create_name)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
OutlinedTextField(
value = draftDescription,
onValueChange = { draftDescription = it },
label = { Text(stringRes(R.string.cordn_create_description)) },
label = { Text(stringRes(Res.string.cordn_create_description)) },
singleLine = false,
modifier = Modifier.fillMaxWidth(),
)
if (admins.isNotEmpty()) {
Text(
text = pluralStringRes(LocalContext.current, R.plurals.cordn_info_admins_kept, admins.size, admins.size),
text = pluralStringRes(Res.plurals.cordn_info_admins_kept, admins.size, admins.size),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -1024,7 +1069,7 @@ private fun EditGroupDetailsDialog(
onClick = { onSave(draftName.trim(), draftDescription.trim()) },
enabled = draftName.isNotBlank(),
) {
Text(stringRes(R.string.cordn_info_save))
Text(stringRes(Res.string.cordn_info_save))
}
},
dismissButton = {
@@ -41,10 +41,8 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.cordn.CoordinatorHealth
import com.vitorpamplona.amethyst.commons.cordn.ui.CoordinatorHealthRow
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
@@ -52,6 +50,14 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.model.cordnGroups.CordnGroupChatroom
import com.vitorpamplona.amethyst.commons.model.navigation.Route
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail
import com.vitorpamplona.amethyst.commons.resources.cordn_groups_count
import com.vitorpamplona.amethyst.commons.resources.cordn_groups_down_note
import com.vitorpamplona.amethyst.commons.resources.cordn_groups_manage
import com.vitorpamplona.amethyst.commons.resources.cordn_groups_none
import com.vitorpamplona.amethyst.commons.resources.cordn_groups_none_detail
import com.vitorpamplona.amethyst.commons.resources.cordn_groups_start
import com.vitorpamplona.amethyst.commons.resources.cordn_groups_title
import com.vitorpamplona.amethyst.commons.ui.components.EmptyState
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
@@ -106,14 +112,14 @@ fun CordnGroupListScreen(
IconButton(onClick = { nav.nav(Route.CordnCoordinators) }) {
Icon(
symbol = MaterialSymbols.Dns,
contentDescription = stringRes(R.string.cordn_groups_manage),
contentDescription = stringRes(Res.string.cordn_groups_manage),
modifier = Modifier.size(22.dp),
)
}
IconButton(onClick = { nav.nav(Route.CordnCreateGroup) }) {
Icon(
symbol = MaterialSymbols.Add,
contentDescription = stringRes(R.string.cordn_groups_start),
contentDescription = stringRes(Res.string.cordn_groups_start),
modifier = Modifier.size(24.dp),
)
}
@@ -122,8 +128,8 @@ fun CordnGroupListScreen(
) { padding ->
if (runtime == null) {
EmptyState(
title = stringRes(R.string.cordn_group_unavailable),
description = stringRes(R.string.cordn_group_unavailable_detail),
title = stringRes(Res.string.cordn_group_unavailable),
description = stringRes(Res.string.cordn_group_unavailable_detail),
modifier = Modifier.padding(padding),
)
return@Scaffold
@@ -148,8 +154,8 @@ fun CordnGroupListScreen(
if (sections.isEmpty()) {
EmptyState(
title = stringRes(R.string.cordn_groups_none),
description = stringRes(R.string.cordn_groups_none_detail),
title = stringRes(Res.string.cordn_groups_none),
description = stringRes(Res.string.cordn_groups_none_detail),
modifier = Modifier.padding(padding),
)
return@Scaffold
@@ -230,7 +236,7 @@ private fun CoordinatorSection(
},
)
Text(
text = pluralStringRes(LocalContext.current, R.plurals.cordn_groups_count, groupCount, groupCount),
text = pluralStringRes(Res.plurals.cordn_groups_count, groupCount, groupCount),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -248,7 +254,7 @@ private fun CoordinatorSection(
verticalAlignment = Alignment.Top,
) {
Text(
text = stringRes(R.string.cordn_groups_down_note),
text = stringRes(Res.string.cordn_groups_down_note),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error,
)
@@ -48,16 +48,30 @@ import androidx.compose.runtime.rememberCoroutineScope
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.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.model.navigation.Route
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.back
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail
import com.vitorpamplona.amethyst.commons.resources.cordn_group_untitled
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_accept
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_decline
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_decline_warning
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_explainer
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_load_failed
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_members
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_members_more
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_no_coordinators
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_none
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_refresh
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_skipped
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_title
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_unreachable
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_via
import com.vitorpamplona.amethyst.commons.ui.components.EmptyState
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.model.cordn.CordnInvitation
@@ -69,6 +83,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.cordn.CoordinatorI
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.cordn.coordinatorDisplayName
import com.vitorpamplona.amethyst.ui.stringRes
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.pluralStringResource
/**
* Invitations to cordn groups, waiting to be answered.
@@ -106,7 +121,7 @@ fun CordnInvitationsScreen(
var invitations by remember { mutableStateOf<CordnInvitations?>(null) }
var busy by remember { mutableStateOf(false) }
var error by remember { mutableStateOf<String?>(null) }
val loadFailed = stringRes(R.string.cordn_invitations_load_failed)
val loadFailed = stringRes(Res.string.cordn_invitations_load_failed)
suspend fun reload() {
busy = true
@@ -130,10 +145,10 @@ fun CordnInvitationsScreen(
Icon(MaterialSymbols.AutoMirrored.ArrowBack, contentDescription = stringRes(Res.string.back))
}
},
title = { Text(stringRes(R.string.cordn_invitations_title)) },
title = { Text(stringRes(Res.string.cordn_invitations_title)) },
actions = {
IconButton(onClick = { scope.launch { reload() } }, enabled = !busy) {
Icon(MaterialSymbols.Refresh, contentDescription = stringRes(R.string.cordn_invitations_refresh))
Icon(MaterialSymbols.Refresh, contentDescription = stringRes(Res.string.cordn_invitations_refresh))
}
},
)
@@ -150,8 +165,8 @@ fun CordnInvitationsScreen(
) {
if (runtime == null) {
EmptyState(
title = stringRes(R.string.cordn_group_unavailable),
description = stringRes(R.string.cordn_group_unavailable_detail),
title = stringRes(Res.string.cordn_group_unavailable),
description = stringRes(Res.string.cordn_group_unavailable_detail),
)
return@Column
}
@@ -163,7 +178,7 @@ fun CordnInvitationsScreen(
val coordinators by runtime.coordinators.collectAsStateWithLifecycle()
Text(
text = stringRes(R.string.cordn_invitations_explainer),
text = stringRes(Res.string.cordn_invitations_explainer),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -212,7 +227,7 @@ fun CordnInvitationsScreen(
NoticeCard(
title =
stringRes(
R.string.cordn_invitations_unreachable,
Res.string.cordn_invitations_unreachable,
coordinatorDisplayName(it.coordinator.pubKey, it.coordinator.label, accountViewModel),
),
detail = it.reason,
@@ -225,7 +240,7 @@ fun CordnInvitationsScreen(
// friend swears they sent simply does not exist here.
loaded.skipped.forEach {
NoticeCard(
title = stringRes(R.string.cordn_invitations_skipped),
title = stringRes(Res.string.cordn_invitations_skipped),
detail = it.reason,
isError = false,
)
@@ -235,9 +250,9 @@ fun CordnInvitationsScreen(
Text(
text =
if (coordinators.isEmpty()) {
stringRes(R.string.cordn_invitations_no_coordinators)
stringRes(Res.string.cordn_invitations_no_coordinators)
} else {
stringRes(R.string.cordn_invitations_none)
stringRes(Res.string.cordn_invitations_none)
},
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -276,7 +291,7 @@ private fun InvitationCard(
// "Already in it", never "invited you" — see the screen KDoc.
Text(
text = pluralStringResource(R.plurals.cordn_invitations_members, welcome.members.size, welcome.members.size),
text = pluralStringResource(Res.plurals.cordn_invitations_members, welcome.members.size, welcome.members.size),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -300,7 +315,7 @@ private fun InvitationCard(
}
if (welcome.members.size > MEMBER_FACES) {
Text(
text = stringRes(R.string.cordn_invitations_members_more, welcome.members.size - MEMBER_FACES),
text = stringRes(Res.string.cordn_invitations_members_more, welcome.members.size - MEMBER_FACES),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -329,7 +344,7 @@ private fun InvitationCard(
size = 20.dp,
) { name ->
Text(
text = stringRes(R.string.cordn_invitations_via, name),
text = stringRes(Res.string.cordn_invitations_via, name),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -340,11 +355,11 @@ private fun InvitationCard(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Button(onClick = onAccept) { Text(stringRes(R.string.cordn_invitations_accept)) }
TextButton(onClick = onDecline) { Text(stringRes(R.string.cordn_invitations_decline)) }
Button(onClick = onAccept) { Text(stringRes(Res.string.cordn_invitations_accept)) }
TextButton(onClick = onDecline) { Text(stringRes(Res.string.cordn_invitations_decline)) }
}
Text(
text = stringRes(R.string.cordn_invitations_decline_warning),
text = stringRes(Res.string.cordn_invitations_decline_warning),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -55,7 +55,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.chats.ui.ChatDivisor
import com.vitorpamplona.amethyst.commons.chats.ui.UserDisplayNameLayout
import com.vitorpamplona.amethyst.commons.cordn.CordnMentions
@@ -67,6 +66,24 @@ import com.vitorpamplona.amethyst.commons.model.navigation.Route
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.copied_to_clipboard
import com.vitorpamplona.amethyst.commons.resources.copy_text
import com.vitorpamplona.amethyst.commons.resources.cordn_action_delete
import com.vitorpamplona.amethyst.commons.resources.cordn_action_edit
import com.vitorpamplona.amethyst.commons.resources.cordn_action_go_to_message
import com.vitorpamplona.amethyst.commons.resources.cordn_action_pin
import com.vitorpamplona.amethyst.commons.resources.cordn_action_reply
import com.vitorpamplona.amethyst.commons.resources.cordn_action_unpin
import com.vitorpamplona.amethyst.commons.resources.cordn_chat_unread_divider
import com.vitorpamplona.amethyst.commons.resources.cordn_chat_yesterday
import com.vitorpamplona.amethyst.commons.resources.cordn_delete_confirm_body
import com.vitorpamplona.amethyst.commons.resources.cordn_delete_confirm_title
import com.vitorpamplona.amethyst.commons.resources.cordn_delivery_accepted
import com.vitorpamplona.amethyst.commons.resources.cordn_message_deleted
import com.vitorpamplona.amethyst.commons.resources.cordn_message_details_coordinator
import com.vitorpamplona.amethyst.commons.resources.cordn_message_details_cursor
import com.vitorpamplona.amethyst.commons.resources.cordn_message_details_sent_at
import com.vitorpamplona.amethyst.commons.resources.cordn_message_details_title
import com.vitorpamplona.amethyst.commons.resources.cordn_message_edited
import com.vitorpamplona.amethyst.commons.resources.cordn_reactions_title
import com.vitorpamplona.amethyst.commons.resources.quick_action_share
import com.vitorpamplona.amethyst.commons.resources.today
import com.vitorpamplona.amethyst.commons.ui.components.ClickableBox
@@ -292,7 +309,7 @@ private fun CordnBubbleContents(
when {
text == null ->
Text(
text = stringRes(R.string.cordn_message_deleted),
text = stringRes(Res.string.cordn_message_deleted),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -448,7 +465,7 @@ private fun CordnReactionDetailSheet(
) {
Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState()).padding(bottom = 24.dp)) {
Text(
text = stringRes(R.string.cordn_reactions_title),
text = stringRes(Res.string.cordn_reactions_title),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(horizontal = 20.dp, vertical = 12.dp),
)
@@ -519,7 +536,7 @@ internal fun CordnQuotedMessage(
val body =
if (annotations.isDeleted(parent.envelope.id)) {
stringRes(R.string.cordn_message_deleted)
stringRes(Res.string.cordn_message_deleted)
} else {
annotations.contentOf(parent.envelope.id).orEmpty()
}
@@ -575,7 +592,7 @@ private fun CordnQuoteActionSheet(
Column(Modifier.padding(bottom = 24.dp)) {
TileRow {
if (onGoToMessage != null) {
ActionTile(MaterialSymbols.ArrowUpward, stringRes(R.string.cordn_action_go_to_message)) {
ActionTile(MaterialSymbols.ArrowUpward, stringRes(Res.string.cordn_action_go_to_message)) {
onGoToMessage()
onDismiss()
}
@@ -594,7 +611,7 @@ private fun CordnQuoteActionSheet(
*/
@Composable
internal fun UnreadDivider() {
ChatDivisor(stringRes(R.string.cordn_chat_unread_divider), MaterialTheme.colorScheme.primary)
ChatDivisor(stringRes(Res.string.cordn_chat_unread_divider), MaterialTheme.colorScheme.primary)
}
/**
@@ -618,7 +635,7 @@ private fun CordnMessageFooter(
if (isPinned) {
Icon(
symbol = MaterialSymbols.PushPin,
contentDescription = stringRes(R.string.cordn_action_pin),
contentDescription = stringRes(Res.string.cordn_action_pin),
modifier = Modifier.size(12.dp),
tint = MaterialTheme.colorScheme.primary,
)
@@ -627,7 +644,7 @@ private fun CordnMessageFooter(
if (isEdited) {
Text(
text = stringRes(R.string.cordn_message_edited),
text = stringRes(Res.string.cordn_message_edited),
fontSize = Font12SP,
color = MaterialTheme.colorScheme.placeholderText,
maxLines = 1,
@@ -658,7 +675,7 @@ private fun CordnMessageFooter(
Spacer(StdHorzSpacer)
Icon(
symbol = MaterialSymbols.Done,
contentDescription = stringRes(R.string.cordn_delivery_accepted),
contentDescription = stringRes(Res.string.cordn_delivery_accepted),
modifier = Modifier.size(12.dp),
tint = MaterialTheme.colorScheme.allGoodColor,
)
@@ -711,18 +728,18 @@ private fun CordnMessageDetailsSheet(
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
Text(
text = stringRes(R.string.cordn_message_details_title),
text = stringRes(Res.string.cordn_message_details_title),
style = MaterialTheme.typography.titleSmall,
)
DetailLine(stringRes(R.string.cordn_message_details_sent_at), sentAt)
DetailLine(stringRes(R.string.cordn_message_details_cursor), message.cursor.toString())
DetailLine(stringRes(Res.string.cordn_message_details_sent_at), sentAt)
DetailLine(stringRes(Res.string.cordn_message_details_cursor), message.cursor.toString())
DetailLine(
stringRes(R.string.cordn_message_details_coordinator),
stringRes(Res.string.cordn_message_details_coordinator),
coordinatorDisplayName(coordinatorPubKey, label = null, accountViewModel = accountViewModel),
)
if (isMine) {
Text(
text = stringRes(R.string.cordn_delivery_accepted),
text = stringRes(Res.string.cordn_delivery_accepted),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.allGoodColor,
)
@@ -797,10 +814,10 @@ private fun CordnMessageActionSheet(
if (confirmingDelete) {
QuickActionAlertDialog(
title = stringRes(R.string.cordn_delete_confirm_title),
textContent = stringRes(R.string.cordn_delete_confirm_body),
title = stringRes(Res.string.cordn_delete_confirm_title),
textContent = stringRes(Res.string.cordn_delete_confirm_body),
buttonIcon = MaterialSymbols.Delete,
buttonText = stringRes(R.string.cordn_action_delete),
buttonText = stringRes(Res.string.cordn_action_delete),
onClickDoOnce = performDelete,
onClickDontShowAgain = {
accountViewModel.account.settings.setHideDeleteRequestDialog()
@@ -843,7 +860,7 @@ private fun CordnMessageActionSheet(
TileRow {
if (isLive) {
ActionTile(MaterialSymbols.AutoMirrored.Chat, stringRes(R.string.cordn_action_reply)) {
ActionTile(MaterialSymbols.AutoMirrored.Chat, stringRes(Res.string.cordn_action_reply)) {
onReply()
onDismiss()
}
@@ -853,7 +870,7 @@ private fun CordnMessageActionSheet(
// message rather than only on your own.
ActionTile(
MaterialSymbols.PushPin,
stringRes(if (isPinned) R.string.cordn_action_unpin else R.string.cordn_action_pin),
stringRes(if (isPinned) Res.string.cordn_action_unpin else Res.string.cordn_action_pin),
) {
onTogglePin()
onDismiss()
@@ -863,11 +880,11 @@ private fun CordnMessageActionSheet(
// anyone else. Hiding them here is the same rule, stated where it stops
// being a surprise.
if (isMine && isLive) {
ActionTile(MaterialSymbols.Edit, stringRes(R.string.cordn_action_edit)) {
ActionTile(MaterialSymbols.Edit, stringRes(Res.string.cordn_action_edit)) {
onEdit()
onDismiss()
}
ActionTile(MaterialSymbols.Delete, stringRes(R.string.cordn_action_delete), isDestructive = true) {
ActionTile(MaterialSymbols.Delete, stringRes(Res.string.cordn_action_delete), isDestructive = true) {
// A withdrawal cannot be taken back and the coordinator keeps
// the ciphertext either way, so it is worth one question —
// unless the account has already opted out of being asked.
@@ -989,7 +1006,7 @@ internal fun DaySeparator(
val label =
when (day) {
today -> stringRes(Res.string.today)
today.minusDays(1) -> stringRes(R.string.cordn_chat_yesterday)
today.minusDays(1) -> stringRes(Res.string.cordn_chat_yesterday)
// Year included only when it is not this one: printing 2026 on every
// divider all year is noise.
else ->
@@ -52,13 +52,26 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.cancel
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_contents_body
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_contents_title
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_explainer
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_export
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_exported
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_failed
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_passphrase
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_restore
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_restore_body
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_restore_confirm_body
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_restore_confirm_title
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_restored
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_section_export
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_section_passphrase
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_section_restore
import com.vitorpamplona.amethyst.commons.resources.cordn_backup_title
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail
import com.vitorpamplona.amethyst.commons.ui.components.EmptyState
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton
@@ -113,9 +126,9 @@ fun CordnBackupScreen(
var error by remember { mutableStateOf<String?>(null) }
var pendingRestore by remember { mutableStateOf<Uri?>(null) }
val exported = stringRes(R.string.cordn_backup_exported)
val restored = stringRes(R.string.cordn_backup_restored)
val failed = stringRes(R.string.cordn_backup_failed)
val exported = stringRes(Res.string.cordn_backup_exported)
val restored = stringRes(Res.string.cordn_backup_restored)
val failed = stringRes(Res.string.cordn_backup_failed)
val saver =
rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/octet-stream")) { uri ->
@@ -179,8 +192,8 @@ fun CordnBackupScreen(
// The app's own empty state, centred and titled, rather than a
// sentence stranded in the top-left corner.
EmptyState(
title = stringRes(R.string.cordn_group_unavailable),
description = stringRes(R.string.cordn_group_unavailable_detail),
title = stringRes(Res.string.cordn_group_unavailable),
description = stringRes(Res.string.cordn_group_unavailable_detail),
modifier = Modifier.padding(padding),
)
return@Scaffold
@@ -196,7 +209,7 @@ fun CordnBackupScreen(
verticalArrangement = Arrangement.spacedBy(20.dp),
) {
Text(
text = stringRes(R.string.cordn_backup_explainer),
text = stringRes(Res.string.cordn_backup_explainer),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -212,7 +225,7 @@ fun CordnBackupScreen(
passphrase = it
error = null
},
label = { Text(stringRes(R.string.cordn_backup_passphrase)) },
label = { Text(stringRes(Res.string.cordn_backup_passphrase)) },
visualTransformation = PasswordVisualTransformation(),
// The dots are only half of it. Without the password
// keyboard type the IME treats this as ordinary prose:
@@ -230,9 +243,9 @@ fun CordnBackupScreen(
SettingsSection(Res.string.cordn_backup_section_export) {
SettingsFormBlock {
Text(stringRes(R.string.cordn_backup_contents_title), style = MaterialTheme.typography.titleSmall)
Text(stringRes(Res.string.cordn_backup_contents_title), style = MaterialTheme.typography.titleSmall)
Text(
text = stringRes(R.string.cordn_backup_contents_body),
text = stringRes(Res.string.cordn_backup_contents_body),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -248,7 +261,7 @@ fun CordnBackupScreen(
// Key derivation here is scrypt, which is slow on
// purpose, so a greyed-out button was the only sign
// anything was happening for several seconds.
BusyLabel(busy, stringRes(R.string.cordn_backup_export))
BusyLabel(busy, stringRes(Res.string.cordn_backup_export))
}
}
}
@@ -256,7 +269,7 @@ fun CordnBackupScreen(
SettingsSection(Res.string.cordn_backup_section_restore) {
SettingsFormBlock {
Text(
text = stringRes(R.string.cordn_backup_restore_body),
text = stringRes(Res.string.cordn_backup_restore_body),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -266,7 +279,7 @@ fun CordnBackupScreen(
enabled = !busy && passphrase.isNotBlank(),
modifier = Modifier.fillMaxWidth(),
) {
BusyLabel(busy, stringRes(R.string.cordn_backup_restore))
BusyLabel(busy, stringRes(Res.string.cordn_backup_restore))
}
}
}
@@ -276,8 +289,8 @@ fun CordnBackupScreen(
pendingRestore?.let { uri ->
AlertDialog(
onDismissRequest = { pendingRestore = null },
title = { Text(stringRes(R.string.cordn_backup_restore_confirm_title)) },
text = { Text(stringRes(R.string.cordn_backup_restore_confirm_body)) },
title = { Text(stringRes(Res.string.cordn_backup_restore_confirm_title)) },
text = { Text(stringRes(Res.string.cordn_backup_restore_confirm_body)) },
confirmButton = {
TextButton(onClick = {
pendingRestore = null
@@ -302,7 +315,7 @@ fun CordnBackupScreen(
}
}
}) {
Text(stringRes(R.string.cordn_backup_restore), color = MaterialTheme.colorScheme.error)
Text(stringRes(Res.string.cordn_backup_restore), color = MaterialTheme.colorScheme.error)
}
},
dismissButton = {
@@ -58,7 +58,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.cordn.CoordinatorConfig
import com.vitorpamplona.amethyst.commons.cordn.CoordinatorHealth
import com.vitorpamplona.amethyst.commons.cordn.CordnCoordinatorDiscovery
@@ -68,10 +67,45 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.cancel
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_add_action
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_add_failed
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_copy_key
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_action
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_add
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_explainer
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_failed
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_none
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_seen
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_discover_unheard
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_explainer
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_health_down
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_health_ok
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_health_retrying
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_health_unknown
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_identify
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_key
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_label
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_label_note
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_more
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_none
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_purge
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_purge_body
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_purge_title
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_relays_label
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_remove
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_rename
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_section_discover
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_section_manual
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_section_yours
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_server
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_server_claim
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_server_silent
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_title
import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_pubkey
import com.vitorpamplona.amethyst.commons.resources.cordn_create_coordinator_relays
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail
import com.vitorpamplona.amethyst.commons.resources.cordn_info_save
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.commons.ui.components.EmptyState
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
@@ -127,8 +161,8 @@ fun CordnCoordinatorsScreen(
// The app's own empty state, centred and titled, rather than a
// sentence stranded in the top-left corner.
EmptyState(
title = stringRes(R.string.cordn_group_unavailable),
description = stringRes(R.string.cordn_group_unavailable_detail),
title = stringRes(Res.string.cordn_group_unavailable),
description = stringRes(Res.string.cordn_group_unavailable_detail),
modifier = Modifier.padding(padding),
)
return@Scaffold
@@ -146,7 +180,7 @@ fun CordnCoordinatorsScreen(
verticalArrangement = Arrangement.spacedBy(20.dp),
) {
Text(
text = stringRes(R.string.cordn_coordinators_explainer),
text = stringRes(Res.string.cordn_coordinators_explainer),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -165,7 +199,7 @@ fun CordnCoordinatorsScreen(
if (coordinators.isEmpty()) {
Text(
text = stringRes(R.string.cordn_coordinators_none),
text = stringRes(Res.string.cordn_coordinators_none),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -225,7 +259,7 @@ private fun CoordinatorCard(
IconButton(onClick = { renaming = true }) {
Icon(
symbol = MaterialSymbols.Edit,
contentDescription = stringRes(R.string.cordn_coordinators_rename),
contentDescription = stringRes(Res.string.cordn_coordinators_rename),
modifier = Modifier.size(18.dp),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -264,10 +298,10 @@ private fun CoordinatorCard(
// away on the clipboard, which is how a comparison is actually done.
val coordinator = remember(config.pubKey) { LocalCache.getOrCreateUser(config.pubKey) }
CopyableKeyRow(
label = stringRes(R.string.cordn_coordinators_key),
label = stringRes(Res.string.cordn_coordinators_key),
shown = coordinator.pubkeyDisplayHex(),
copied = coordinator.pubkeyNpub(),
copyDescription = stringRes(R.string.cordn_coordinators_copy_key),
copyDescription = stringRes(Res.string.cordn_coordinators_copy_key),
)
// Every relay, not the first two and a count: this is the management
@@ -275,7 +309,7 @@ private fun CoordinatorCard(
// came here to check or to rule out.
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text(
text = stringRes(R.string.cordn_coordinators_relays_label),
text = stringRes(Res.string.cordn_coordinators_relays_label),
style = MaterialTheme.typography.labelMedium,
fontWeight = FontWeight.SemiBold,
)
@@ -304,10 +338,10 @@ private fun CoordinatorCard(
text =
info?.let {
stringRes(
R.string.cordn_coordinators_server,
Res.string.cordn_coordinators_server,
listOfNotNull(it.name, it.version, it.protocolVersion).joinToString(" \u00b7 "),
)
} ?: stringRes(R.string.cordn_coordinators_server_silent),
} ?: stringRes(Res.string.cordn_coordinators_server_silent),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -315,7 +349,7 @@ private fun CoordinatorCard(
// Said next to it, every time, because a name on a settings
// screen reads as verified and this one is not: §8.5 makes
// the pubkey the identity and nothing else.
text = stringRes(R.string.cordn_coordinators_server_claim),
text = stringRes(Res.string.cordn_coordinators_server_claim),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -333,7 +367,7 @@ private fun CoordinatorCard(
infoChecked = true
}
}) {
Text(stringRes(R.string.cordn_coordinators_identify))
Text(stringRes(Res.string.cordn_coordinators_identify))
}
Spacer(Modifier.weight(1f))
@@ -345,14 +379,14 @@ private fun CoordinatorCard(
IconButton(onClick = { menuOpen = true }) {
Icon(
symbol = MaterialSymbols.MoreVert,
contentDescription = stringRes(R.string.cordn_coordinators_more),
contentDescription = stringRes(Res.string.cordn_coordinators_more),
modifier = Modifier.size(20.dp),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
DropdownMenu(expanded = menuOpen, onDismissRequest = { menuOpen = false }) {
DropdownMenuItem(
text = { Text(stringRes(R.string.cordn_coordinators_remove)) },
text = { Text(stringRes(Res.string.cordn_coordinators_remove)) },
onClick = {
menuOpen = false
scope.launch { runtime.forget(config.pubKey) }
@@ -361,7 +395,7 @@ private fun CoordinatorCard(
DropdownMenuItem(
text = {
Text(
text = stringRes(R.string.cordn_coordinators_purge),
text = stringRes(Res.string.cordn_coordinators_purge),
color = MaterialTheme.colorScheme.error,
)
},
@@ -378,14 +412,14 @@ private fun CoordinatorCard(
if (confirmingPurge) {
AlertDialog(
onDismissRequest = { confirmingPurge = false },
title = { Text(stringRes(R.string.cordn_coordinators_purge_title)) },
text = { Text(stringRes(R.string.cordn_coordinators_purge_body)) },
title = { Text(stringRes(Res.string.cordn_coordinators_purge_title)) },
text = { Text(stringRes(Res.string.cordn_coordinators_purge_body)) },
confirmButton = {
TextButton(onClick = {
confirmingPurge = false
scope.launch { runtime.purge(config.pubKey) }
}) {
Text(stringRes(R.string.cordn_coordinators_purge), color = MaterialTheme.colorScheme.error)
Text(stringRes(Res.string.cordn_coordinators_purge), color = MaterialTheme.colorScheme.error)
}
},
dismissButton = {
@@ -409,10 +443,10 @@ private fun CoordinatorCard(
private fun HealthLine(state: CoordinatorHealth.State) {
val text =
when {
state.isUnknown -> stringRes(R.string.cordn_coordinators_health_unknown)
state.isDown -> stringRes(R.string.cordn_coordinators_health_down, state.consecutiveFailures)
state.consecutiveFailures > 0 -> stringRes(R.string.cordn_coordinators_health_retrying)
else -> stringRes(R.string.cordn_coordinators_health_ok)
state.isUnknown -> stringRes(Res.string.cordn_coordinators_health_unknown)
state.isDown -> stringRes(Res.string.cordn_coordinators_health_down, state.consecutiveFailures)
state.consecutiveFailures > 0 -> stringRes(Res.string.cordn_coordinators_health_retrying)
else -> stringRes(Res.string.cordn_coordinators_health_ok)
}
Text(
@@ -447,10 +481,10 @@ private fun DiscoverCoordinators(
var result by remember { mutableStateOf<CordnCoordinatorDiscovery.Result?>(null) }
var busy by remember { mutableStateOf(false) }
var error by remember { mutableStateOf<String?>(null) }
val failed = stringRes(R.string.cordn_coordinators_discover_failed)
val failed = stringRes(Res.string.cordn_coordinators_discover_failed)
Text(
text = stringRes(R.string.cordn_coordinators_discover_explainer),
text = stringRes(Res.string.cordn_coordinators_discover_explainer),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -475,7 +509,7 @@ private fun DiscoverCoordinators(
CircularProgressIndicator(Modifier.size(16.dp), strokeWidth = 2.dp)
Spacer(Modifier.width(8.dp))
}
Text(stringRes(R.string.cordn_coordinators_discover_action))
Text(stringRes(Res.string.cordn_coordinators_discover_action))
}
error?.let { Text(it, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error) }
@@ -497,11 +531,11 @@ private fun DiscoverCoordinators(
Text(
text =
if (found.unreachable.isEmpty()) {
stringRes(R.string.cordn_coordinators_discover_none)
stringRes(Res.string.cordn_coordinators_discover_none)
} else {
// "Nobody is announcing" and "we were not told" are
// different answers and only one of them is final.
stringRes(R.string.cordn_coordinators_discover_unheard, found.unreachable.size)
stringRes(Res.string.cordn_coordinators_discover_unheard, found.unreachable.size)
},
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -529,19 +563,19 @@ private fun RenameCoordinatorDialog(
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringRes(R.string.cordn_coordinators_rename)) },
title = { Text(stringRes(Res.string.cordn_coordinators_rename)) },
text = {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedTextField(
value = input,
onValueChange = { input = it },
label = { Text(stringRes(R.string.cordn_coordinators_label)) },
label = { Text(stringRes(Res.string.cordn_coordinators_label)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
Text(
// The same caution the add form gives, for the same reason.
text = stringRes(R.string.cordn_coordinators_label_note),
text = stringRes(Res.string.cordn_coordinators_label_note),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -549,7 +583,7 @@ private fun RenameCoordinatorDialog(
},
confirmButton = {
TextButton(onClick = { onSave(input.trim().ifEmpty { null }) }) {
Text(stringRes(R.string.cordn_info_save))
Text(stringRes(Res.string.cordn_info_save))
}
},
dismissButton = {
@@ -601,7 +635,7 @@ private fun DiscoveredCard(
Text(
// Staleness matters more here than anywhere: the last live
// survey found most announcements were months-dead demos.
text = stringRes(R.string.cordn_coordinators_discover_seen, timeAgoNoDot(offer.announcedAt).trim()),
text = stringRes(Res.string.cordn_coordinators_discover_seen, timeAgoNoDot(offer.announcedAt).trim()),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -619,7 +653,7 @@ private fun DiscoveredCard(
enabled = !busy,
modifier = Modifier.padding(top = 4.dp),
) {
Text(stringRes(R.string.cordn_coordinators_discover_add))
Text(stringRes(Res.string.cordn_coordinators_discover_add))
}
}
}
@@ -633,7 +667,7 @@ private fun AddCoordinator(runtime: CordnRuntime) {
var labelInput by remember { mutableStateOf("") }
var error by remember { mutableStateOf<String?>(null) }
var busy by remember { mutableStateOf(false) }
val failed = stringRes(R.string.cordn_coordinators_add_failed)
val failed = stringRes(Res.string.cordn_coordinators_add_failed)
val config =
remember(pubKeyInput, relaysInput, labelInput) {
@@ -652,7 +686,7 @@ private fun AddCoordinator(runtime: CordnRuntime) {
pubKeyInput = it
error = null
},
label = { Text(stringRes(R.string.cordn_create_coordinator_pubkey)) },
label = { Text(stringRes(Res.string.cordn_create_coordinator_pubkey)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
@@ -662,14 +696,14 @@ private fun AddCoordinator(runtime: CordnRuntime) {
relaysInput = it
error = null
},
label = { Text(stringRes(R.string.cordn_create_coordinator_relays)) },
label = { Text(stringRes(Res.string.cordn_create_coordinator_relays)) },
singleLine = false,
modifier = Modifier.fillMaxWidth(),
)
OutlinedTextField(
value = labelInput,
onValueChange = { labelInput = it },
label = { Text(stringRes(R.string.cordn_coordinators_label)) },
label = { Text(stringRes(Res.string.cordn_coordinators_label)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
@@ -677,7 +711,7 @@ private fun AddCoordinator(runtime: CordnRuntime) {
Text(
// A label is ours, never theirs. A coordinator cannot prove a name and
// this one is not asked for one.
text = stringRes(R.string.cordn_coordinators_label_note),
text = stringRes(Res.string.cordn_coordinators_label_note),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -705,6 +739,6 @@ private fun AddCoordinator(runtime: CordnRuntime) {
enabled = !busy && config != null,
modifier = Modifier.fillMaxWidth(),
) {
Text(stringRes(R.string.cordn_coordinators_add_action))
Text(stringRes(Res.string.cordn_coordinators_add_action))
}
}
@@ -47,14 +47,28 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.cordn.CoordinatorConfig
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_none
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_disclosure
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_explainer
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_failed
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_last_resort_no
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_last_resort_yes
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_orphans
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_orphans_body
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_publish
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_publish_last_resort
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_section
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_summary
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_title
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_topup_note
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_withdraw_all
import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_withdraw_orphans
import com.vitorpamplona.amethyst.commons.ui.components.EmptyState
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton
@@ -106,8 +120,8 @@ fun CordnKeyPackagesScreen(
) { padding ->
if (runtime == null) {
EmptyState(
title = stringRes(R.string.cordn_group_unavailable),
description = stringRes(R.string.cordn_group_unavailable_detail),
title = stringRes(Res.string.cordn_group_unavailable),
description = stringRes(Res.string.cordn_group_unavailable_detail),
modifier = Modifier.padding(padding),
)
return@Scaffold
@@ -125,7 +139,7 @@ fun CordnKeyPackagesScreen(
verticalArrangement = Arrangement.spacedBy(20.dp),
) {
Text(
text = stringRes(R.string.cordn_keypackages_explainer),
text = stringRes(Res.string.cordn_keypackages_explainer),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -140,7 +154,7 @@ fun CordnKeyPackagesScreen(
SettingsFormBlock {
if (coordinators.isEmpty()) {
Text(
text = stringRes(R.string.cordn_coordinators_none),
text = stringRes(Res.string.cordn_coordinators_none),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -169,7 +183,7 @@ private fun CoordinatorKeyPackages(
var rows by remember(config.pubKey) { mutableStateOf<List<CordnKeyPackageRow>?>(null) }
var busy by remember(config.pubKey) { mutableStateOf(false) }
var error by remember(config.pubKey) { mutableStateOf<String?>(null) }
val failed = stringRes(R.string.cordn_keypackages_failed)
val failed = stringRes(Res.string.cordn_keypackages_failed)
suspend fun reload() {
busy = true
@@ -204,15 +218,15 @@ private fun CoordinatorKeyPackages(
val hasLastResort = loaded.any { it.lastResort }
Text(
text = pluralStringRes(LocalContext.current, R.plurals.cordn_keypackages_summary, single, single),
text = pluralStringRes(Res.plurals.cordn_keypackages_summary, single, single),
style = MaterialTheme.typography.bodyMedium,
)
Text(
text =
if (hasLastResort) {
stringRes(R.string.cordn_keypackages_last_resort_yes)
stringRes(Res.string.cordn_keypackages_last_resort_yes)
} else {
stringRes(R.string.cordn_keypackages_last_resort_no)
stringRes(Res.string.cordn_keypackages_last_resort_no)
},
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -225,11 +239,11 @@ private fun CoordinatorKeyPackages(
) {
Column(Modifier.padding(12.dp)) {
Text(
text = pluralStringRes(LocalContext.current, R.plurals.cordn_keypackages_orphans, orphans.size, orphans.size),
text = pluralStringRes(Res.plurals.cordn_keypackages_orphans, orphans.size, orphans.size),
style = MaterialTheme.typography.titleSmall,
)
Text(
text = stringRes(R.string.cordn_keypackages_orphans_body),
text = stringRes(Res.string.cordn_keypackages_orphans_body),
style = MaterialTheme.typography.bodySmall,
)
TextButton(onClick = {
@@ -242,7 +256,7 @@ private fun CoordinatorKeyPackages(
reload()
}
}) {
Text(stringRes(R.string.cordn_keypackages_withdraw_orphans))
Text(stringRes(Res.string.cordn_keypackages_withdraw_orphans))
}
}
}
@@ -251,7 +265,7 @@ private fun CoordinatorKeyPackages(
Text(
// Above the buttons, because it is the part that cannot be taken
// back: §8.4 makes publishing an attributable, permanent record.
text = stringRes(R.string.cordn_keypackages_disclosure),
text = stringRes(Res.string.cordn_keypackages_disclosure),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -274,7 +288,7 @@ private fun CoordinatorKeyPackages(
},
enabled = !busy,
) {
Text(stringRes(R.string.cordn_keypackages_publish))
Text(stringRes(Res.string.cordn_keypackages_publish))
}
OutlinedButton(
onClick = {
@@ -289,7 +303,7 @@ private fun CoordinatorKeyPackages(
},
enabled = !busy && !hasLastResort,
) {
Text(stringRes(R.string.cordn_keypackages_publish_last_resort))
Text(stringRes(Res.string.cordn_keypackages_publish_last_resort))
}
if (loaded.isNotEmpty()) {
TextButton(
@@ -305,7 +319,7 @@ private fun CoordinatorKeyPackages(
},
enabled = !busy,
) {
Text(stringRes(R.string.cordn_keypackages_withdraw_all), color = MaterialTheme.colorScheme.error)
Text(stringRes(Res.string.cordn_keypackages_withdraw_all), color = MaterialTheme.colorScheme.error)
}
}
}
@@ -313,7 +327,7 @@ private fun CoordinatorKeyPackages(
Text(
// The rule CordnRuntime.maintainKeyPackages implements, said where
// someone can see that it applies to them.
text = stringRes(R.string.cordn_keypackages_topup_note),
text = stringRes(Res.string.cordn_keypackages_topup_note),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -45,7 +45,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.cordn.CordnLinkInspection
import com.vitorpamplona.amethyst.commons.cordn.ui.CordnExposureCard
import com.vitorpamplona.amethyst.commons.resources.Res
@@ -60,6 +59,11 @@ import com.vitorpamplona.amethyst.commons.resources.cordn_link_no_coordinator
import com.vitorpamplona.amethyst.commons.resources.cordn_link_not_joinable
import com.vitorpamplona.amethyst.commons.resources.cordn_link_paste
import com.vitorpamplona.amethyst.commons.resources.cordn_link_relays
import com.vitorpamplona.amethyst.commons.resources.cordn_link_request
import com.vitorpamplona.amethyst.commons.resources.cordn_link_request_disclosure
import com.vitorpamplona.amethyst.commons.resources.cordn_link_request_failed
import com.vitorpamplona.amethyst.commons.resources.cordn_link_request_sent
import com.vitorpamplona.amethyst.commons.resources.cordn_link_scan
import com.vitorpamplona.amethyst.commons.resources.cordn_link_section_input
import com.vitorpamplona.amethyst.commons.resources.cordn_link_title
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
@@ -102,8 +106,8 @@ fun CordnLinkScreen(
var requesting by remember { mutableStateOf(false) }
val runtime = accountViewModel.account.cordnRuntime
val scope = rememberCoroutineScope()
val requestFailed = stringRes(R.string.cordn_link_request_failed)
val asked = stringRes(R.string.cordn_link_request_sent)
val requestFailed = stringRes(Res.string.cordn_link_request_failed)
val asked = stringRes(Res.string.cordn_link_request_sent)
var scanning by remember { mutableStateOf(false) }
val clipboard = LocalClipboardManager.current
@@ -163,7 +167,7 @@ fun CordnLinkScreen(
Text(stringResource(Res.string.cordn_link_paste))
}
OutlinedButton(onClick = { scanning = true }) {
Text(stringRes(R.string.cordn_link_scan))
Text(stringRes(Res.string.cordn_link_scan))
}
if (input.isNotEmpty()) {
OutlinedButton(
@@ -265,7 +269,7 @@ fun CordnLinkScreen(
enabled = !requesting,
modifier = Modifier.fillMaxWidth(),
) {
Text(stringRes(R.string.cordn_link_request))
Text(stringRes(Res.string.cordn_link_request))
}
Text(
@@ -274,7 +278,7 @@ fun CordnLinkScreen(
// KeyPackage under this account's own key and tells
// the coordinator this account wants into this
// group, whether or not anyone ever answers (§8.4).
text = stringRes(R.string.cordn_link_request_disclosure),
text = stringRes(Res.string.cordn_link_request_disclosure),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -47,9 +47,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.cordn.CordnMigration
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable
import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_detail
import com.vitorpamplona.amethyst.commons.resources.cordn_migrate_cancel
import com.vitorpamplona.amethyst.commons.resources.cordn_migrate_cancel_desc
import com.vitorpamplona.amethyst.commons.resources.cordn_migrate_code_note
@@ -119,8 +120,8 @@ fun CordnMigrateScreen(
) { padding ->
if (runtime == null) {
EmptyState(
title = stringRes(R.string.cordn_group_unavailable),
description = stringRes(R.string.cordn_group_unavailable_detail),
title = stringRes(Res.string.cordn_group_unavailable),
description = stringRes(Res.string.cordn_group_unavailable_detail),
modifier = Modifier.padding(padding),
)
return@Scaffold
@@ -353,322 +353,4 @@
<string name="mute_button">ध्वनि सक्रिय। मौन करने के लिए टाँकें</string>
<string name="play">चलाएँ</string>
<string name="pause">विराम</string>
<string name="backup_conflict_title">आपने अपना %1$s अन्य क्रमक में परिवर्तित किया</string>
<string name="backup_conflict_removed">हटाए गए (%1$s)</string>
<string name="backup_conflict_added">जोडे गए (%1$s)</string>
<string name="backup_conflict_changed">परिवर्तित (%1$s)</string>
<string name="backup_conflict_private_cleared">आपने सभी निजी (रहस्यीकृत) वस्तुओं को हटा दिया।</string>
<string name="backup_conflict_private_changed">आपने निजी (रहस्यीकृत) वस्तुओं का परिवर्तन भी किया। उनकी तुलना यहाँ नहीं की जा सकती।</string>
<string name="backup_conflict_private_added">आपने निजी (रहस्यीकृत) वस्तुओं को जोडा।</string>
<string name="backup_conflict_restore_mine">सुरक्षित संस्करण पुनःस्थापन</string>
<string name="backup_review_saved">इस यन्त्र पर</string>
<string name="backup_review_new">नया संस्करण</string>
<string name="backup_review_not_set">स्थापित नहीं</string>
<string name="backup_conflict_count_removed">%1$s हटाए गए</string>
<string name="backup_conflict_count_added">%1$s जोडे गए</string>
<string name="backup_conflict_count_changed">%1$s परिवर्तित</string>
<plurals name="backup_card_follows_dropped">
<item quantity="one">आपने %1$d व्यक्ति का अनुगमन हटाया</item>
<item quantity="other">आपने %1$d व्यक्तियों का अनुगमन हटाए</item>
</plurals>
<string name="backup_card_accept">स्वीकार</string>
<string name="backup_review_title_updated">%1$s अद्यतनकृत</string>
<string name="backup_review_title_cleared">%1$s पारित</string>
<string name="backup_review_changed_by_other_app">अन्य क्रमक में परिवर्तित। %1$s</string>
<string name="backup_action_keep_count">रखें %1$s</string>
<string name="backup_action_restore_count">पुनःस्थापन %1$s</string>
<string name="backup_action_remute">पुनःमौन %1$s</string>
<string name="backup_action_rejoin">पुनःजुडें %1$s</string>
<string name="backup_action_restore_profile">परिचय पुनःस्थापध</string>
<string name="backup_action_restore_relays">पुनःप्रसारक पुनःस्थापन</string>
<string name="backup_review_kept_count">%1$s रखे गए</string>
<string name="backup_review_dropped_count">%1$s परित्यक्त</string>
<string name="backup_review_new_count">%1$s नए</string>
<string name="backup_review_tab_dropped">परित्यक्त। %1$s</string>
<string name="backup_review_tab_new">नए। %1$s</string>
<string name="backup_review_tab_edited">सम्पादित। %1$s</string>
<string name="backup_review_search_people">%1$s लोग खोजें</string>
<string name="backup_review_nothing_here">यहाँ कुछ नहीं</string>
<string name="backup_review_unmuted">%1$s मौनावस्था हटाए गए</string>
<string name="backup_review_unmuted_explainer">ये पुनः आपके सूचनावलियों तक अभिगम्य</string>
<string name="backup_review_newly_muted">%1$s नए मौनकृत</string>
<string name="backup_review_words_and_hashtags">शब्द तथा विषयसूचक</string>
<string name="backup_review_names_and_more">%1$s तथा %2$s अधिक</string>
<string name="backup_review_private_wiped_title">आपने सभी निजी वस्तुओं को हटा दिया</string>
<string name="backup_review_private_wiped_body">वे रहस्यीकृत हैं। इसलिए केवल आप देख सकते हैं कि वे क्या थे। पुनःस्थापन से सब लौटके आ जाएँगे।</string>
<string name="backup_review_private_added_title">आपने वस्तुओं को निजी किया</string>
<string name="backup_review_private_added_body">वे अब रहस्यीकृत हैं। जो कि सम्भाव्यतः ऊपर के सार्वजनिक वस्तुओं का गन्तव्य था।</string>
<string name="backup_review_private_changed_title">आपने निजी वस्तुओं का परिवर्तन किया</string>
<string name="backup_review_private_changed_body">वे रहस्यीकृत हैं। इसलिए उनकी तुलना यहाँ नहीं की जा सकती।</string>
<string name="backup_review_gone">चला गया</string>
<string name="backup_review_new_tag">नव्य</string>
<string name="backup_review_dropped_tag">परित्यक्त</string>
<string name="backup_review_demoted_tag">स्थानान्तरित</string>
<string name="backup_review_units_tag">इकाई</string>
<string name="backup_review_left_tag">शेष</string>
<string name="backup_review_renamed_tag">पुनःनामकृत</string>
<string name="backup_review_outbox">निर्गतपेटिका</string>
<string name="backup_review_outbox_explainer">आप जहाँ प्रकाशित करते हैं</string>
<string name="backup_review_inbox">आगतपेटिका</string>
<string name="backup_review_inbox_explainer">जहाँ अन्य आप तक अभिगमन करते हैं</string>
<string name="backup_review_key_replaced">ग्राहक कुंचिका प्रतिस्थापित</string>
<string name="backup_review_key_removed">ग्राहक कुंचिका हटाया गया</string>
<string name="backup_review_key_warning">नटज्साप आगे से इस कुंचिका से बद्ध होंगे। यदि इस यन्त्र में वह नहीं है तो आप सम्भाव्यतः उनको यहाँ नहीं चुका सकते।</string>
<string name="backup_review_left">शेष</string>
<string name="backup_review_joined">जुड गए</string>
<string name="backup_review_renamed">पुनःनामकृत</string>
<string name="backup_review_was_named">पूर्वतः %1$s था</string>
<string name="backup_action_block_again">पुनःबाधित करें %1$s को</string>
<string name="backup_action_restore_feeds">सूचनावलियाँ पुनःस्थापन</string>
<string name="backup_action_restore_topics">विषय पुनःस्थापन</string>
<string name="backup_action_restore_places">स्थल पुनःस्थापन</string>
<string name="backup_action_restore_providers">प्रदाता पुनःस्थापन</string>
<string name="backup_action_restore_wallet">धनकोष पुनःस्थापन</string>
<string name="backup_review_kept">रखे गए</string>
<string name="backup_review_receiving_tag">ग्रहण मध्य</string>
<string name="backup_review_listed_tag">सूचीबद्ध</string>
<string name="backup_review_searching_tag">खोज मध्य</string>
<string name="backup_review_storing_tag">अभिलेखन मध्य</string>
<string name="backup_review_trusted_tag">विश्वसनीय</string>
<string name="backup_review_untrusted_tag">अविश्वसनीय</string>
<string name="backup_review_unblocked">%1$s अबाधित</string>
<string name="backup_review_unblocked_explainer">अमेथिस्ट पुनः उनसे जुडेगा</string>
<string name="backup_review_no_longer_blocked">अब से बाधित नहीं</string>
<string name="backup_review_newly_blocked">नव्यतः बाधित</string>
<string name="backup_review_still_blocked">अभी भी बाधित</string>
<string name="backup_review_allowed_tag">अनुमत</string>
<string name="backup_review_moved_tag">स्थानान्तरित</string>
<string name="backup_review_feed">सूचनावली</string>
<string name="backup_review_topics_gone">विषय हटाए गए</string>
<string name="backup_review_places_gone">स्थल हटाए गए</string>
<string name="backup_review_new_count_caption">नव्य</string>
<string name="backup_review_hashtags_you_follow">विषयसूचक जिनका आप अनुगमन करते हैं</string>
<string name="backup_review_places_you_follow">स्थल जिनका आप अनुगमन करते हैं</string>
<string name="backup_review_services_changed">%2$s में से %1$s सेवाएँ परिवर्तित</string>
<string name="backup_review_services_changed_explainer">वे अब अन्य व्यक्ति से आते है। अथवा किसी से नहीं</string>
<string name="backup_review_service">सेवा</string>
<string name="backup_review_service_rank">श्रेणी</string>
<string name="backup_review_service_followers">अनुचर</string>
<string name="backup_review_service_topics">विषयसूची</string>
<string name="backup_review_trust_warning">श्रेणीयाँ तथा अनुचर गिनती तथा कचरालेख छालन जो आप देखते हैं इन प्रदाताओं से आते हैं।</string>
<plurals name="backup_review_payment_targets_gone">
<item quantity="one">आपको भुगतान करने की %1$d विधि हटाया गया</item>
<item quantity="other">आपको भुगतान करने की %1$d विधियाँ हटाए गए</item>
</plurals>
<plurals name="backup_review_offers_gone">
<item quantity="one">%1$d प्रस्ताव हटाया गया</item>
<item quantity="other">%1$d प्रस्ताव हटाए गए</item>
</plurals>
<string name="backup_review_payments_explainer">लोग जो आपको भुगतान करते हैं उसी को देखते हैं जो इस सूची में है।</string>
<string name="backup_review_offer">बोल्ट॰बारह प्रस्ताव</string>
<string name="backup_review_encrypted_cleared">आपने उसे रिक्त किया</string>
<string name="backup_review_encrypted_added">आपने उसे बनाया</string>
<string name="backup_review_encrypted_changed">आपने उसे परिवर्तित किया</string>
<string name="backup_review_wallet_body">आपके धनकोष के टकसाल तथा कुंचिकाएँ आपके लिए रहस्यीकृत हैं। इसलिए उनकी तुलना वस्तु प्रति वस्तु यहाँ नहीं की जा सकती। पुनःस्थापन उन्हीं को लौट डालेगा जिनका अभिलेखन इस यन्त्र ने किया।</string>
<string name="backup_review_concord_body">आपके कोंकोर्ड समुदाय आपके लिए रहस्यीकृत हैं। इसलिए उनकी तुलना यहाँ नहीं की जा सकती। पुनःस्थापन उन्हीं को लौट डालेगा जिनका अभिलेखन इस यन्त्र ने किया।</string>
<plurals name="backup_card_mutes_visible">
<item quantity="one">आपने %1$d की मौनावस्था हटाया</item>
<item quantity="other">आपने %1$d की मौनावस्था हटाए</item>
</plurals>
<plurals name="backup_card_profile_lost_fields">
<item quantity="one">आपने %1$d परिचय प्रपत्रस्थान हटाया</item>
<item quantity="other">आपने %1$d परिचय प्रपत्रस्थान हटाए</item>
</plurals>
<plurals name="backup_card_nip65_lost">
<item quantity="one">आपने %1$d सार्वजनिक पुनःप्रसारक हटाया</item>
<item quantity="other">आपने %1$d सार्वजनिक पुनःप्रसारकों को हटाए</item>
</plurals>
<plurals name="backup_card_dm_relays_lost">
<item quantity="one">आपने %1$d सीधासन्देश पुनःप्रसारक हटाया</item>
<item quantity="other">आपने %1$d सीधासन्देश पुनःप्रसारकों को हटाए</item>
</plurals>
<plurals name="backup_card_key_package_relays_lost">
<item quantity="one">आपने %1$d समूहचर्चा कुंचिका पुनःप्रसारक हटाया</item>
<item quantity="other">आपने %1$d समूहचर्चा कुंचिका पुनःप्रसारकों को हटाए</item>
</plurals>
<plurals name="backup_card_search_relays_lost">
<item quantity="one">आपने %1$d खोज पुनःप्रसारक हटाया</item>
<item quantity="other">आपने %1$d खोज पुनःप्रसारकों को हटाए</item>
</plurals>
<plurals name="backup_card_indexer_relays_lost">
<item quantity="one">आपने %1$d अनुक्रमणीकार पुनःप्रसारक हटाया</item>
<item quantity="other">आपने %1$d अनुक्रमणीकार पुनःप्रसारकों को हटाए</item>
</plurals>
<plurals name="backup_card_relay_feeds_lost">
<item quantity="one">आपने %1$d पुनःप्रसारक सूचनावली हटाया</item>
<item quantity="other">आपने %1$d पुनःप्रसारक सूचनावलियाँ हटाए</item>
</plurals>
<plurals name="backup_card_private_outbox_lost">
<item quantity="one">आपने %1$d निजी पुनःप्रसारक हटाया</item>
<item quantity="other">आपने %1$d निजी पुनःप्रसारकों को हटाए</item>
</plurals>
<plurals name="backup_card_trusted_lost">
<item quantity="one">आपने %1$d पुनःप्रसारक पर विश्वास करना रोक दिया</item>
<item quantity="other">आपने %1$d पुनःप्रसारकों पर विश्वास करना रोक दिए</item>
</plurals>
<plurals name="backup_card_unblocked">
<item quantity="one">आपने %1$d पुनःप्रसारक अबाधित किया</item>
<item quantity="other">आपने %1$d पुनःप्रसारकों को अबाधित किए</item>
</plurals>
<plurals name="backup_card_leave_chats">
<item quantity="one">आपने %1$d सार्वजनिक चर्चा छोड दिया</item>
<item quantity="other">आपने %1$d सार्वजनिक चर्चाओं को छोड दिए</item>
</plurals>
<plurals name="backup_card_leave_communities">
<item quantity="one">आपने %1$d समुदाय छोड दिया</item>
<item quantity="other">आपने %1$d समुदायों को छोड दिए</item>
</plurals>
<plurals name="backup_card_leave_rooms">
<item quantity="one">आपने %1$d चर्चाशाला छोड दिया</item>
<item quantity="other">आपने %1$d चर्चाशालाओं को छोड दिए</item>
</plurals>
<plurals name="backup_card_leave_groups">
<item quantity="one">आपने %1$d समूह छोड दिया</item>
<item quantity="other">आपने %1$d समूहों को छोड दिए</item>
</plurals>
<plurals name="backup_card_feeds_removed">
<item quantity="one">आपने %1$d इष्ट सूचनावली हटाया</item>
<item quantity="other">आपने %1$d इष्ट सूचनावलियों को हटाए</item>
</plurals>
<plurals name="backup_card_hashtags_unfollowed">
<item quantity="one">आपने %1$d विषयसूचक का अनुगमन हटाया</item>
<item quantity="other">आपने %1$d विषयसूचकों का अनुगमन हटाए</item>
</plurals>
<plurals name="backup_card_places_unfollowed">
<item quantity="one">आपने %1$d स्थल का अनुगमन हटाया</item>
<item quantity="other">आपने %1$d स्थलों का अनुगमन हटाए</item>
</plurals>
<plurals name="backup_card_trust_services_changed">
<item quantity="one">आपने %1$d विश्वाससेवा प्रदाता परिवर्तित किया</item>
<item quantity="other">आपने %1$d विश्वाससेवाओं के प्रदाता को परिवर्तित किया</item>
</plurals>
<plurals name="backup_card_nutzap_mints_removed">
<item quantity="one">आपने %1$d नटज्साप टकसाल हटाया</item>
<item quantity="other">आपने %1$d नटज्साप टकसालों को हटाए</item>
</plurals>
<plurals name="backup_card_payment_targets_removed">
<item quantity="one">आपने %1$d भुगतान प्राप्त करने की विधि हटाया</item>
<item quantity="other">आपने %1$d भुगतान प्राप्त करने के विधियों को हटाए</item>
</plurals>
<plurals name="backup_card_offers_removed">
<item quantity="one">आपने %1$d बोल्ट॰बारह प्रस्ताव हटाया</item>
<item quantity="other">आपने %1$d बोल्ट॰बारह प्रस्तावों को हटाए</item>
</plurals>
<plurals name="backup_card_and_added">
<item quantity="one">%1$s तथा %2$d जोडा</item>
<item quantity="other">%1$s तथा %2$d जोडे</item>
</plurals>
<plurals name="backup_card_and_followed">
<item quantity="one">%1$s तथा %2$d का अनुगमन किया</item>
<item quantity="other">%1$s तथा %2$d का अनुगमन किए</item>
</plurals>
<plurals name="backup_card_and_muted">
<item quantity="one">%1$s तथा %2$d को मौनकृत किया</item>
<item quantity="other">%1$s तथा %2$d को मौनकृत किए</item>
</plurals>
<plurals name="backup_card_and_joined">
<item quantity="one">%1$s तथा %2$d से जुडे</item>
<item quantity="other">%1$s तथा %2$d से जुडे</item>
</plurals>
<plurals name="backup_card_and_blocked">
<item quantity="one">%1$s तथा %2$d बाधित किया</item>
<item quantity="other">%1$s तथा %2$d को बाधित किए</item>
</plurals>
<string name="backup_card_app_settings">आपके अमेथिस्ट समचरणीकृत क्रमक स्थापना विकल्पों को परिवर्तित किया गया एक अन्य क्रमक में और वह अमेथिस्ट नहीं था। इससे समस्याएँ उत्पन्न हो सकते हैं।</string>
<string name="backup_card_nutzap_key_replaced">आपने अपनी नटज्साप कुंचिका का प्रतिस्थापन किया</string>
<string name="backup_card_nutzap_key_removed">आपने अपनी नटज्साप कुंचिका को हटाया</string>
<string name="backup_card_wallet_emptied">आपने अपना काशयू धनकोष रिक्त किया</string>
<string name="backup_card_wallet_rewritten">आपने अपने काशयू धनकोष को परिवर्तित किया</string>
<string name="backup_card_concord_emptied">आपने अपनी कोंकोर्ड सूची रिक्त की</string>
<string name="backup_card_concord_rewritten">आपने अपनी कोंकोर्ड सूची का परिवर्तन किया</string>
<string name="backup_conflict_keep_new">नया संस्करण का उपयोग करें</string>
<string name="backup_conflict_relay_read_only">पठनेवशक्य</string>
<string name="backup_conflict_relay_write_only">लेखनेवशक्य</string>
<string name="backup_conflict_relay_read_write">पठन तथा लेखन</string>
<string name="backup_type_profile">परिचय</string>
<string name="backup_type_profile_explainer">आपका नाम चित्र चरित तथा भुगतान पते। जैसे सब को दिखते हैं वैसे।</string>
<string name="backup_type_follow_list">अनुचरण सूची</string>
<string name="backup_type_follow_list_explainer">निर्णय करता है किसके पत्र आपकी मुख्य सूचनावली में प्रकट होंगे।</string>
<string name="backup_type_mute_list">मौन सूची</string>
<string name="backup_type_mute_list_explainer">छिपाता है लोग शब्द विषयसूचक तथा सूत्र उन सब में से जो आप देखते हैं।</string>
<string name="backup_type_outbox_inbox_relays">सार्वजनिक पुनःप्रसारक सूची</string>
<string name="backup_type_outbox_inbox_relays_explainer">सबको बताता है आपके पत्र कहाँ प्राप्त करें तथा आपको उत्तर तथा उल्लेखों को कहाँ भेजें।</string>
<string name="backup_type_dm_relays">सीधासन्देश पुनःप्रसारक सूची</string>
<string name="backup_type_dm_relays_explainer">जहाँ अन्य आपके निजी सन्देश भेजते हैं।</string>
<string name="backup_type_key_package_relays">समूहचर्चा कुंचिका पुनःप्रसारक</string>
<string name="backup_type_key_package_relays_explainer">जहाँ अन्य उन कुंचिकाओं को प्राप्त करते हैं आपको रहस्यीकृत समूहचर्चाओं में जोडने के लिए।</string>
<string name="backup_type_search_relays">खोज पुनःप्रसारक सूची</string>
<string name="backup_type_search_relays_explainer">उपयुक्त पुनःप्रसारक जब आप खोजते हैं।</string>
<string name="backup_type_indexer_relays">अनुक्रमणीकार पुनःप्रसारक सूची</string>
<string name="backup_type_indexer_relays_explainer">पुनःप्रसारक उपयुक्त अन्य लोगों के परिचय तथा पुनःप्रसारक सूचियों को प्राप्त करने के लिए।</string>
<string name="backup_type_relay_feeds">इष्ट पुनःप्रसारक सूचनावलियाँ</string>
<string name="backup_type_relay_feeds_explainer">पुनःप्रसारक जिनका आप वीक्षण करते हैं सूचनावलियों के रूप में।</string>
<string name="backup_type_blocked_relays">बाधित पुनःप्रसारक सूची</string>
<string name="backup_type_blocked_relays_explainer">पुनःप्रसारक जिनसे यह क्रमक कभी भी संयोजन नहीं करता।</string>
<string name="backup_type_trusted_relays">विश्वसनीय पुनःप्रसारक सूची</string>
<string name="backup_type_trusted_relays_explainer">पुनःप्रसारक जिनपर आप पर्याप्त विश्वास करते हैं बिना पूछे उनसे संयोजन करने के लिए।</string>
<string name="backup_type_private_outbox_relays">निजी पुनःप्रसारक सूची</string>
<string name="backup_type_private_outbox_relays_explainer">जहाँ आपके पाण्डुलिपियाँ तथा निजी जानकारी रखे जाते हैं।</string>
<string name="backup_type_app_settings">समचरणीकृत क्रमक स्थापना विकल्प</string>
<string name="backup_type_app_settings_explainer">अमेथिस्ट स्थापना विकल्प समचरणीकृत आपके यन्त्रों में। वे रहस्यीकृत हैं। इसलिए केवल उनकी उपस्थिति की तुलना की जा सकती है।</string>
<string name="backup_type_public_chats">सार्वजनिक चर्चा सूची</string>
<string name="backup_type_public_chats_explainer">सार्वजनिक चर्चाएँ जिनसे आप जुडे।</string>
<string name="backup_type_communities">समुदाय सूची</string>
<string name="backup_type_communities_explainer">समुदाय जिनसे आप जुडे।</string>
<string name="backup_type_hashtags">अनुचरित विषयसूचक</string>
<string name="backup_type_hashtags_explainer">विषयसूचक जिससे युक्त पत्र आपकी मुख्य सूचनावली में प्रकट होंगे।</string>
<string name="backup_type_geohashes">अनुचरित स्थान</string>
<string name="backup_type_geohashes_explainer">स्थान जहाँ से पत्र आपकी मुख्य सूचनावली में प्रकट होंगे।</string>
<string name="backup_type_favorite_algo_feeds">इष्ट सूचनावली सूची</string>
<string name="backup_type_favorite_algo_feeds_explainer">कलनविध्यात्मक सूचनावलियाँ जिनको आपने अभिलेखित किया।</string>
<string name="backup_type_ephemeral_chats">अस्थायी चर्चा सूची</string>
<string name="backup_type_ephemeral_chats_explainer">अस्थायी चर्चा शालाएँ जिनसे आप जुडे।</string>
<string name="backup_type_relay_groups">समूह सूची</string>
<string name="backup_type_relay_groups_explainer">पुनःप्रसारक आधारित समूह जिनसे आप जुडे।</string>
<string name="backup_type_concord_communities">कोंकोर्ड समुदाय सूची</string>
<string name="backup_type_concord_communities_explainer">कोंकोर्ड समुदाय जिनसे आप जुडे। वे रहस्यीकृत हैं। इसलिए केवल उनकी उपस्थिति की तुलना की जा सकती है।</string>
<string name="backup_type_trust_providers">विश्वाससेवा प्रदाता सूची</string>
<string name="backup_type_trust_providers_explainer">सेवाएँ जिनपर आप विश्वास करते है आपके लिए लोगों का सत्यापन तथा श्रेणीकरण करने के लिए।</string>
<string name="backup_type_cashu_wallet">काशयू धनकोष</string>
<string name="backup_type_cashu_wallet_explainer">आपके ईकाश धनकोष स्थापना विकल्प तथा टकसाल। वे रहस्यीकृत हैं। इसलिए केवल उनकी उपस्थिति की तुलना की जा सकती है।</string>
<string name="backup_type_nutzap_info">नटज्साप प्राप्ती स्थापना विकल्प</string>
<string name="backup_type_nutzap_info_explainer">टकसाल तथा कुंचिका जिनका उपयोग अन्य करते हैं आपको नटज्साप भेजने के लिए।</string>
<string name="backup_type_payment_targets">भुगतान पते</string>
<string name="backup_type_payment_targets_explainer">विधियाँ जिनसे अन्य लोग आपको भुगतान कर सके।</string>
<string name="backup_type_bolt12_offers">बोल्ट॰बारह प्रस्ताव</string>
<string name="backup_type_bolt12_offers_explainer">लैटनिंग प्रस्ताव जिनका उपयोग करके अन्य लोग आपको भुगतान करते हैं।</string>
<string name="backup_type_other">सूची</string>
<string name="backup_type_other_explainer">इस यन्त्र पर अभिलेखित सूची।</string>
<string name="backup_entry_profile_field">परिचय प्रपत्रस्थान</string>
<string name="backup_entry_person">लोग</string>
<string name="backup_entry_hashtag">विषयसूचक</string>
<string name="backup_entry_word">शब्द</string>
<string name="backup_entry_thread">सूत्र</string>
<string name="backup_entry_public_chat">सार्वजनिक चर्चाएँ</string>
<string name="backup_entry_community">समुदाय</string>
<string name="backup_entry_algo_feed">सूचनावलियाँ</string>
<string name="backup_entry_location">स्थान</string>
<string name="backup_entry_relay">पुनःप्रसारक</string>
<string name="backup_entry_relay_group">समूह</string>
<string name="backup_entry_chat_room">चर्चा शालाएँ</string>
<string name="backup_entry_trust_provider">विश्वाससेवा प्रदाता</string>
<string name="backup_entry_mint">टकसाल</string>
<string name="backup_entry_nutzap_key">नटज्साप कुंचिकाएँ</string>
<string name="backup_entry_payment_target">भुगतान पते</string>
<string name="backup_entry_offer">प्रस्ताव</string>
<string name="backup_entry_identity_claim">सम्बन्धित व्यक्तिगण</string>
<string name="backup_entry_other">अन्य प्रविष्टियाँ</string>
<string name="backup_profile_field_name">प्रयोक्तानाम</string>
<string name="backup_profile_field_display_name">प्रदर्शन नाम</string>
<string name="backup_profile_field_about">जानकारी</string>
<string name="backup_profile_field_picture">चित्र</string>
<string name="backup_profile_field_banner">ध्वज</string>
<string name="backup_profile_field_website">जालस्थान</string>
<string name="backup_profile_field_nip05">नोस्टर पता</string>
<string name="backup_profile_field_lud16">लैटनिंग पता</string>
<string name="backup_profile_field_lud06">लैटनिंग जालपता</string>
<string name="backup_profile_field_pronouns">सर्वनाम</string>
<string name="backup_profile_field_birthday">जन्मदिन</string>
<string name="backup_profile_field_clink_offer">क्लिंक॰ प्रस्ताव</string>
<string name="backup_profile_field_bot">क्रमचर चिह्न</string>
</resources>
@@ -353,322 +353,4 @@
<string name="mute_button">Hang bekapcsolva. Koppintson a némításhoz</string>
<string name="play">Lejátszás</string>
<string name="pause">Szüneteltetés</string>
<string name="backup_conflict_title">Ön módosította a következőt: %1$s, egy másik alkalmazásban</string>
<string name="backup_conflict_removed">Eltávolítva (%1$s)</string>
<string name="backup_conflict_added">Hozzáadva (%1$s)</string>
<string name="backup_conflict_changed">Módosítva (%1$s)</string>
<string name="backup_conflict_private_cleared">Ön eltávolította az összes privát (titkosított) elemet.</string>
<string name="backup_conflict_private_changed">Ön privát (titkosított) elemeket is módosított. Ezek itt nem hasonlíthatók össze.</string>
<string name="backup_conflict_private_added">Ön privát (titkosított) elemeket adott hozzá.</string>
<string name="backup_conflict_restore_mine">Mentett verzió visszaállítása</string>
<string name="backup_review_saved">Ezen az eszközön</string>
<string name="backup_review_new">Új verzió</string>
<string name="backup_review_not_set">Nincs beállítva</string>
<string name="backup_conflict_count_removed">%1$s eltávolítva</string>
<string name="backup_conflict_count_added">%1$s hozzáadva</string>
<string name="backup_conflict_count_changed">%1$s módosítva</string>
<plurals name="backup_card_follows_dropped">
<item quantity="one">%1$d személyt nem követ többé</item>
<item quantity="other">%1$d személyt nem követ többé</item>
</plurals>
<string name="backup_card_accept">Elfogadás</string>
<string name="backup_review_title_updated">%1$s frissítve</string>
<string name="backup_review_title_cleared">%1$s törölve</string>
<string name="backup_review_changed_by_other_app">Módosítva egy másik alkalmazásban · %1$s</string>
<string name="backup_action_keep_count">%1$s megtartása</string>
<string name="backup_action_restore_count">%1$s visszaállítása</string>
<string name="backup_action_remute">%1$s újbóli némítása</string>
<string name="backup_action_rejoin">Újbóli csatlakozás ehhez: %1$s</string>
<string name="backup_action_restore_profile">Profil visszaállítása</string>
<string name="backup_action_restore_relays">Átjátszók visszaállítása</string>
<string name="backup_review_kept_count">%1$s megtartva</string>
<string name="backup_review_dropped_count">%1$s elvetve</string>
<string name="backup_review_new_count">%1$s új</string>
<string name="backup_review_tab_dropped">Elvetve · %1$s</string>
<string name="backup_review_tab_new">Új · %1$s</string>
<string name="backup_review_tab_edited">Szerkesztve · %1$s</string>
<string name="backup_review_search_people">%1$s személy keresése</string>
<string name="backup_review_nothing_here">Itt nincs semmi</string>
<string name="backup_review_unmuted">%1$s némítása feloldva</string>
<string name="backup_review_unmuted_explainer">Ezek újra megjelenhetnek a hírfolyamaiban</string>
<string name="backup_review_newly_muted">%1$s újonnan némítva</string>
<string name="backup_review_words_and_hashtags">Szavak és kulcsszavak</string>
<string name="backup_review_names_and_more">%1$s és %2$s további</string>
<string name="backup_review_private_wiped_title">Ön eltávolította az összes privát elemet</string>
<string name="backup_review_private_wiped_body">Titkosítva vannak, így csak Ön láthatja, mik voltak. A visszaállítás mindet visszahozza.</string>
<string name="backup_review_private_added_title">Ön priváttá tette az elemeket</string>
<string name="backup_review_private_added_body">Most már titkosítva vannak, valószínűleg ide kerültek a fenti nyilvános elemek.</string>
<string name="backup_review_private_changed_title">Ön módosította a privát elemeket</string>
<string name="backup_review_private_changed_body">Titkosítva vannak, így itt nem hasonlíthatók össze.</string>
<string name="backup_review_gone">Eltűnt</string>
<string name="backup_review_new_tag">Új</string>
<string name="backup_review_dropped_tag">Elvetve</string>
<string name="backup_review_demoted_tag">Áthelyezve</string>
<string name="backup_review_units_tag">Egységek</string>
<string name="backup_review_left_tag">Elhagyva</string>
<string name="backup_review_renamed_tag">Átnevezve</string>
<string name="backup_review_outbox">Kimenő</string>
<string name="backup_review_outbox_explainer">ahol közzétesz</string>
<string name="backup_review_inbox">Bejövő</string>
<string name="backup_review_inbox_explainer">ahol mások elérik Önt</string>
<string name="backup_review_key_replaced">Fogadási kulcs lecserélve</string>
<string name="backup_review_key_removed">Fogadási kulcs eltávolítva</string>
<string name="backup_review_key_warning">A nutzapok ehhez az új kulcshoz lesznek kötve. Ha ez az eszköz nem rendelkezik vele, lehet, hogy itt nem tudja beváltani őket.</string>
<string name="backup_review_left">elhagyta a csoportot</string>
<string name="backup_review_joined">csatlakozott</string>
<string name="backup_review_renamed">átnevezte</string>
<string name="backup_review_was_named">„%1$s” volt</string>
<string name="backup_action_block_again">%1$s újbóli letiltása</string>
<string name="backup_action_restore_feeds">Hírfolyamok visszaállítása</string>
<string name="backup_action_restore_topics">Témák visszaállítása</string>
<string name="backup_action_restore_places">Helyek visszaállítása</string>
<string name="backup_action_restore_providers">Szolgáltatók visszaállítása</string>
<string name="backup_action_restore_wallet">Pénztárca visszaállítása</string>
<string name="backup_review_kept">megtartva</string>
<string name="backup_review_receiving_tag">Fogadás</string>
<string name="backup_review_listed_tag">Listázva</string>
<string name="backup_review_searching_tag">Keresés</string>
<string name="backup_review_storing_tag">Tárolás</string>
<string name="backup_review_trusted_tag">Megbízható</string>
<string name="backup_review_untrusted_tag">Nem megbízható</string>
<string name="backup_review_unblocked">%1$s letiltása feloldva</string>
<string name="backup_review_unblocked_explainer">Az Amethyst újra kapcsolódni fog hozzájuk</string>
<string name="backup_review_no_longer_blocked">Már nincs letiltva</string>
<string name="backup_review_newly_blocked">Újonnan letiltva</string>
<string name="backup_review_still_blocked">Továbbra is le van tiltva</string>
<string name="backup_review_allowed_tag">Engedélyezve</string>
<string name="backup_review_moved_tag">Áthelyezve</string>
<string name="backup_review_feed">Hírfolyam</string>
<string name="backup_review_topics_gone">eltűnt témák</string>
<string name="backup_review_places_gone">eltűnt helyek</string>
<string name="backup_review_new_count_caption">új</string>
<string name="backup_review_hashtags_you_follow">Ön által követett kulcsszavak</string>
<string name="backup_review_places_you_follow">Ön által követett helyek</string>
<string name="backup_review_services_changed">%2$s szolgáltatásból %1$s módosult</string>
<string name="backup_review_services_changed_explainer">Most már valaki mástól vagy senkitől sem származnak</string>
<string name="backup_review_service">Szolgáltatás</string>
<string name="backup_review_service_rank">Rang</string>
<string name="backup_review_service_followers">Követők</string>
<string name="backup_review_service_topics">Témák</string>
<string name="backup_review_trust_warning">A látott rangok, követőszámok és a kéretlen tartalomszűrés ezektől a szolgáltatóktól származnak.</string>
<plurals name="backup_review_payment_targets_gone">
<item quantity="one">%1$d fizetési mód eltávolítva</item>
<item quantity="other">%1$d fizetési mód eltávolítva</item>
</plurals>
<plurals name="backup_review_offers_gone">
<item quantity="one">%1$d ajánlat eltávolítva</item>
<item quantity="other">%1$d ajánlat eltávolítva</item>
</plurals>
<string name="backup_review_payments_explainer">A fizető emberek csak azt látják, ami ebben a listában van.</string>
<string name="backup_review_offer">BOLT12 ajánlat</string>
<string name="backup_review_encrypted_cleared">Ön kiürítette</string>
<string name="backup_review_encrypted_added">Ön hozta létre</string>
<string name="backup_review_encrypted_changed">Ön módosította</string>
<string name="backup_review_wallet_body">A saját pénztárca pénzverdéi és kulcsai Önhöz vannak titkosítva, így itt nem hasonlíthatók össze tételesen. A visszaállítás pontosan azt hozza vissza, amit ez az eszköz mentett.</string>
<string name="backup_review_concord_body">A Concord közösségei Önhöz vannak titkosítva, így itt nem hasonlíthatók össze. A visszaállítás pontosan azt hozza vissza, amit ez az eszköz mentett.</string>
<plurals name="backup_card_mutes_visible">
<item quantity="one">Ön %1$d dolog némítását oldotta fel</item>
<item quantity="other">%1$d dolog némítását oldotta fel</item>
</plurals>
<plurals name="backup_card_profile_lost_fields">
<item quantity="one">Ön %1$d profilmezőt távolított el</item>
<item quantity="other">%1$d profilmezőt távolított el</item>
</plurals>
<plurals name="backup_card_nip65_lost">
<item quantity="one">Ön %1$d nyilvános átjátszót távolított el</item>
<item quantity="other">%1$d nyilvános átjátszót távolított el</item>
</plurals>
<plurals name="backup_card_dm_relays_lost">
<item quantity="one">Ön %1$d privát üzenetátjátszót távolított el</item>
<item quantity="other">%1$d privát üzenet átjátszót távolított el</item>
</plurals>
<plurals name="backup_card_key_package_relays_lost">
<item quantity="one">Ön %1$d csoportos csevegési kulcsátjátszót távolított el</item>
<item quantity="other">%1$d csoportcsevegés kulcs átjátszót távolított el</item>
</plurals>
<plurals name="backup_card_search_relays_lost">
<item quantity="one">Ön %1$d kereső átjátszót távolított el</item>
<item quantity="other">%1$d kereső átjátszót távolított el</item>
</plurals>
<plurals name="backup_card_indexer_relays_lost">
<item quantity="one">Ön %1$d indexelő átjátszót távolított el</item>
<item quantity="other">%1$d indexelő átjátszót távolított el</item>
</plurals>
<plurals name="backup_card_relay_feeds_lost">
<item quantity="one">Ön %1$d átjátszó-hírfolyamot távolított el</item>
<item quantity="other">%1$d átjátszó hírfolyamot távolított el</item>
</plurals>
<plurals name="backup_card_private_outbox_lost">
<item quantity="one">Ön %1$d privát átjátszót távolított el</item>
<item quantity="other">%1$d privát átjátszót távolított el</item>
</plurals>
<plurals name="backup_card_trusted_lost">
<item quantity="one">Ön %1$d átjátszóban nem bízik többé</item>
<item quantity="other">%1$d átjátszóban nem bízik többé</item>
</plurals>
<plurals name="backup_card_unblocked">
<item quantity="one">Ön %1$d átjátszó letiltását oldotta fel</item>
<item quantity="other">%1$d átjátszó blokkolását oldotta fel</item>
</plurals>
<plurals name="backup_card_leave_chats">
<item quantity="one">Ön %1$d nyilvános csevegést hagyott el</item>
<item quantity="other">%1$d nyilvános csevegést hagyott el</item>
</plurals>
<plurals name="backup_card_leave_communities">
<item quantity="one">Ön %1$d közösséget hagyott el</item>
<item quantity="other">%1$d közösséget hagyott el</item>
</plurals>
<plurals name="backup_card_leave_rooms">
<item quantity="one">Ön %1$d csevegőszobát hagyott el</item>
<item quantity="other">%1$d csevegőszobát hagyott el</item>
</plurals>
<plurals name="backup_card_leave_groups">
<item quantity="one">Ön %1$d csoportot hagyott el</item>
<item quantity="other">%1$d csoportot hagyott el</item>
</plurals>
<plurals name="backup_card_feeds_removed">
<item quantity="one">Ön %1$d kedvenc hírfolyamot távolított el</item>
<item quantity="other">%1$d kedvenc hírfolyamot távolított el</item>
</plurals>
<plurals name="backup_card_hashtags_unfollowed">
<item quantity="one">Ön %1$d kulcsszót nem követ többé</item>
<item quantity="other">%1$d hashtaget nem követ többé</item>
</plurals>
<plurals name="backup_card_places_unfollowed">
<item quantity="one">Ön %1$d helyet nem követ többé</item>
<item quantity="other">%1$d helyet nem követ többé</item>
</plurals>
<plurals name="backup_card_trust_services_changed">
<item quantity="one">Ön %1$d bizalmi szolgáltatás szolgáltatóját módosította</item>
<item quantity="other">%1$d bizalmi szolgáltatás szolgáltatóját változtatta meg</item>
</plurals>
<plurals name="backup_card_nutzap_mints_removed">
<item quantity="one">Ön %1$d nutzap pénzverdét távolított el</item>
<item quantity="other">%1$d nutzap pénzverdét távolított el</item>
</plurals>
<plurals name="backup_card_payment_targets_removed">
<item quantity="one">Ön %1$d fizetési módot távolított el</item>
<item quantity="other">%1$d fizetési módot távolított el</item>
</plurals>
<plurals name="backup_card_offers_removed">
<item quantity="one">Ön %1$d BOLT12 ajánlatot távolított el</item>
<item quantity="other">%1$d BOLT12 ajánlatot távolított el</item>
</plurals>
<plurals name="backup_card_and_added">
<item quantity="one">%1$s és %2$d hozzáadva</item>
<item quantity="other">%1$s és %2$d hozzáadva</item>
</plurals>
<plurals name="backup_card_and_followed">
<item quantity="one">%1$s és %2$d követve</item>
<item quantity="other">%1$s és %2$d követve</item>
</plurals>
<plurals name="backup_card_and_muted">
<item quantity="one">%1$s és %2$d némítva</item>
<item quantity="other">%1$s és %2$d némítva</item>
</plurals>
<plurals name="backup_card_and_joined">
<item quantity="one">%1$s és %2$d csatlakozva</item>
<item quantity="other">%1$s és %2$d csatlakozva</item>
</plurals>
<plurals name="backup_card_and_blocked">
<item quantity="one">%1$s és %2$d letiltva</item>
<item quantity="other">%1$s és %2$d letiltva</item>
</plurals>
<string name="backup_card_app_settings">A szinkronizált Amethyst alkalmazásbeállításai egy másik, nem Amethyst alkalmazásban módosultak. Ez problémákat okozhat.</string>
<string name="backup_card_nutzap_key_replaced">Ön lecserélte a nutzap kulcsát</string>
<string name="backup_card_nutzap_key_removed">Ön eltávolította a nutzap kulcsát</string>
<string name="backup_card_wallet_emptied">Ön kiürítette a Cashu pénztárcáját</string>
<string name="backup_card_wallet_rewritten">Ön módosította a Cashu pénztárcáját</string>
<string name="backup_card_concord_emptied">Ön kiürítette a Concord listáját</string>
<string name="backup_card_concord_rewritten">Ön módosította a Concord listáját</string>
<string name="backup_conflict_keep_new">Új verzió használata</string>
<string name="backup_conflict_relay_read_only">csak olvasható</string>
<string name="backup_conflict_relay_write_only">csak írható</string>
<string name="backup_conflict_relay_read_write">olvasás és írás</string>
<string name="backup_type_profile">profil</string>
<string name="backup_type_profile_explainer">Az Ön neve, képe, bemutatkozása és fizetési címei, ahogy mindenki látja őket.</string>
<string name="backup_type_follow_list">követési lista</string>
<string name="backup_type_follow_list_explainer">Meghatározza, kinek a bejegyzései jelenjenek meg a kezdőlapon.</string>
<string name="backup_type_mute_list">némítási lista</string>
<string name="backup_type_mute_list_explainer">Elrejti a személyeket, szavakat, kulcsszavakat és beszélgetéseket mindenből, amit lát.</string>
<string name="backup_type_outbox_inbox_relays">nyilvános átjátszólista</string>
<string name="backup_type_outbox_inbox_relays_explainer">Megmondja mindenkinek, hol találhatók a bejegyzései, és hová küldjék a válaszokat és említéseket.</string>
<string name="backup_type_dm_relays">privát üzenet átjátszólista</string>
<string name="backup_type_dm_relays_explainer">Ahová mások kézbesítik a privát üzeneteit.</string>
<string name="backup_type_key_package_relays">csoportos csevegési kulcsátjátszók</string>
<string name="backup_type_key_package_relays_explainer">Ahol mások megtalálják a kulcsokat, hogy titkosított csoportos csevegésekhez adhassák Önt.</string>
<string name="backup_type_search_relays">kereső átjátszólista</string>
<string name="backup_type_search_relays_explainer">Kereséskor használt átjátszók.</string>
<string name="backup_type_indexer_relays">indexelő átjátszólista</string>
<string name="backup_type_indexer_relays_explainer">Azok az átjátszók, amelyeket mások profiljainak és átjátszólistáinak megtalálásához használnak.</string>
<string name="backup_type_relay_feeds">kedvenc átjátszóhírfolyamok</string>
<string name="backup_type_relay_feeds_explainer">Olyan átjátszók, amelyeket hírfolyamként böngész.</string>
<string name="backup_type_blocked_relays">letiltott átjátszólista</string>
<string name="backup_type_blocked_relays_explainer">Átjátszók, amelyekhez ez az alkalmazás soha nem kapcsolódik.</string>
<string name="backup_type_trusted_relays">megbízható átjátszólista</string>
<string name="backup_type_trusted_relays_explainer">Átjátszók, amelyekhez kérdezés nélkül kapcsolódhat.</string>
<string name="backup_type_private_outbox_relays">privát átjátszólista</string>
<string name="backup_type_private_outbox_relays_explainer">Ahol a piszkozatai és privát adatai tárolódnak.</string>
<string name="backup_type_app_settings">szinkronizált alkalmazásbeállítások</string>
<string name="backup_type_app_settings_explainer">Az eszközök között szinkronizált Amethyst-beállítások. Titkosítva vannak, így csak a jelenlétük hasonlítható össze.</string>
<string name="backup_type_public_chats">nyilvános csevegési lista</string>
<string name="backup_type_public_chats_explainer">Nyilvános csevegések, amelyekhez csatlakozott.</string>
<string name="backup_type_communities">közösséglista</string>
<string name="backup_type_communities_explainer">Közösségek, amelyekhez csatlakozott.</string>
<string name="backup_type_hashtags">követett kulcsszavak</string>
<string name="backup_type_hashtags_explainer">Kulcsszavak, amelyek bejegyzései megjelennek a hírfolyamban.</string>
<string name="backup_type_geohashes">követett helyek</string>
<string name="backup_type_geohashes_explainer">Helyek, amelyek bejegyzései megjelennek a hírfolyamaiban.</string>
<string name="backup_type_favorite_algo_feeds">kedvenc hírfolyamlista</string>
<string name="backup_type_favorite_algo_feeds_explainer">Mentett algoritmus-hírfolyamok.</string>
<string name="backup_type_ephemeral_chats">rövid életű csevegési lista</string>
<string name="backup_type_ephemeral_chats_explainer">Rövid életű csevegőszobák, amelyekhez csatlakozott.</string>
<string name="backup_type_relay_groups">csoportlista</string>
<string name="backup_type_relay_groups_explainer">Átjátszó-alapú csoportok, amelyekhez csatlakozott.</string>
<string name="backup_type_concord_communities">Concord közösséglista</string>
<string name="backup_type_concord_communities_explainer">A Concord közösségek, amelyekhez csatlakozott. Titkosítva vannak, így csak a jelenlétük hasonlítható össze.</string>
<string name="backup_type_trust_providers">bizalmi szolgáltatólista</string>
<string name="backup_type_trust_providers_explainer">A szolgáltatások, amelyekben megbízik, hogy rangsorolják és ellenőrizzék Önnek az embereket.</string>
<string name="backup_type_cashu_wallet">Cashu pénztárca</string>
<string name="backup_type_cashu_wallet_explainer">Az ecash pénztárca beállításai és pénzverdéi. Titkosítva vannak, így csak a jelenlétük hasonlítható össze.</string>
<string name="backup_type_nutzap_info">nutzap fogadási beállítások</string>
<string name="backup_type_nutzap_info_explainer">A pénzverdék és a kulcs, amelyeket mások a nutzapok küldéséhez használnak az Ön számára.</string>
<string name="backup_type_payment_targets">fizetési címek</string>
<string name="backup_type_payment_targets_explainer">A módok, ahogy mások fizethetnek Önnek.</string>
<string name="backup_type_bolt12_offers">BOLT12 ajánlatok</string>
<string name="backup_type_bolt12_offers_explainer">A lightning ajánlatok, amelyeket mások a fizetéshez használnak az Ön számára.</string>
<string name="backup_type_other">lista</string>
<string name="backup_type_other_explainer">Egy listaelem ezen az eszközön mentve.</string>
<string name="backup_entry_profile_field">Profilmezők</string>
<string name="backup_entry_person">Személyek</string>
<string name="backup_entry_hashtag">Kulcsszavak</string>
<string name="backup_entry_word">Szavak</string>
<string name="backup_entry_thread">Beszélgetések</string>
<string name="backup_entry_public_chat">Nyilvános csevegések</string>
<string name="backup_entry_community">Közösségek</string>
<string name="backup_entry_algo_feed">Hírfolyamok</string>
<string name="backup_entry_location">Helyek</string>
<string name="backup_entry_relay">Átjátszók</string>
<string name="backup_entry_relay_group">Csoportok</string>
<string name="backup_entry_chat_room">Csevegőszobák</string>
<string name="backup_entry_trust_provider">Bizalmi szolgáltatók</string>
<string name="backup_entry_mint">Pénzverdék</string>
<string name="backup_entry_nutzap_key">Nutzap kulcsok</string>
<string name="backup_entry_payment_target">Fizetési címek</string>
<string name="backup_entry_offer">Ajánlatok</string>
<string name="backup_entry_identity_claim">Kapcsolt identitások</string>
<string name="backup_entry_other">Egyéb bejegyzések</string>
<string name="backup_profile_field_name">Felhasználónév</string>
<string name="backup_profile_field_display_name">Megjelenített név</string>
<string name="backup_profile_field_about">Névjegy</string>
<string name="backup_profile_field_picture">Kép</string>
<string name="backup_profile_field_banner">Borító</string>
<string name="backup_profile_field_website">Weboldal</string>
<string name="backup_profile_field_nip05">Nostr-cím</string>
<string name="backup_profile_field_lud16">Lightning-cím</string>
<string name="backup_profile_field_lud06">LNURL</string>
<string name="backup_profile_field_pronouns">Névmások</string>
<string name="backup_profile_field_birthday">Születésnap</string>
<string name="backup_profile_field_clink_offer">CLINK-ajánlat</string>
<string name="backup_profile_field_bot">Bot jelölő</string>
</resources>
@@ -381,382 +381,4 @@
<string name="mute_button">Dźwięk włączony. Kliknij, aby wyciszyć</string>
<string name="play">Odtwórz</string>
<string name="pause">Pauza</string>
<string name="backup_conflict_title">Zmieniłeś %1$s w innej aplikacji</string>
<string name="backup_conflict_removed">Usunięto (%1$s)</string>
<string name="backup_conflict_added">Dodano (%1$s)</string>
<string name="backup_conflict_changed">Zmieniono (%1$s)</string>
<string name="backup_conflict_private_cleared">Usunąłeś wszystkie prywatne (zaszyfrowane) elementy.</string>
<string name="backup_conflict_private_changed">Zmieniłeś również elementy prywatne (zaszyfrowane). Nie można ich tutaj porównać.</string>
<string name="backup_conflict_private_added">Dodano prywatne (zaszyfrowane) pozycje.</string>
<string name="backup_conflict_restore_mine">Przywróć zapisaną wersję</string>
<string name="backup_review_saved">Na tym urządzeniu</string>
<string name="backup_review_new">Nowa wersja</string>
<string name="backup_review_not_set">Nie ustawiono</string>
<string name="backup_conflict_count_removed">Usunięto %1$s</string>
<string name="backup_conflict_count_added">Dodano %1$s</string>
<string name="backup_conflict_count_changed">%1$s zmieniono</string>
<plurals name="backup_card_follows_dropped">
<item quantity="one">Przestałeś obserwować %1$d osobę</item>
<item quantity="few">Przestałeś obserwować %1$d osób</item>
<item quantity="many">Przestałeś obserwować %1$d osób</item>
<item quantity="other">Przestałeś obserwować %1$d osoby</item>
</plurals>
<string name="backup_card_accept">Zaakceptuj</string>
<string name="backup_review_title_updated">%1$s zaktualizowano</string>
<string name="backup_review_title_cleared">%1$s wyczyszczono</string>
<string name="backup_review_changed_by_other_app">Zmieniono w innej aplikacji · %1$s</string>
<string name="backup_action_keep_count">Zachowaj %1$s</string>
<string name="backup_action_restore_count">Przywróć %1$s</string>
<string name="backup_action_remute">Ponownie wycisz %1$s</string>
<string name="backup_action_rejoin">Ponów %1$s</string>
<string name="backup_action_restore_profile">Przywróć profil</string>
<string name="backup_action_restore_relays">Przywróć transmitery</string>
<string name="backup_review_kept_count">Zachowano %1$s</string>
<string name="backup_review_dropped_count">%1$s odrzuconych</string>
<string name="backup_review_new_count">%1$s nowych</string>
<string name="backup_review_tab_dropped">Usunięte · %1$s</string>
<string name="backup_review_tab_new">Nowe · %1$s</string>
<string name="backup_review_tab_edited">Edytowane · %1$s</string>
<string name="backup_review_search_people">Szukaj %1$s osób</string>
<string name="backup_review_nothing_here">Nic tu nie ma!</string>
<string name="backup_review_unmuted">%1$s odblokowanych</string>
<string name="backup_review_unmuted_explainer">Mogą one ponownie pojawić się w Twoich informacjach</string>
<string name="backup_review_newly_muted">%1$s nowo wyciszonych</string>
<string name="backup_review_words_and_hashtags">Wyrazy &amp; hasztagi</string>
<string name="backup_review_names_and_more">%1$s i %2$s więcej</string>
<string name="backup_review_private_wiped_title">Usunąłeś wszystkie prywatne elementy</string>
<string name="backup_review_private_wiped_body">Są zaszyfrowane, więc tylko Ty możesz zobaczyć, co to było. Odtworzenie przywróci je wszystkie.</string>
<string name="backup_review_private_added_title">Ustawiłeś te pozycje jako prywatne</string>
<string name="backup_review_private_added_body">Są teraz zaszyfrowane, co prawdopodobnie wyjaśnia, gdzie podziały się powyższe elementy publiczne.</string>
<string name="backup_review_private_changed_title">Zmieniłeś prywatne elementy</string>
<string name="backup_review_private_changed_body">Są zaszyfrowane, więc nie można ich tu porównać.</string>
<string name="backup_review_gone">Zniknęła</string>
<string name="backup_review_new_tag">Nowa</string>
<string name="backup_review_dropped_tag">Usunięta</string>
<string name="backup_review_demoted_tag">Przeniesiona</string>
<string name="backup_review_units_tag">Jednostek</string>
<string name="backup_review_left_tag">Zostało</string>
<string name="backup_review_renamed_tag">Zmieniono nazwę</string>
<string name="backup_review_outbox">Skrzynka nadawcza</string>
<string name="backup_review_outbox_explainer">gdzie publikujesz</string>
<string name="backup_review_inbox">Skrzynka odbiorcza</string>
<string name="backup_review_inbox_explainer">gdzie inni docierają do Ciebie</string>
<string name="backup_review_key_replaced">Wymieniono klucz odbierający</string>
<string name="backup_review_key_removed">Usunięto klucz odbierający</string>
<string name="backup_review_key_warning">Nutzapy zostaną przypisane do tego nowego klucza. Jeśli urządzenie nie posiada tego klucza, może się okazać, że nie będzie można ich tutaj zrealizować.</string>
<string name="backup_review_left">zostało</string>
<string name="backup_review_joined">dołączył(a)</string>
<string name="backup_review_renamed">zmieniono nazwę</string>
<string name="backup_review_was_named">było „%1$s”</string>
<string name="backup_action_block_again">Zablokuj %1$s ponownie</string>
<string name="backup_action_restore_feeds">Przywróć kanały</string>
<string name="backup_action_restore_topics">Przywróć tematy</string>
<string name="backup_action_restore_places">Przywróć miejsca</string>
<string name="backup_action_restore_providers">Przywróć dostawców</string>
<string name="backup_action_restore_wallet">Przywróć portfel</string>
<string name="backup_review_kept">zachowane</string>
<string name="backup_review_receiving_tag">Pobieranie</string>
<string name="backup_review_listed_tag">Na Liście</string>
<string name="backup_review_searching_tag">Szukam</string>
<string name="backup_review_storing_tag">Przechowywanie</string>
<string name="backup_review_trusted_tag">Zaufane</string>
<string name="backup_review_untrusted_tag">Niezaufane</string>
<string name="backup_review_unblocked">%1$s odblokowane</string>
<string name="backup_review_unblocked_explainer">Ametyst ponownie połączy się z nimi</string>
<string name="backup_review_no_longer_blocked">Nie jest już zablokowana</string>
<string name="backup_review_newly_blocked">Nowo zablokowana</string>
<string name="backup_review_still_blocked">Nadal zablokowana</string>
<string name="backup_review_allowed_tag">Dozwolona</string>
<string name="backup_review_moved_tag">Przeniesiona</string>
<string name="backup_review_feed">Kanał</string>
<string name="backup_review_topics_gone">tematy zniknęły</string>
<string name="backup_review_places_gone"> miejsca zniknęły</string>
<string name="backup_review_new_count_caption">nowe</string>
<string name="backup_review_hashtags_you_follow">Hashtagi, które obserwujesz</string>
<string name="backup_review_places_you_follow">Miejsca które obserwujesz</string>
<string name="backup_review_services_changed">Zmieniono %1$s z %2$s serwisów</string>
<string name="backup_review_services_changed_explainer">Teraz pochodzą od kogoś innego albo od nikogo</string>
<string name="backup_review_service">Serwis</string>
<string name="backup_review_service_rank">Ranking</string>
<string name="backup_review_service_followers">Obserwujący</string>
<string name="backup_review_service_topics">Tematy</string>
<string name="backup_review_trust_warning">Rankingi, liczba obserwujących i filtrowanie spamu, które widzisz, pochodzą od tych dostawców.</string>
<plurals name="backup_review_payment_targets_gone">
<item quantity="one">%1$d sposób płatności został usunięty</item>
<item quantity="few">%1$d sposobów płatności zostało usuniętych</item>
<item quantity="many">%1$d sposobów płatności zostało usuniętych</item>
<item quantity="other">%1$d sposoby płatności zostały usunięte</item>
</plurals>
<plurals name="backup_review_offers_gone">
<item quantity="one">%1$d oferta usunięta</item>
<item quantity="few">%1$d ofert usunięto</item>
<item quantity="many">%1$d ofert usunięto</item>
<item quantity="other">%1$d oferty usunięte</item>
</plurals>
<string name="backup_review_payments_explainer">Osoby, które Ci płacą, widzą tylko to, co znajduje się na tej liście.</string>
<string name="backup_review_offer">Oferta BOLT12</string>
<string name="backup_review_encrypted_cleared">Opróżniłeś to</string>
<string name="backup_review_encrypted_added">Utworzyłeś to</string>
<string name="backup_review_encrypted_changed">Zmieniłeś to</string>
<string name="backup_review_wallet_body">Minty i klucze w Twoim portfelu są zaszyfrowane dla Ciebie, więc nie można ich tutaj porównać pozycja po pozycji. Przywrócenie danych powoduje przywrócenie dokładnie tego, co zostało zapisane na tym urządzeniu.</string>
<string name="backup_review_concord_body">Twoje społeczności w Concord są dla Ciebie zaszyfrowane, więc nie można ich tutaj porównać. Przywrócenie danych powoduje przywrócenie dokładnie tego, co zostało zapisane na tym urządzeniu.</string>
<plurals name="backup_card_mutes_visible">
<item quantity="one">Włączyłeś dźwięk dla %1$d elementu</item>
<item quantity="few">Włączyłeś dźwięk dla %1$d elementów</item>
<item quantity="many">Włączyłeś dźwięk dla %1$d elementów</item>
<item quantity="other">%1$d elementy mają włączony dźwięk</item>
</plurals>
<plurals name="backup_card_profile_lost_fields">
<item quantity="one">Usunąłeś %1$d pole profilu</item>
<item quantity="few">Usunąłeś %1$d pól profilowych</item>
<item quantity="many">Usunąłeś %1$d pól profilowych</item>
<item quantity="other">Usunąłeś %1$d pola profilowe</item>
</plurals>
<plurals name="backup_card_nip65_lost">
<item quantity="one">Usunąłeś %1$d publiczny transmiter</item>
<item quantity="few">Usunąłeś %1$d publicznych transmiterów</item>
<item quantity="many">Usunąłeś %1$d publicznych transmiterów</item>
<item quantity="other">Usunąłeś %1$d publiczne transmitery</item>
</plurals>
<plurals name="backup_card_dm_relays_lost">
<item quantity="one">Usunąłeś %1$d transmiter DM</item>
<item quantity="few">Usunąłeś %1$d transmiterów DM</item>
<item quantity="many">Usunąłeś %1$d transmiterów DM</item>
<item quantity="other">Usunąłeś %1$d transmitery DM</item>
</plurals>
<plurals name="backup_card_key_package_relays_lost">
<item quantity="one">Usunięto %1$d transmiter klucza grupowego czatu</item>
<item quantity="few">Usunięto %1$d transmiterów klucza grupowego czatu</item>
<item quantity="many">Usunięto %1$d transmiterów klucza grupowego czatu</item>
<item quantity="other">Usunięto %1$d transmitery klucza grupowego czatu</item>
</plurals>
<plurals name="backup_card_search_relays_lost">
<item quantity="one">Usunięto %1$d transmiter wyszukiwania</item>
<item quantity="few">Usunięto %1$d transmiterów wyszukiwania</item>
<item quantity="many">Usunięto %1$d transmiterów wyszukiwania</item>
<item quantity="other">Usunięto %1$d transmitery wyszukiwania</item>
</plurals>
<plurals name="backup_card_indexer_relays_lost">
<item quantity="one">Usunięto %1$d transmiter indeksujący</item>
<item quantity="few">Usunięto %1$d transmiterów indeksujących</item>
<item quantity="many">Usunięto %1$d transmiterów indeksujących</item>
<item quantity="other">Usunięto %1$d transmitery indeksujące</item>
</plurals>
<plurals name="backup_card_relay_feeds_lost">
<item quantity="one">Usunięto %1$d kanał transmitera</item>
<item quantity="few">Usunięto %1$d kanałów transmitera</item>
<item quantity="many">Usunięto %1$d kanałów transmitera</item>
<item quantity="other">Usunięto %1$d kanały transmitera</item>
</plurals>
<plurals name="backup_card_private_outbox_lost">
<item quantity="one">Usunięto %1$d prywatny transmiter</item>
<item quantity="few">Usunięto %1$d prywatnych transmiterów</item>
<item quantity="many">Usunięto %1$d prywatnych transmiterów</item>
<item quantity="other">Usunięto %1$d prywatne transmitery</item>
</plurals>
<plurals name="backup_card_trusted_lost">
<item quantity="one">Przestałeś ufać %1$d transmiterowi</item>
<item quantity="few">Przestałeś ufać %1$d transmiterom</item>
<item quantity="many">Przestałeś ufać %1$d transmiterom</item>
<item quantity="other">Przestałeś ufać %1$d transmiterom</item>
</plurals>
<plurals name="backup_card_unblocked">
<item quantity="one">Odblokowałeś %1$d transmiter</item>
<item quantity="few">Odblokowałeś %1$d transmiterów</item>
<item quantity="many">Odblokowałeś %1$d transmiterów</item>
<item quantity="other">Odblokowałeś %1$d transmitery</item>
</plurals>
<plurals name="backup_card_leave_chats">
<item quantity="one">Opuściłeś %1$d czat publiczny</item>
<item quantity="few">Opuściłeś %1$d czatów publicznych</item>
<item quantity="many">Opuściłeś %1$d czatów publicznych</item>
<item quantity="other">Opuściłeś %1$d czaty publiczne</item>
</plurals>
<plurals name="backup_card_leave_communities">
<item quantity="one">Opuściłeś %1$d społeczność</item>
<item quantity="few">Opuściłeś %1$d społeczności</item>
<item quantity="many">Opuściłeś %1$d społeczności</item>
<item quantity="other">Opuściłeś %1$d społeczności</item>
</plurals>
<plurals name="backup_card_leave_rooms">
<item quantity="one">Opuściłeś %1$d pokój czatu</item>
<item quantity="few">Opuściłeś %1$d pokoi czatu</item>
<item quantity="many">Opuściłeś %1$d pokoi czatu</item>
<item quantity="other">Opuściłeś %1$d pokoje czatu</item>
</plurals>
<plurals name="backup_card_leave_groups">
<item quantity="one">Opuściłeś %1$d grupę</item>
<item quantity="few">Opuściłeś %1$d grup</item>
<item quantity="many">Opuściłeś %1$d grup</item>
<item quantity="other">Opuszczono %1$d grupy</item>
</plurals>
<plurals name="backup_card_feeds_removed">
<item quantity="one">Usunąłeś %1$d ulubiony kanał</item>
<item quantity="few">Usunąłeś %1$d ulubionych kanałów</item>
<item quantity="many">Usunąłeś %1$d ulubionych kanałów</item>
<item quantity="other">Usunięto %1$d ulubione kanały</item>
</plurals>
<plurals name="backup_card_hashtags_unfollowed">
<item quantity="one">Przestałeś obserwować %1$d hashtag</item>
<item quantity="few">Przestałeś obserwować %1$d hashtagów</item>
<item quantity="many">Przestałeś obserwować %1$d hashtagów</item>
<item quantity="other">Przestałeś obserwować %1$d hashtagi</item>
</plurals>
<plurals name="backup_card_places_unfollowed">
<item quantity="one">Przestałeś obserwować %1$d miejsce</item>
<item quantity="few">Przestałeś obserwować %1$d miejsc</item>
<item quantity="many">Przestałeś obserwować %1$d miejsc</item>
<item quantity="other">Przestałeś obserwować %1$d miejsca</item>
</plurals>
<plurals name="backup_card_trust_services_changed">
<item quantity="one">Zmieniłeś dostawcę %1$d zaufanego serwisu</item>
<item quantity="few">Zmieniłeś dostawcę %1$d zaufanych serwisów</item>
<item quantity="many">Zmieniłeś dostawcę %1$d zaufanych serwisów</item>
<item quantity="other">Zmieniłeś dostawcę %1$d zaufanych serwisów</item>
</plurals>
<plurals name="backup_card_nutzap_mints_removed">
<item quantity="one">Usunięto %1$d nutzap mint</item>
<item quantity="few">Usunięto %1$d nutzap mintów</item>
<item quantity="many">Usunięto %1$d nutzap mintów</item>
<item quantity="other">Usunięto %1$d nutzap minty</item>
</plurals>
<plurals name="backup_card_payment_targets_removed">
<item quantity="one">Usunięto %1$d sposób na otrzymanie zapłaty</item>
<item quantity="few">Usunięto %1$d sposobów otrzymywania zapłaty</item>
<item quantity="many">Usunięto %1$d sposobów otrzymywania zapłaty</item>
<item quantity="other">Usunięto %1$d sposoby otrzymywania zapłaty</item>
</plurals>
<plurals name="backup_card_offers_removed">
<item quantity="one">Usunięto %1$d ofertę BOLT12</item>
<item quantity="few">Usunięto %1$d ofert BOLT12</item>
<item quantity="many">Usunięto %1$d ofert BOLT12</item>
<item quantity="other">Usunięto %1$d oferty BOLT12</item>
</plurals>
<plurals name="backup_card_and_added">
<item quantity="one">%1$s i dodano %2$d</item>
<item quantity="few">%1$s i dodano %2$d</item>
<item quantity="many">%1$s i dodano %2$d</item>
<item quantity="other">%1$s i dodano %2$d</item>
</plurals>
<plurals name="backup_card_and_followed">
<item quantity="one">%1$s i obserwowany %2$d</item>
<item quantity="few">%1$s i obserwowanych %2$d</item>
<item quantity="many">%1$s i obserwowanych %2$d</item>
<item quantity="other">%1$s i obserwowane %2$d</item>
</plurals>
<plurals name="backup_card_and_muted">
<item quantity="one">%1$s i wyciszony %2$d</item>
<item quantity="few">%1$s i wyciszono %2$d</item>
<item quantity="many">%1$s i wyciszono %2$d</item>
<item quantity="other">%1$s i wyciszone %2$d</item>
</plurals>
<plurals name="backup_card_and_joined">
<item quantity="one">%1$s i dołączono do %2$d</item>
<item quantity="few">%1$s i dołączono do %2$d</item>
<item quantity="many">%1$s i dołączono do %2$d</item>
<item quantity="other">%1$s i dołączono do %2$d</item>
</plurals>
<plurals name="backup_card_and_blocked">
<item quantity="one">%1$s i zablokowano %2$d</item>
<item quantity="few">%1$s i zablokowano %2$d</item>
<item quantity="many">%1$s i zablokowano %2$d</item>
<item quantity="other">%1$s i zablokowano %2$d</item>
</plurals>
<string name="backup_card_app_settings">Twoje zsynchronizowane ustawienia aplikacji Amethyst zostały zmienione w innej aplikacji, która nie jest Amethystem. To może powodować problemy.</string>
<string name="backup_card_nutzap_key_replaced">Wymieniłeś swój klucz Nutzap</string>
<string name="backup_card_nutzap_key_removed">Usunąłeś swój klucz nutzap</string>
<string name="backup_card_wallet_emptied">Opróżniłeś swój portfel Cashu</string>
<string name="backup_card_wallet_rewritten">Zmieniłeś swój portfel Cashu</string>
<string name="backup_card_concord_emptied">Opróżniłeś swoją listę Concorda</string>
<string name="backup_card_concord_rewritten">Zmieniłeś swoją listę Concorda</string>
<string name="backup_conflict_keep_new">Użyj nowej wersji</string>
<string name="backup_conflict_relay_read_only">tylko do odczytu</string>
<string name="backup_conflict_relay_write_only">tylko do zapisu</string>
<string name="backup_conflict_relay_read_write">odczyt i zapis</string>
<string name="backup_type_profile">profil</string>
<string name="backup_type_profile_explainer">Twoje imię i nazwisko, zdjęcie, biografia oraz adresy do płatności – tak, jak widzą je wszyscy.</string>
<string name="backup_type_follow_list">lista obserwowanych</string>
<string name="backup_type_follow_list_explainer">Określa, czyje posty pojawiają się w Twoim kanale głównym.</string>
<string name="backup_type_mute_list">lista zablokowanych</string>
<string name="backup_type_mute_list_explainer">Ukrywa osoby, słowa, hashtagi i wątki we wszystkich wyświetlanych treściach.</string>
<string name="backup_type_outbox_inbox_relays">lista publicznych transmiterów</string>
<string name="backup_type_outbox_inbox_relays_explainer">Informuje wszystkich, gdzie mogą znaleźć Twoje posty oraz gdzie mają wysyłać Ci odpowiedzi i wzmianki.</string>
<string name="backup_type_dm_relays">Lista transmiterów DM</string>
<string name="backup_type_dm_relays_explainer">Gdzie inni dostarczają Twoje prywatne wiadomości.</string>
<string name="backup_type_key_package_relays">transmitery kluczy czatu grupowego</string>
<string name="backup_type_key_package_relays_explainer">Gdzie inni znajdą klucze do dodawania cię do zaszyfrowanych czatów grupowych.</string>
<string name="backup_type_search_relays">lista transmiterów wyszukiwania</string>
<string name="backup_type_search_relays_explainer">Transmitery używane podczas wyszukiwania.</string>
<string name="backup_type_indexer_relays">lista transmiterów indeksujących</string>
<string name="backup_type_indexer_relays_explainer">Transmitery służące do wyszukiwania profili innych osób oraz list transmiterów.</string>
<string name="backup_type_relay_feeds">ulubione kanały transmitera</string>
<string name="backup_type_relay_feeds_explainer">Transmitery, które przeglądasz jako kanały informacyjne.</string>
<string name="backup_type_blocked_relays">lista zablokowanych przekaźników</string>
<string name="backup_type_blocked_relays_explainer">Transmitery, z którymi ta aplikacja nigdy się nie łączy.</string>
<string name="backup_type_trusted_relays">lista zaufanych transmiterów</string>
<string name="backup_type_trusted_relays_explainer">Zaufane transmitery, z którymi bez wahania nawiązujesz połączenie.</string>
<string name="backup_type_private_outbox_relays">lista prywatnych transmiterów</string>
<string name="backup_type_private_outbox_relays_explainer">Gdzie przechowywane są Twoje projekty i prywatne dane.</string>
<string name="backup_type_app_settings">zsynchronizowane ustawienia aplikacji</string>
<string name="backup_type_app_settings_explainer">Ustawienia Amethyst są synchronizowane na wszystkich Twoich urządzeniach. Są one zaszyfrowane, więc można porównać jedynie sam fakt ich obecności.</string>
<string name="backup_type_public_chats">lista czatów publicznych</string>
<string name="backup_type_public_chats_explainer">Publiczne czaty, do których dołączyłeś.</string>
<string name="backup_type_communities">lista społeczności</string>
<string name="backup_type_communities_explainer">Społeczności, do których dołączyłeś.</string>
<string name="backup_type_hashtags">obserwowane hashtagi</string>
<string name="backup_type_hashtags_explainer">Hashtagi, z których pochodzą posty wyświetlane w Twoich kanałach.</string>
<string name="backup_type_geohashes">obserwowane lokalizacje</string>
<string name="backup_type_geohashes_explainer">Miejsca, z których posty pojawiają się na Twoich kanałach.</string>
<string name="backup_type_favorite_algo_feeds">lista ulubionych kanałów</string>
<string name="backup_type_favorite_algo_feeds_explainer">Zapisane przez Ciebie kanały algorytmiczne.</string>
<string name="backup_type_ephemeral_chats">lista czatów efemerycznych</string>
<string name="backup_type_ephemeral_chats_explainer">Tymczasowe czaty, do których dołączyłeś.</string>
<string name="backup_type_relay_groups">lista grup</string>
<string name="backup_type_relay_groups_explainer">Grupy oparte na transmiterach, do których dołączyłeś.</string>
<string name="backup_type_concord_communities">Lista społeczności Concord</string>
<string name="backup_type_concord_communities_explainer">Społeczności Concord, do których dołączyłeś. Są one zaszyfrowane, więc można porównać jedynie fakt ich istnienia.</string>
<string name="backup_type_trust_providers">lista zaufanych dostawców</string>
<string name="backup_type_trust_providers_explainer">Serwisy, którym ufasz, a które zajmują się klasyfikowaniem i weryfikacją osób w Twoim imieniu.</string>
<string name="backup_type_cashu_wallet">Portfel Cashu</string>
<string name="backup_type_cashu_wallet_explainer">Ustawienia portfela ecash oraz minty. Są one zaszyfrowane, więc można porównać jedynie sam fakt ich istnienia.</string>
<string name="backup_type_nutzap_info">ustawienia odbioru nutzap</string>
<string name="backup_type_nutzap_info_explainer">Minty i klucz, które inni używają do wysyłania ci „nutzapów”.</string>
<string name="backup_type_payment_targets">adresy płatności</string>
<string name="backup_type_payment_targets_explainer">Sposoby, w jakie inni mogą Ci zapłacić.</string>
<string name="backup_type_bolt12_offers">Oferta BOLT12</string>
<string name="backup_type_bolt12_offers_explainer">Ta możliwość pozwala innym płacić Ci za pomocą Lightning.</string>
<string name="backup_type_other">lista</string>
<string name="backup_type_other_explainer">Lista zapisana na tym urządzeniu.</string>
<string name="backup_entry_profile_field">Pola profilu</string>
<string name="backup_entry_person">Osoby</string>
<string name="backup_entry_hashtag">Hashtagi</string>
<string name="backup_entry_word">Słowa</string>
<string name="backup_entry_thread">Wątki</string>
<string name="backup_entry_public_chat">Czaty publiczne</string>
<string name="backup_entry_community">Społeczności</string>
<string name="backup_entry_algo_feed">Kanały</string>
<string name="backup_entry_location">Lokalizacje</string>
<string name="backup_entry_relay">Transmitery</string>
<string name="backup_entry_relay_group">Grupy</string>
<string name="backup_entry_chat_room">Czat pokoje</string>
<string name="backup_entry_trust_provider">Zaufani dostawcy</string>
<string name="backup_entry_mint">Minty</string>
<string name="backup_entry_nutzap_key">Klucze Nutzap</string>
<string name="backup_entry_payment_target">Adresy płatności</string>
<string name="backup_entry_offer">Oferty</string>
<string name="backup_entry_identity_claim">Powiązane tożsamości</string>
<string name="backup_entry_other">Inne wpisy</string>
<string name="backup_profile_field_name">Nazwa użytkownika</string>
<string name="backup_profile_field_display_name">Nazwa wyświetlana</string>
<string name="backup_profile_field_about">O profilu</string>
<string name="backup_profile_field_picture">Zdjęcie</string>
<string name="backup_profile_field_banner">Baner</string>
<string name="backup_profile_field_website">Strona www</string>
<string name="backup_profile_field_nip05">Adres Nostr</string>
<string name="backup_profile_field_lud16">Lightning Adres</string>
<string name="backup_profile_field_lud06">LNURL</string>
<string name="backup_profile_field_pronouns">Zaimki</string>
<string name="backup_profile_field_birthday">Data urodzin</string>
<string name="backup_profile_field_clink_offer">Oferta CLINK</string>
<string name="backup_profile_field_bot">Flaga bota</string>
</resources>
-570
View File
@@ -305,250 +305,6 @@
<string name="cordn_groups_manage">Coordinators</string>
<string name="cordn_groups_down_note">Nothing is arriving from these groups. Amethyst keeps retrying.</string>
<string name="cordn_groups_none">No cordn groups yet</string>
<string name="cordn_groups_none_detail">A cordn group lives on one coordinator that holds its membership and passes messages along, without ever seeing what anyone writes.</string>
<string name="cordn_groups_start">Start one</string>
<string name="cordn_group_unavailable">This group is not available. Its coordinator may have been removed.</string>
<string name="cordn_group_info">Group info</string>
<string name="cordn_message_deleted">Message deleted</string>
<string name="cordn_message_edited">edited</string>
<string name="cordn_action_go_to_message">Go to message</string>
<string name="cordn_delete_confirm_title">Withdraw message</string>
<string name="cordn_delete_confirm_body">Everyone in the group will see this message as withdrawn, and Amethyst will stop showing its text. The coordinator still holds the encrypted copy it was sent as, and anyone who already read it has already read it.</string>
<string name="cordn_delivery_accepted">Accepted by the coordinator</string>
<string name="cordn_message_details_title">Message details</string>
<string name="cordn_message_details_sent_at">Sent</string>
<string name="cordn_message_details_cursor">Cursor</string>
<string name="cordn_message_details_coordinator">Coordinator</string>
<string name="cordn_composer_hint">Message</string>
<string name="cordn_action_reply">Reply</string>
<string name="cordn_action_edit">Edit</string>
<string name="cordn_action_delete">Delete</string>
<string name="cordn_action_pin">Pin</string>
<string name="cordn_pinned_previous">Previous pinned message</string>
<string name="cordn_pinned_next">Next pinned message</string>
<string name="cordn_pinned_show_all">Show all pinned messages</string>
<string name="cordn_pinned_title">Pinned messages</string>
<string name="cordn_pinned_by">Pinned by %1$s</string>
<plurals name="cordn_pinned_count">
<item quantity="one">%1$d pinned message in this group</item>
<item quantity="other">%1$d pinned messages in this group</item>
</plurals>
<string name="cordn_action_unpin">Unpin</string>
<string name="cordn_action_editing">Editing your message</string>
<string name="cordn_media_upload_failed">Could not send that file.</string>
<string name="cordn_send_failed">That did not send.</string>
<string name="cordn_send_no_session">This group\'s coordinator is not open, so nothing can be sent yet.</string>
<string name="cordn_media_download_failed">Could not open that file.</string>
<string name="cordn_media_no_server">No media server is set for this account, so there is nowhere to put the file. Pick one in Settings.</string>
<string name="cordn_media_unreadable">That file could not be read.</string>
<string name="cordn_voice_record">Record a voice note</string>
<string name="cordn_voice_stop">Stop and send</string>
<string name="cordn_voice_play">Play voice note</string>
<string name="cordn_backup_explainer">Your nsec cannot bring cordn groups back. A cordn group lives as encryption state on this device and an ordered stream on a coordinator that cannot read it — lose the device without a backup and the group is gone, including everything already said in it.</string>
<string name="cordn_backup_passphrase">Passphrase</string>
<string name="cordn_backup_contents_title">What is in the file</string>
<string name="cordn_backup_contents_body">Your group encryption keys, your key packages, and every message in those groups. Anyone who opens it can read the conversations. The passphrase is the only thing protecting it once it leaves Amethyst.</string>
<string name="cordn_backup_export">Export</string>
<string name="cordn_backup_exported">Backup saved.</string>
<string name="cordn_backup_restore_title">Restore</string>
<string name="cordn_backup_restore_body">Restoring replaces the cordn groups on this device with the ones in the file. It is for a phone that has taken over from another — not a way to use the same groups on two devices at once.</string>
<string name="cordn_backup_restore">Restore</string>
<string name="cordn_backup_restore_confirm_title">Replace this device\'s cordn groups?</string>
<string name="cordn_backup_restore_confirm_body">Every cordn group now on this device will be removed and replaced by the ones in the file.\n\nDo not restore onto a second phone while the first is still in use. Both would hold the same group keys, and once both have sent a message the group breaks for everyone — silently, and with no way back.</string>
<string name="cordn_backup_restored">Restored.</string>
<string name="cordn_backup_failed">That did not work. Check the passphrase and the file.</string>
<string name="cordn_send">Send</string>
<string name="cordn_info_coordinator">Coordinator</string>
<string name="cordn_info_coordinator_key">Coordinator key</string>
<string name="cordn_info_coordinator_nprofile">Coordinator link</string>
<string name="cordn_info_copy_nprofile">Copy the coordinator\'s nprofile</string>
<string name="cordn_info_gid">Group id</string>
<string name="cordn_info_epoch">Epoch</string>
<string name="cordn_info_members">Members</string>
<string name="cordn_info_egalitarian">This group has no admins: everyone can add, remove and rename, permanently.</string>
<string name="cordn_info_technical">Technical details</string>
<string name="cordn_create_title">New Cordn group</string>
<string name="cordn_create_explainer">A cordn group is ordered by one coordinator you choose. Creating the group happens on this device — the coordinator only learns the group exists once you invite someone or send a message.</string>
<string name="cordn_create_people_none">Nobody yet &#8212; tap to add people</string>
<string name="cordn_create_people_empty">Nobody added yet. A group can be created empty and invitations sent later, but naming people now is what shows which coordinator can reach them.</string>
<string name="cordn_create_people_done">Done</string>
<string name="cordn_create_you">You, always an admin</string>
<string name="cordn_create_step_name">Name it</string>
<string name="cordn_create_step_people">Who is in it</string>
<string name="cordn_create_step_where">Where it lives</string>
<string name="cordn_create_step_admins">Who can add and remove</string>
<string name="cordn_create_add_member">Add someone</string>
<string name="cordn_create_people_note">Naming people now is what tells us which coordinator can reach them. Each one gets a Welcome left on that coordinator, which they pick up next time they open their app.</string>
<string name="cordn_create_reach_unknown">Not checked yet</string>
<string name="cordn_create_reach_none">No key on any coordinator you use</string>
<string name="cordn_create_egalitarian">Everyone can add and remove</string>
<string name="cordn_create_admin">Admin</string>
<string name="cordn_create_no_coordinator">No coordinator chosen</string>
<string name="cordn_create_coordinator_change">Change</string>
<string name="cordn_create_coordinator_hide">Done</string>
<string name="cordn_create_coverage_unknown">Not checked &#8212; picking it will ask</string>
<string name="cordn_create_coverage_all">Reaches all %1$d of your people</string>
<string name="cordn_create_coverage_partial">Reaches %1$d of %2$d &#8212; the rest would be left out</string>
<string name="cordn_create_outcome_title">The group exists</string>
<string name="cordn_create_outcome_explainer">Each invitation is a Welcome left on the coordinator, so they can fail one at a time. Nobody has been notified: they see it next time they open their app.</string>
<string name="cordn_create_outcome_no_welcome">Added to the group, but the invitation did not reach them &#8212; try again from the group screen</string>
<string name="cordn_create_outcome_waiting">Waiting for them</string>
<string name="cordn_create_outcome_failed">The coordinator refused the invitation</string>
<string name="cordn_create_outcome_skipped">No key here, so no Welcome could be left</string>
<string name="cordn_create_outcome_link">Send a link</string>
<string name="cordn_create_outcome_open">Open the chat</string>
<string name="cordn_create_coordinator">Coordinator</string>
<string name="cordn_create_coordinator_discover">Find coordinators</string>
<string name="cordn_create_coordinator_new">Another coordinator</string>
<string name="cordn_create_coordinator_pubkey">Coordinator public key (hex or npub)</string>
<string name="cordn_create_coordinator_relays">Relays it answers on, one per line</string>
<string name="cordn_create_name">Group name</string>
<string name="cordn_create_description">Description (optional)</string>
<string name="cordn_create_egalitarian_note">Everyone in the group can invite, remove members and change its details. Any member can name admins later, which ends that.</string>
<string name="cordn_create_admin_only_me">Only I manage this group</string>
<string name="cordn_admin_action_failed">That change could not be made.</string>
<string name="cordn_info_remove_member">Remove</string>
<string name="cordn_info_remove_confirm_title">Remove from group?</string>
<string name="cordn_info_remove_confirm_body">%1$s stops receiving messages from here. They keep what they have already read, and this cannot be undone — they would have to be invited again.</string>
<string name="cordn_info_edit_details">Edit details</string>
<string name="cordn_info_save">Save</string>
<string name="cordn_create_admin_only_me_on">Only you can invite, remove members or change the group\'s details. You can hand that to someone else later.</string>
<string name="cordn_create_action">Create group</string>
<string name="cordn_create_failed">The group could not be created.</string>
<string name="cordn_invitations_title">Cordn invitations</string>
<string name="cordn_invitations_explainer">Checked when you open this screen, and at no other time — asking a coordinator whether anyone invited you tells it you are here, so Amethyst does not do it in the background.</string>
<string name="cordn_invitations_refresh">Check again</string>
<string name="cordn_invitations_accept">Join</string>
<string name="cordn_invitations_decline">Decline</string>
<string name="cordn_invitations_decline_warning">Declining is permanent. Getting back in means being invited again.</string>
<string name="cordn_chat_yesterday">Yesterday</string>
<string name="cordn_chat_unread_divider">New messages</string>
<string name="cordn_chat_empty_title">No messages yet</string>
<string name="cordn_chat_empty_description">Anything sent here is encrypted for this group. The coordinator relays it without being able to read it.</string>
<string name="cordn_reactions_title">Reactions</string>
<string name="cordn_group_unavailable_detail">Add a coordinator in Settings to start using cordn groups.</string>
<string name="cordn_coordinators_discover_explainer">Reads announcements your relays already carry. No coordinator is contacted, so none of them learns you looked.</string>
<string name="cordn_coordinators_discover_action">Look for coordinators</string>
<string name="cordn_coordinators_discover_add">Add</string>
<string name="cordn_coordinators_discover_failed">Could not read announcements</string>
<string name="cordn_coordinators_discover_none">Nobody is announcing on your relays.</string>
<string name="cordn_coordinators_discover_unheard">Nothing found, and %1$d of your relays did not answer.</string>
<string name="cordn_coordinators_discover_seen">Announced %1$s ago</string>
<string name="cordn_coordinators_discover_seen_on">Last announced %1$s</string>
<string name="cordn_coordinators_relays">%1$s</string>
<string name="cordn_coordinators_relays_more">%1$s +%2$d more</string>
<string name="cordn_coordinators_show_all">Show all %1$d</string>
<string name="cordn_coordinators_show_fewer">Show fewer</string>
<string name="cordn_coordinators_show_older">Show %1$d that stopped announcing</string>
<string name="cordn_coordinators_hide_older">Hide the ones that stopped announcing</string>
<string name="cordn_coordinators_stale_note">These have not announced in over a month. Creating a group on one that has gone away will fail.</string>
<string name="cordn_info_add_member">Add someone</string>
<string name="cordn_info_add_member_placeholder">Name, npub or name@domain</string>
<string name="cordn_info_has_key_package">Can be added on this coordinator</string>
<string name="cordn_info_add_member_note">They can only be added if they published a key package to this coordinator.</string>
<string name="cordn_exposure_open">What this coordinator can see</string>
<string name="cordn_exposure_close">Got it</string>
<string name="cordn_admin_someone">That person</string>
<string name="cordn_admin_no_key_package">%1$s has not published a key package to this coordinator, so they cannot be added yet. Ask them to open cordn on this coordinator first.</string>
<string name="cordn_admin_wrong_key_package">This coordinator served a key package that belongs to someone else, so %1$s was not added. Nothing was changed.</string>
<string name="cordn_admin_no_welcome">%1$s could not be given a way into the group, so the add was abandoned.</string>
<string name="cordn_admin_not_a_member">%1$s is not in this group.</string>
<string name="cordn_admin_no_self_remove">You cannot remove yourself from a cordn group.</string>
<string name="cordn_admin_coordinator_silent">The coordinator did not answer, so nothing was changed. Try again in a moment.</string>
<string name="cordn_info_add_member_none">Nobody found by that name.</string>
<string name="cordn_info_admin_badge">Admin</string>
<string name="cordn_invitations_members_more">+%1$d more</string>
<string name="cordn_invitations_via">Through %1$s</string>
<string name="cordn_invitations_none">No invitations waiting.</string>
<string name="cordn_invitations_no_coordinators">You have no coordinator open. cordn invitations arrive through a coordinator, so there is nowhere to look yet.</string>
<string name="cordn_invitations_unreachable">%1$s did not answer</string>
<string name="cordn_invitations_skipped">An invitation was left for another device</string>
<string name="cordn_invitations_load_failed">Could not read invitations.</string>
<string name="cordn_link_request">Ask to join this group</string>
<string name="cordn_link_request_disclosure">This publishes a key package under your own account key and tells the coordinator you want into this group — whether or not anyone answers.</string>
<string name="cordn_link_request_sent">Asked. A member of the group has to add you; the invitation will show up under cordn invitations.</string>
<string name="cordn_link_request_failed">Could not ask to join.</string>
<string name="cordn_requests_title">Requests to join</string>
<string name="cordn_requests_check">Check for requests</string>
<string name="cordn_requests_none">Nobody is waiting to join.</string>
<string name="cordn_requests_accept">Add</string>
<string name="cordn_requests_decline">Dismiss</string>
<string name="cordn_requests_admin_only">Only an admin can accept. In a group with no admins, that is everyone.</string>
<string name="cordn_requests_failed">Could not read the requests.</string>
<string name="cordn_link_scan">Scan</string>
<string name="cordn_share_title">Share this group</string>
<string name="cordn_share_explainer">Anyone with this link can ask to join. An admin still has to let them in.</string>
<string name="cordn_share_copy">Copy link</string>
<string name="cordn_share_copied">Copied</string>
<string name="cordn_coordinators_explainer">A coordinator orders one group\'s messages and is the only thing that can. Losing it loses the ordering, and a second one does not mirror the first — so these are named things you choose, not interchangeable relays.</string>
<string name="cordn_coordinators_none">No coordinators yet.</string>
<string name="cordn_coordinators_add_action">Add</string>
<string name="cordn_coordinators_add_failed">Could not open that coordinator.</string>
<string name="cordn_coordinators_label">A name for it (yours, optional)</string>
<string name="cordn_coordinators_relays_label">Answers on</string>
<string name="cordn_coordinators_key">Coordinator key</string>
<string name="cordn_coordinators_copy_key">Copy the coordinator\'s npub</string>
<string name="cordn_coordinators_more">More actions</string>
<string name="cordn_coordinators_rename">Rename</string>
<string name="cordn_coordinators_label_note">This name is yours and stays on this device. A coordinator cannot prove a name, so it is never asked for one.</string>
<string name="cordn_coordinators_identify">Ask who it is</string>
<string name="cordn_coordinators_server">Says it is: %1$s</string>
<string name="cordn_coordinators_server_silent">Answered, but named nothing.</string>
<string name="cordn_coordinators_server_claim">A coordinator\'s name and version are strings it chose. Its public key is the only thing that identifies it.</string>
<string name="cordn_coordinators_health_ok">Answering.</string>
<string name="cordn_coordinators_health_retrying">A call failed; retrying.</string>
<string name="cordn_coordinators_health_down">Not answering — %1$d calls in a row have failed. Groups on it are not syncing.</string>
<string name="cordn_coordinators_health_unknown">Nothing asked of it yet.</string>
<string name="cordn_coordinators_remove">Remove</string>
<string name="cordn_coordinators_purge">Purge</string>
<string name="cordn_coordinators_purge_title">Purge this coordinator?</string>
<string name="cordn_coordinators_purge_body">Removing keeps the groups on this device, so adding the coordinator back brings them with it. Purging deletes them: the group keys, the read positions and your key packages all go, and those groups can only be re-entered by a fresh invitation.\n\nThe coordinator is not told and keeps whatever it already had.</string>
<string name="cordn_keypackages_explainer">Others can only add you to a cordn group by taking a key package you published to that coordinator. cordn has no other place to keep one, so with none published nobody can invite you — and nothing tells them why.</string>
<plurals name="cordn_create_unreachable_count">
<item quantity="one">%1$d person cannot be reached by any coordinator you use</item>
<item quantity="other">%1$d people cannot be reached by any coordinator you use</item>
</plurals>
<plurals name="cordn_groups_count">
<item quantity="one">%1$d group</item>
<item quantity="other">%1$d groups</item>
</plurals>
<plurals name="cordn_create_reach_count">
<item quantity="one">Reachable on %1$d coordinator</item>
<item quantity="other">Reachable on %1$d coordinators</item>
</plurals>
<plurals name="cordn_create_admin_count">
<item quantity="one">%1$d person can add and remove</item>
<item quantity="other">%1$d people can add and remove</item>
</plurals>
<plurals name="cordn_create_action_invite">
<item quantity="one">Create and invite %1$d person</item>
<item quantity="other">Create and invite %1$d people</item>
</plurals>
<plurals name="cordn_member_count">
<item quantity="one">%1$d member</item>
<item quantity="other">%1$d members</item>
</plurals>
<plurals name="cordn_keypackages_summary">
<item quantity="one">%1$d single-use package available.</item>
<item quantity="other">%1$d single-use packages available.</item>
</plurals>
<string name="cordn_keypackages_last_resort_yes">A last-resort package is published, so an invitation can still be made once the single-use ones run out.</string>
<string name="cordn_keypackages_last_resort_no">No last-resort package. Once the single-use ones run out, invitations fail.</string>
<plurals name="cordn_keypackages_orphans">
<item quantity="one">%1$d package this device cannot open</item>
<item quantity="other">%1$d packages this device cannot open</item>
</plurals>
<string name="cordn_keypackages_orphans_body">The coordinator will hand these to anyone inviting you, but the private half is not on this device — it belongs to another install. If that install is gone, an invitation using one of these produces a welcome nobody can ever open.</string>
<string name="cordn_keypackages_withdraw_orphans">Withdraw those</string>
<string name="cordn_keypackages_disclosure">Publishing signs a record under your own account key. That coordinator then knows this account exists and is invitable — permanently, and whether or not anyone invites you.</string>
<string name="cordn_keypackages_publish">Publish one</string>
<string name="cordn_keypackages_publish_last_resort">Publish last-resort</string>
<string name="cordn_keypackages_withdraw_all">Withdraw all</string>
<string name="cordn_keypackages_topup_note">Once you have published here, Amethyst quietly replaces packages as they are used up. It never publishes the first one for you.</string>
<string name="cordn_keypackages_failed">Could not read the key packages.</string>
<!-- Buzz calls its groups channels, and a Buzz relay is one workspace rather than a directory of groups. -->
<string name="buzz_channel_flag_forum">Forum channel</string>
<string name="buzz_channel_flag_forum_desc">Threaded posts instead of a chat timeline. This cannot be changed later.</string>
@@ -899,10 +655,6 @@
<!-- My Fitness: the signed-in user's own training summary. Android resource because
NavBarItemDef labels are R.string ids. -->
<plurals name="cordn_invitations_members">
<item quantity="one">%1$d person is already in this group</item>
<item quantity="other">%1$d people are already in this group</item>
</plurals>
<string name="private_message">Private Message</string>
<string name="pow_kind_chat_message">Chat message</string>
<string name="nest_notification_broadcasting">Nest — Live</string>
@@ -931,326 +683,4 @@
<string name="mute_button">Sound on. Click to mute</string>
<string name="play">Play</string>
<string name="pause">Pause</string>
<string name="backup_conflict_title">You changed your %1$s in another app</string>
<string name="backup_conflict_removed">Removed (%1$s)</string>
<string name="backup_conflict_added">Added (%1$s)</string>
<string name="backup_conflict_changed">Changed (%1$s)</string>
<string name="backup_conflict_private_cleared">You removed all private (encrypted) items.</string>
<string name="backup_conflict_private_changed">You also changed private (encrypted) items. They can\'t be compared here.</string>
<string name="backup_conflict_private_added">You added private (encrypted) items.</string>
<string name="backup_conflict_restore_mine">Restore saved version</string>
<string name="backup_review_saved">On this device</string>
<string name="backup_review_new">New version</string>
<string name="backup_review_not_set">Not set</string>
<string name="backup_conflict_count_removed">%1$s removed</string>
<string name="backup_conflict_count_added">%1$s added</string>
<string name="backup_conflict_count_changed">%1$s changed</string>
<plurals name="backup_card_follows_dropped">
<item quantity="one">You unfollowed %1$d person</item>
<item quantity="other">You unfollowed %1$d people</item>
</plurals>
<string name="backup_card_accept">Accept</string>
<string name="backup_review_title_updated">%1$s updated</string>
<string name="backup_review_title_cleared">%1$s cleared</string>
<string name="backup_review_changed_by_other_app">Changed in another app · %1$s</string>
<string name="backup_action_keep_count">Keep %1$s</string>
<string name="backup_action_restore_count">Restore %1$s</string>
<string name="backup_action_remute">Re-mute %1$s</string>
<string name="backup_action_rejoin">Rejoin %1$s</string>
<string name="backup_action_restore_profile">Restore profile</string>
<string name="backup_action_restore_relays">Restore relays</string>
<string name="backup_review_kept_count">%1$s kept</string>
<string name="backup_review_dropped_count">%1$s dropped</string>
<string name="backup_review_new_count">%1$s new</string>
<string name="backup_review_tab_dropped">Dropped · %1$s</string>
<string name="backup_review_tab_new">New · %1$s</string>
<string name="backup_review_tab_edited">Edited · %1$s</string>
<string name="backup_review_search_people">Search %1$s people</string>
<string name="backup_review_nothing_here">Nothing here</string>
<string name="backup_review_unmuted">%1$s unmuted</string>
<string name="backup_review_unmuted_explainer">These can reach your feeds again</string>
<string name="backup_review_newly_muted">%1$s newly muted</string>
<string name="backup_review_words_and_hashtags">Words &amp; hashtags</string>
<string name="backup_review_names_and_more">%1$s and %2$s more</string>
<string name="backup_review_private_wiped_title">You removed all private items</string>
<string name="backup_review_private_wiped_body">They\'re encrypted, so only you can see what they were. Restoring brings them all back.</string>
<string name="backup_review_private_added_title">You made items private</string>
<string name="backup_review_private_added_body">They\'re encrypted now, which is likely where the public items above went.</string>
<string name="backup_review_private_changed_title">You changed private items</string>
<string name="backup_review_private_changed_body">They\'re encrypted, so they can\'t be compared here.</string>
<string name="backup_review_gone">Gone</string>
<string name="backup_review_new_tag">New</string>
<string name="backup_review_dropped_tag">Dropped</string>
<string name="backup_review_demoted_tag">Moved</string>
<string name="backup_review_units_tag">Units</string>
<string name="backup_review_left_tag">Left</string>
<string name="backup_review_renamed_tag">Renamed</string>
<string name="backup_review_outbox">Outbox</string>
<string name="backup_review_outbox_explainer">where you publish</string>
<string name="backup_review_inbox">Inbox</string>
<string name="backup_review_inbox_explainer">where others reach you</string>
<string name="backup_review_key_replaced">Receiving key replaced</string>
<string name="backup_review_key_removed">Receiving key removed</string>
<string name="backup_review_key_warning">Nutzaps will be locked to this new key. If this device doesn\'t have it, you may not be able to redeem them here.</string>
<string name="backup_review_left">left</string>
<string name="backup_review_joined">joined</string>
<string name="backup_review_renamed">renamed</string>
<string name="backup_review_was_named">was “%1$s”</string>
<string name="backup_action_block_again">Block %1$s again</string>
<string name="backup_action_restore_feeds">Restore feeds</string>
<string name="backup_action_restore_topics">Restore topics</string>
<string name="backup_action_restore_places">Restore places</string>
<string name="backup_action_restore_providers">Restore providers</string>
<string name="backup_action_restore_wallet">Restore wallet</string>
<string name="backup_review_kept">kept</string>
<string name="backup_review_receiving_tag">Receiving</string>
<string name="backup_review_listed_tag">Listed</string>
<string name="backup_review_searching_tag">Searching</string>
<string name="backup_review_storing_tag">Storing</string>
<string name="backup_review_trusted_tag">Trusted</string>
<string name="backup_review_untrusted_tag">Untrusted</string>
<string name="backup_review_unblocked">%1$s unblocked</string>
<string name="backup_review_unblocked_explainer">Amethyst will connect to them again</string>
<string name="backup_review_no_longer_blocked">No longer blocked</string>
<string name="backup_review_newly_blocked">Newly blocked</string>
<string name="backup_review_still_blocked">Still blocked</string>
<string name="backup_review_allowed_tag">Allowed</string>
<string name="backup_review_moved_tag">Moved</string>
<string name="backup_review_feed">Feed</string>
<string name="backup_review_topics_gone">topics gone</string>
<string name="backup_review_places_gone">places gone</string>
<string name="backup_review_new_count_caption">new</string>
<string name="backup_review_hashtags_you_follow">Hashtags you follow</string>
<string name="backup_review_places_you_follow">Places you follow</string>
<string name="backup_review_services_changed">%1$s of %2$s services changed</string>
<string name="backup_review_services_changed_explainer">They now come from someone else, or no one</string>
<string name="backup_review_service">Service</string>
<string name="backup_review_service_rank">Rank</string>
<string name="backup_review_service_followers">Followers</string>
<string name="backup_review_service_topics">Topics</string>
<string name="backup_review_trust_warning">Ranks, follower counts and spam filtering you see come from these providers.</string>
<plurals name="backup_review_payment_targets_gone">
<item quantity="one">%1$d way to pay you removed</item>
<item quantity="other">%1$d ways to pay you removed</item>
</plurals>
<plurals name="backup_review_offers_gone">
<item quantity="one">%1$d offer removed</item>
<item quantity="other">%1$d offers removed</item>
</plurals>
<string name="backup_review_payments_explainer">People paying you only see what\'s in this list.</string>
<string name="backup_review_offer">BOLT12 offer</string>
<string name="backup_review_encrypted_cleared">You emptied it</string>
<string name="backup_review_encrypted_added">You created it</string>
<string name="backup_review_encrypted_changed">You changed it</string>
<string name="backup_review_wallet_body">Your wallet\'s mints and keys are encrypted to you, so they can\'t be compared item by item here. Restoring puts back exactly what this device saved.</string>
<string name="backup_review_concord_body">Your Concord communities are encrypted to you, so they can\'t be compared here. Restoring puts back exactly what this device saved.</string>
<plurals name="backup_card_mutes_visible">
<item quantity="one">You unmuted %1$d thing</item>
<item quantity="other">You unmuted %1$d things</item>
</plurals>
<plurals name="backup_card_profile_lost_fields">
<item quantity="one">You removed %1$d profile field</item>
<item quantity="other">You removed %1$d profile fields</item>
</plurals>
<plurals name="backup_card_nip65_lost">
<item quantity="one">You removed %1$d public relay</item>
<item quantity="other">You removed %1$d public relays</item>
</plurals>
<plurals name="backup_card_dm_relays_lost">
<item quantity="one">You removed %1$d DM relay</item>
<item quantity="other">You removed %1$d DM relays</item>
</plurals>
<plurals name="backup_card_key_package_relays_lost">
<item quantity="one">You removed %1$d group-chat key relay</item>
<item quantity="other">You removed %1$d group-chat key relays</item>
</plurals>
<plurals name="backup_card_search_relays_lost">
<item quantity="one">You removed %1$d search relay</item>
<item quantity="other">You removed %1$d search relays</item>
</plurals>
<plurals name="backup_card_indexer_relays_lost">
<item quantity="one">You removed %1$d indexer relay</item>
<item quantity="other">You removed %1$d indexer relays</item>
</plurals>
<plurals name="backup_card_relay_feeds_lost">
<item quantity="one">You removed %1$d relay feed</item>
<item quantity="other">You removed %1$d relay feeds</item>
</plurals>
<plurals name="backup_card_private_outbox_lost">
<item quantity="one">You removed %1$d private relay</item>
<item quantity="other">You removed %1$d private relays</item>
</plurals>
<plurals name="backup_card_trusted_lost">
<item quantity="one">You stopped trusting %1$d relay</item>
<item quantity="other">You stopped trusting %1$d relays</item>
</plurals>
<plurals name="backup_card_unblocked">
<item quantity="one">You unblocked %1$d relay</item>
<item quantity="other">You unblocked %1$d relays</item>
</plurals>
<plurals name="backup_card_leave_chats">
<item quantity="one">You left %1$d public chat</item>
<item quantity="other">You left %1$d public chats</item>
</plurals>
<plurals name="backup_card_leave_communities">
<item quantity="one">You left %1$d community</item>
<item quantity="other">You left %1$d communities</item>
</plurals>
<plurals name="backup_card_leave_rooms">
<item quantity="one">You left %1$d chat room</item>
<item quantity="other">You left %1$d chat rooms</item>
</plurals>
<plurals name="backup_card_leave_groups">
<item quantity="one">You left %1$d group</item>
<item quantity="other">You left %1$d groups</item>
</plurals>
<plurals name="backup_card_feeds_removed">
<item quantity="one">You removed %1$d favorite feed</item>
<item quantity="other">You removed %1$d favorite feeds</item>
</plurals>
<plurals name="backup_card_hashtags_unfollowed">
<item quantity="one">You unfollowed %1$d hashtag</item>
<item quantity="other">You unfollowed %1$d hashtags</item>
</plurals>
<plurals name="backup_card_places_unfollowed">
<item quantity="one">You unfollowed %1$d place</item>
<item quantity="other">You unfollowed %1$d places</item>
</plurals>
<plurals name="backup_card_trust_services_changed">
<item quantity="one">You changed the provider of %1$d trust service</item>
<item quantity="other">You changed the provider of %1$d trust services</item>
</plurals>
<plurals name="backup_card_nutzap_mints_removed">
<item quantity="one">You removed %1$d nutzap mint</item>
<item quantity="other">You removed %1$d nutzap mints</item>
</plurals>
<plurals name="backup_card_payment_targets_removed">
<item quantity="one">You removed %1$d way to get paid</item>
<item quantity="other">You removed %1$d ways to get paid</item>
</plurals>
<plurals name="backup_card_offers_removed">
<item quantity="one">You removed %1$d BOLT12 offer</item>
<item quantity="other">You removed %1$d BOLT12 offers</item>
</plurals>
<plurals name="backup_card_and_added">
<item quantity="one">%1$s and added %2$d</item>
<item quantity="other">%1$s and added %2$d</item>
</plurals>
<plurals name="backup_card_and_followed">
<item quantity="one">%1$s and followed %2$d</item>
<item quantity="other">%1$s and followed %2$d</item>
</plurals>
<plurals name="backup_card_and_muted">
<item quantity="one">%1$s and muted %2$d</item>
<item quantity="other">%1$s and muted %2$d</item>
</plurals>
<plurals name="backup_card_and_joined">
<item quantity="one">%1$s and joined %2$d</item>
<item quantity="other">%1$s and joined %2$d</item>
</plurals>
<plurals name="backup_card_and_blocked">
<item quantity="one">%1$s and blocked %2$d</item>
<item quantity="other">%1$s and blocked %2$d</item>
</plurals>
<string name="backup_card_app_settings">Your Amethyst synced app settings changed in another app that is not Amethyst. This can create problems.</string>
<string name="backup_card_nutzap_key_replaced">You replaced your nutzap key</string>
<string name="backup_card_nutzap_key_removed">You removed your nutzap key</string>
<string name="backup_card_wallet_emptied">You emptied your Cashu wallet</string>
<string name="backup_card_wallet_rewritten">You changed your Cashu wallet</string>
<string name="backup_card_concord_emptied">You emptied your Concord list</string>
<string name="backup_card_concord_rewritten">You changed your Concord list</string>
<string name="backup_conflict_keep_new">Use new version</string>
<string name="backup_conflict_relay_read_only">read only</string>
<string name="backup_conflict_relay_write_only">write only</string>
<string name="backup_conflict_relay_read_write">read and write</string>
<string name="backup_type_profile">profile</string>
<string name="backup_type_profile_explainer">Your name, picture, bio and payment addresses, as everyone sees them.</string>
<string name="backup_type_follow_list">follow list</string>
<string name="backup_type_follow_list_explainer">Decides whose posts appear in your home feed.</string>
<string name="backup_type_mute_list">mute list</string>
<string name="backup_type_mute_list_explainer">Hides people, words, hashtags and threads from everything you see.</string>
<string name="backup_type_outbox_inbox_relays">public relay list</string>
<string name="backup_type_outbox_inbox_relays_explainer">Tells everyone where to find your posts and where to send you replies and mentions.</string>
<string name="backup_type_dm_relays">DM relay list</string>
<string name="backup_type_dm_relays_explainer">Where others deliver your private messages.</string>
<string name="backup_type_key_package_relays">group-chat key relays</string>
<string name="backup_type_key_package_relays_explainer">Where others find the keys to add you to encrypted group chats.</string>
<string name="backup_type_search_relays">search relay list</string>
<string name="backup_type_search_relays_explainer">The relays used when you search.</string>
<string name="backup_type_indexer_relays">indexer relay list</string>
<string name="backup_type_indexer_relays_explainer">The relays used to find other people\'s profiles and relay lists.</string>
<string name="backup_type_relay_feeds">favorite relay feeds</string>
<string name="backup_type_relay_feeds_explainer">The relays you browse as feeds.</string>
<string name="backup_type_blocked_relays">blocked relay list</string>
<string name="backup_type_blocked_relays_explainer">Relays this app never connects to.</string>
<string name="backup_type_trusted_relays">trusted relay list</string>
<string name="backup_type_trusted_relays_explainer">Relays you trust to connect to without asking.</string>
<string name="backup_type_private_outbox_relays">private relay list</string>
<string name="backup_type_private_outbox_relays_explainer">Where your drafts and private data are stored.</string>
<string name="backup_type_app_settings">synced app settings</string>
<string name="backup_type_app_settings_explainer">Amethyst settings synced across your devices. They are encrypted, so only their presence can be compared.</string>
<string name="backup_type_public_chats">public chat list</string>
<string name="backup_type_public_chats_explainer">The public chats you joined.</string>
<string name="backup_type_communities">community list</string>
<string name="backup_type_communities_explainer">The communities you joined.</string>
<string name="backup_type_hashtags">followed hashtags</string>
<string name="backup_type_hashtags_explainer">Hashtags whose posts appear in your feeds.</string>
<string name="backup_type_geohashes">followed locations</string>
<string name="backup_type_geohashes_explainer">Locations whose posts appear in your feeds.</string>
<string name="backup_type_favorite_algo_feeds">favorite feeds list</string>
<string name="backup_type_favorite_algo_feeds_explainer">The algorithmic feeds you saved.</string>
<string name="backup_type_ephemeral_chats">ephemeral chat list</string>
<string name="backup_type_ephemeral_chats_explainer">The ephemeral chat rooms you joined.</string>
<string name="backup_type_relay_groups">group list</string>
<string name="backup_type_relay_groups_explainer">The relay-based groups you joined.</string>
<string name="backup_type_concord_communities">Concord community list</string>
<string name="backup_type_concord_communities_explainer">The Concord communities you joined. They are encrypted, so only their presence can be compared.</string>
<string name="backup_type_trust_providers">trust provider list</string>
<string name="backup_type_trust_providers_explainer">The services you trust to rank and verify people for you.</string>
<string name="backup_type_cashu_wallet">Cashu wallet</string>
<string name="backup_type_cashu_wallet_explainer">Your ecash wallet settings and mints. They are encrypted, so only their presence can be compared.</string>
<string name="backup_type_nutzap_info">nutzap receiving settings</string>
<string name="backup_type_nutzap_info_explainer">The mints and key others use to send you nutzaps.</string>
<string name="backup_type_payment_targets">payment addresses</string>
<string name="backup_type_payment_targets_explainer">The ways other people can pay you.</string>
<string name="backup_type_bolt12_offers">BOLT12 offers</string>
<string name="backup_type_bolt12_offers_explainer">The lightning offers others use to pay you.</string>
<string name="backup_type_other">list</string>
<string name="backup_type_other_explainer">A list saved on this device.</string>
<string name="backup_entry_profile_field">Profile fields</string>
<string name="backup_entry_person">People</string>
<string name="backup_entry_hashtag">Hashtags</string>
<string name="backup_entry_word">Words</string>
<string name="backup_entry_thread">Threads</string>
<string name="backup_entry_public_chat">Public chats</string>
<string name="backup_entry_community">Communities</string>
<string name="backup_entry_algo_feed">Feeds</string>
<string name="backup_entry_location">Locations</string>
<string name="backup_entry_relay">Relays</string>
<string name="backup_entry_relay_group">Groups</string>
<string name="backup_entry_chat_room">Chat rooms</string>
<string name="backup_entry_trust_provider">Trust providers</string>
<string name="backup_entry_mint">Mints</string>
<string name="backup_entry_nutzap_key">Nutzap keys</string>
<string name="backup_entry_payment_target">Payment addresses</string>
<string name="backup_entry_offer">Offers</string>
<string name="backup_entry_identity_claim">Linked identities</string>
<string name="backup_entry_other">Other entries</string>
<string name="backup_profile_field_name">Username</string>
<string name="backup_profile_field_display_name">Display name</string>
<string name="backup_profile_field_about">About</string>
<string name="backup_profile_field_picture">Picture</string>
<string name="backup_profile_field_banner">Banner</string>
<string name="backup_profile_field_website">Website</string>
<string name="backup_profile_field_nip05">Nostr address</string>
<string name="backup_profile_field_lud16">Lightning address</string>
<string name="backup_profile_field_lud06">LNURL</string>
<string name="backup_profile_field_pronouns">Pronouns</string>
<string name="backup_profile_field_birthday">Birthday</string>
<string name="backup_profile_field_clink_offer">CLINK offer</string>
<string name="backup_profile_field_bot">Bot flag</string>
<plurals name="cordn_info_admins_kept">
<item quantity="one">The group\'s %1$d admin stays as it is.</item>
<item quantity="other">The group\'s %1$d admins stay as they are.</item>
</plurals>
</resources>
+4 -1
View File
@@ -58,7 +58,10 @@ is specific to the UI module.
`tools/icon-font/build_icon_font.py` read `icons/` and write
`composeResources/font/` in this module (see root `.claude/CLAUDE.md`, "Icons").
- Translations: `crowdin.yml` and `tools/strings-migrate/` target
`commonsUI/src/commonMain/composeResources/`.
`commonsUI/src/commonMain/composeResources/`. This is where **every new
user-visible string** goes, Android-only screens included; the app's own
`res/values/strings.xml` holds only the synchronous-platform tier (see root
`.claude/CLAUDE.md`, "Strings").
- CI: `.github/workflows/build.yml` runs `:commonsUI:jvmTest`,
`:commonsUI:verifyKmpPurity` and the iOS compile/test tasks next to the
`:commons` ones.
@@ -5286,4 +5286,322 @@
<string name="cyberspace_bag_unsigned">हस्ताक्षररहित। इसलिए यह कर्ता केवल एक कथन है</string>
<string name="cyberspace_bag_unknown_kind">प्रकार %1$d का वस्तु। जिसे यह ग्राहक चित्रित नहीं करता</string>
<string name="cyberspace_bag_opaque">%1$d अष्टकों का अमुक जिसे यह ग्राहक पढता नहीं</string>
<string name="backup_action_block_again">पुनःबाधित करें %1$s को</string>
<string name="backup_action_keep_count">रखें %1$s</string>
<string name="backup_action_rejoin">पुनःजुडें %1$s</string>
<string name="backup_action_remute">पुनःमौन %1$s</string>
<string name="backup_action_restore_count">पुनःस्थापन %1$s</string>
<string name="backup_action_restore_feeds">सूचनावलियाँ पुनःस्थापन</string>
<string name="backup_action_restore_places">स्थल पुनःस्थापन</string>
<string name="backup_action_restore_profile">परिचय पुनःस्थापध</string>
<string name="backup_action_restore_providers">प्रदाता पुनःस्थापन</string>
<string name="backup_action_restore_relays">पुनःप्रसारक पुनःस्थापन</string>
<string name="backup_action_restore_topics">विषय पुनःस्थापन</string>
<string name="backup_action_restore_wallet">धनकोष पुनःस्थापन</string>
<string name="backup_card_accept">स्वीकार</string>
<plurals name="backup_card_and_added">
<item quantity="one">%1$s तथा %2$d जोडा</item>
<item quantity="other">%1$s तथा %2$d जोडे</item>
</plurals>
<plurals name="backup_card_and_blocked">
<item quantity="one">%1$s तथा %2$d बाधित किया</item>
<item quantity="other">%1$s तथा %2$d को बाधित किए</item>
</plurals>
<plurals name="backup_card_and_followed">
<item quantity="one">%1$s तथा %2$d का अनुगमन किया</item>
<item quantity="other">%1$s तथा %2$d का अनुगमन किए</item>
</plurals>
<plurals name="backup_card_and_joined">
<item quantity="one">%1$s तथा %2$d से जुडे</item>
<item quantity="other">%1$s तथा %2$d से जुडे</item>
</plurals>
<plurals name="backup_card_and_muted">
<item quantity="one">%1$s तथा %2$d को मौनकृत किया</item>
<item quantity="other">%1$s तथा %2$d को मौनकृत किए</item>
</plurals>
<string name="backup_card_app_settings">आपके अमेथिस्ट समचरणीकृत क्रमक स्थापना विकल्पों को परिवर्तित किया गया एक अन्य क्रमक में और वह अमेथिस्ट नहीं था। इससे समस्याएँ उत्पन्न हो सकते हैं।</string>
<string name="backup_card_concord_emptied">आपने अपनी कोंकोर्ड सूची रिक्त की</string>
<string name="backup_card_concord_rewritten">आपने अपनी कोंकोर्ड सूची का परिवर्तन किया</string>
<plurals name="backup_card_dm_relays_lost">
<item quantity="one">आपने %1$d सीधासन्देश पुनःप्रसारक हटाया</item>
<item quantity="other">आपने %1$d सीधासन्देश पुनःप्रसारकों को हटाए</item>
</plurals>
<plurals name="backup_card_feeds_removed">
<item quantity="one">आपने %1$d इष्ट सूचनावली हटाया</item>
<item quantity="other">आपने %1$d इष्ट सूचनावलियों को हटाए</item>
</plurals>
<plurals name="backup_card_follows_dropped">
<item quantity="one">आपने %1$d व्यक्ति का अनुगमन हटाया</item>
<item quantity="other">आपने %1$d व्यक्तियों का अनुगमन हटाए</item>
</plurals>
<plurals name="backup_card_hashtags_unfollowed">
<item quantity="one">आपने %1$d विषयसूचक का अनुगमन हटाया</item>
<item quantity="other">आपने %1$d विषयसूचकों का अनुगमन हटाए</item>
</plurals>
<plurals name="backup_card_indexer_relays_lost">
<item quantity="one">आपने %1$d अनुक्रमणीकार पुनःप्रसारक हटाया</item>
<item quantity="other">आपने %1$d अनुक्रमणीकार पुनःप्रसारकों को हटाए</item>
</plurals>
<plurals name="backup_card_key_package_relays_lost">
<item quantity="one">आपने %1$d समूहचर्चा कुंचिका पुनःप्रसारक हटाया</item>
<item quantity="other">आपने %1$d समूहचर्चा कुंचिका पुनःप्रसारकों को हटाए</item>
</plurals>
<plurals name="backup_card_leave_chats">
<item quantity="one">आपने %1$d सार्वजनिक चर्चा छोड दिया</item>
<item quantity="other">आपने %1$d सार्वजनिक चर्चाओं को छोड दिए</item>
</plurals>
<plurals name="backup_card_leave_communities">
<item quantity="one">आपने %1$d समुदाय छोड दिया</item>
<item quantity="other">आपने %1$d समुदायों को छोड दिए</item>
</plurals>
<plurals name="backup_card_leave_groups">
<item quantity="one">आपने %1$d समूह छोड दिया</item>
<item quantity="other">आपने %1$d समूहों को छोड दिए</item>
</plurals>
<plurals name="backup_card_leave_rooms">
<item quantity="one">आपने %1$d चर्चाशाला छोड दिया</item>
<item quantity="other">आपने %1$d चर्चाशालाओं को छोड दिए</item>
</plurals>
<plurals name="backup_card_mutes_visible">
<item quantity="one">आपने %1$d की मौनावस्था हटाया</item>
<item quantity="other">आपने %1$d की मौनावस्था हटाए</item>
</plurals>
<plurals name="backup_card_nip65_lost">
<item quantity="one">आपने %1$d सार्वजनिक पुनःप्रसारक हटाया</item>
<item quantity="other">आपने %1$d सार्वजनिक पुनःप्रसारकों को हटाए</item>
</plurals>
<string name="backup_card_nutzap_key_removed">आपने अपनी नटज्साप कुंचिका को हटाया</string>
<string name="backup_card_nutzap_key_replaced">आपने अपनी नटज्साप कुंचिका का प्रतिस्थापन किया</string>
<plurals name="backup_card_nutzap_mints_removed">
<item quantity="one">आपने %1$d नटज्साप टकसाल हटाया</item>
<item quantity="other">आपने %1$d नटज्साप टकसालों को हटाए</item>
</plurals>
<plurals name="backup_card_offers_removed">
<item quantity="one">आपने %1$d बोल्ट॰बारह प्रस्ताव हटाया</item>
<item quantity="other">आपने %1$d बोल्ट॰बारह प्रस्तावों को हटाए</item>
</plurals>
<plurals name="backup_card_payment_targets_removed">
<item quantity="one">आपने %1$d भुगतान प्राप्त करने की विधि हटाया</item>
<item quantity="other">आपने %1$d भुगतान प्राप्त करने के विधियों को हटाए</item>
</plurals>
<plurals name="backup_card_places_unfollowed">
<item quantity="one">आपने %1$d स्थल का अनुगमन हटाया</item>
<item quantity="other">आपने %1$d स्थलों का अनुगमन हटाए</item>
</plurals>
<plurals name="backup_card_private_outbox_lost">
<item quantity="one">आपने %1$d निजी पुनःप्रसारक हटाया</item>
<item quantity="other">आपने %1$d निजी पुनःप्रसारकों को हटाए</item>
</plurals>
<plurals name="backup_card_profile_lost_fields">
<item quantity="one">आपने %1$d परिचय प्रपत्रस्थान हटाया</item>
<item quantity="other">आपने %1$d परिचय प्रपत्रस्थान हटाए</item>
</plurals>
<plurals name="backup_card_relay_feeds_lost">
<item quantity="one">आपने %1$d पुनःप्रसारक सूचनावली हटाया</item>
<item quantity="other">आपने %1$d पुनःप्रसारक सूचनावलियाँ हटाए</item>
</plurals>
<plurals name="backup_card_search_relays_lost">
<item quantity="one">आपने %1$d खोज पुनःप्रसारक हटाया</item>
<item quantity="other">आपने %1$d खोज पुनःप्रसारकों को हटाए</item>
</plurals>
<plurals name="backup_card_trust_services_changed">
<item quantity="one">आपने %1$d विश्वाससेवा प्रदाता परिवर्तित किया</item>
<item quantity="other">आपने %1$d विश्वाससेवाओं के प्रदाता को परिवर्तित किया</item>
</plurals>
<plurals name="backup_card_trusted_lost">
<item quantity="one">आपने %1$d पुनःप्रसारक पर विश्वास करना रोक दिया</item>
<item quantity="other">आपने %1$d पुनःप्रसारकों पर विश्वास करना रोक दिए</item>
</plurals>
<plurals name="backup_card_unblocked">
<item quantity="one">आपने %1$d पुनःप्रसारक अबाधित किया</item>
<item quantity="other">आपने %1$d पुनःप्रसारकों को अबाधित किए</item>
</plurals>
<string name="backup_card_wallet_emptied">आपने अपना काशयू धनकोष रिक्त किया</string>
<string name="backup_card_wallet_rewritten">आपने अपने काशयू धनकोष को परिवर्तित किया</string>
<string name="backup_conflict_added">जोडे गए (%1$s)</string>
<string name="backup_conflict_changed">परिवर्तित (%1$s)</string>
<string name="backup_conflict_count_added">%1$s जोडे गए</string>
<string name="backup_conflict_count_changed">%1$s परिवर्तित</string>
<string name="backup_conflict_count_removed">%1$s हटाए गए</string>
<string name="backup_conflict_keep_new">नया संस्करण का उपयोग करें</string>
<string name="backup_conflict_private_added">आपने निजी (रहस्यीकृत) वस्तुओं को जोडा।</string>
<string name="backup_conflict_private_changed">आपने निजी (रहस्यीकृत) वस्तुओं का परिवर्तन भी किया। उनकी तुलना यहाँ नहीं की जा सकती।</string>
<string name="backup_conflict_private_cleared">आपने सभी निजी (रहस्यीकृत) वस्तुओं को हटा दिया।</string>
<string name="backup_conflict_relay_read_only">पठनेवशक्य</string>
<string name="backup_conflict_relay_read_write">पठन तथा लेखन</string>
<string name="backup_conflict_relay_write_only">लेखनेवशक्य</string>
<string name="backup_conflict_removed">हटाए गए (%1$s)</string>
<string name="backup_conflict_restore_mine">सुरक्षित संस्करण पुनःस्थापन</string>
<string name="backup_conflict_title">आपने अपना %1$s अन्य क्रमक में परिवर्तित किया</string>
<string name="backup_entry_algo_feed">सूचनावलियाँ</string>
<string name="backup_entry_chat_room">चर्चा शालाएँ</string>
<string name="backup_entry_community">समुदाय</string>
<string name="backup_entry_hashtag">विषयसूचक</string>
<string name="backup_entry_identity_claim">सम्बन्धित व्यक्तिगण</string>
<string name="backup_entry_location">स्थान</string>
<string name="backup_entry_mint">टकसाल</string>
<string name="backup_entry_nutzap_key">नटज्साप कुंचिकाएँ</string>
<string name="backup_entry_offer">प्रस्ताव</string>
<string name="backup_entry_other">अन्य प्रविष्टियाँ</string>
<string name="backup_entry_payment_target">भुगतान पते</string>
<string name="backup_entry_person">लोग</string>
<string name="backup_entry_profile_field">परिचय प्रपत्रस्थान</string>
<string name="backup_entry_public_chat">सार्वजनिक चर्चाएँ</string>
<string name="backup_entry_relay">पुनःप्रसारक</string>
<string name="backup_entry_relay_group">समूह</string>
<string name="backup_entry_thread">सूत्र</string>
<string name="backup_entry_trust_provider">विश्वाससेवा प्रदाता</string>
<string name="backup_entry_word">शब्द</string>
<string name="backup_profile_field_about">जानकारी</string>
<string name="backup_profile_field_banner">ध्वज</string>
<string name="backup_profile_field_birthday">जन्मदिन</string>
<string name="backup_profile_field_bot">क्रमचर चिह्न</string>
<string name="backup_profile_field_clink_offer">क्लिंक॰ प्रस्ताव</string>
<string name="backup_profile_field_display_name">प्रदर्शन नाम</string>
<string name="backup_profile_field_lud06">लैटनिंग जालपता</string>
<string name="backup_profile_field_lud16">लैटनिंग पता</string>
<string name="backup_profile_field_name">प्रयोक्तानाम</string>
<string name="backup_profile_field_nip05">नोस्टर पता</string>
<string name="backup_profile_field_picture">चित्र</string>
<string name="backup_profile_field_pronouns">सर्वनाम</string>
<string name="backup_profile_field_website">जालस्थान</string>
<string name="backup_review_allowed_tag">अनुमत</string>
<string name="backup_review_changed_by_other_app">अन्य क्रमक में परिवर्तित। %1$s</string>
<string name="backup_review_concord_body">आपके कोंकोर्ड समुदाय आपके लिए रहस्यीकृत हैं। इसलिए उनकी तुलना यहाँ नहीं की जा सकती। पुनःस्थापन उन्हीं को लौट डालेगा जिनका अभिलेखन इस यन्त्र ने किया।</string>
<string name="backup_review_demoted_tag">स्थानान्तरित</string>
<string name="backup_review_dropped_count">%1$s परित्यक्त</string>
<string name="backup_review_dropped_tag">परित्यक्त</string>
<string name="backup_review_encrypted_added">आपने उसे बनाया</string>
<string name="backup_review_encrypted_changed">आपने उसे परिवर्तित किया</string>
<string name="backup_review_encrypted_cleared">आपने उसे रिक्त किया</string>
<string name="backup_review_feed">सूचनावली</string>
<string name="backup_review_gone">चला गया</string>
<string name="backup_review_hashtags_you_follow">विषयसूचक जिनका आप अनुगमन करते हैं</string>
<string name="backup_review_inbox">आगतपेटिका</string>
<string name="backup_review_inbox_explainer">जहाँ अन्य आप तक अभिगमन करते हैं</string>
<string name="backup_review_joined">जुड गए</string>
<string name="backup_review_kept">रखे गए</string>
<string name="backup_review_kept_count">%1$s रखे गए</string>
<string name="backup_review_key_removed">ग्राहक कुंचिका हटाया गया</string>
<string name="backup_review_key_replaced">ग्राहक कुंचिका प्रतिस्थापित</string>
<string name="backup_review_key_warning">नटज्साप आगे से इस कुंचिका से बद्ध होंगे। यदि इस यन्त्र में वह नहीं है तो आप सम्भाव्यतः उनको यहाँ नहीं चुका सकते।</string>
<string name="backup_review_left">शेष</string>
<string name="backup_review_left_tag">शेष</string>
<string name="backup_review_listed_tag">सूचीबद्ध</string>
<string name="backup_review_moved_tag">स्थानान्तरित</string>
<string name="backup_review_names_and_more">%1$s तथा %2$s अधिक</string>
<string name="backup_review_new">नया संस्करण</string>
<string name="backup_review_new_count">%1$s नए</string>
<string name="backup_review_new_count_caption">नव्य</string>
<string name="backup_review_new_tag">नव्य</string>
<string name="backup_review_newly_blocked">नव्यतः बाधित</string>
<string name="backup_review_newly_muted">%1$s नए मौनकृत</string>
<string name="backup_review_no_longer_blocked">अब से बाधित नहीं</string>
<string name="backup_review_not_set">स्थापित नहीं</string>
<string name="backup_review_nothing_here">यहाँ कुछ नहीं</string>
<string name="backup_review_offer">बोल्ट॰बारह प्रस्ताव</string>
<plurals name="backup_review_offers_gone">
<item quantity="one">%1$d प्रस्ताव हटाया गया</item>
<item quantity="other">%1$d प्रस्ताव हटाए गए</item>
</plurals>
<string name="backup_review_outbox">निर्गतपेटिका</string>
<string name="backup_review_outbox_explainer">आप जहाँ प्रकाशित करते हैं</string>
<plurals name="backup_review_payment_targets_gone">
<item quantity="one">आपको भुगतान करने की %1$d विधि हटाया गया</item>
<item quantity="other">आपको भुगतान करने की %1$d विधियाँ हटाए गए</item>
</plurals>
<string name="backup_review_payments_explainer">लोग जो आपको भुगतान करते हैं उसी को देखते हैं जो इस सूची में है।</string>
<string name="backup_review_places_gone">स्थल हटाए गए</string>
<string name="backup_review_places_you_follow">स्थल जिनका आप अनुगमन करते हैं</string>
<string name="backup_review_private_added_body">वे अब रहस्यीकृत हैं। जो कि सम्भाव्यतः ऊपर के सार्वजनिक वस्तुओं का गन्तव्य था।</string>
<string name="backup_review_private_added_title">आपने वस्तुओं को निजी किया</string>
<string name="backup_review_private_changed_body">वे रहस्यीकृत हैं। इसलिए उनकी तुलना यहाँ नहीं की जा सकती।</string>
<string name="backup_review_private_changed_title">आपने निजी वस्तुओं का परिवर्तन किया</string>
<string name="backup_review_private_wiped_body">वे रहस्यीकृत हैं। इसलिए केवल आप देख सकते हैं कि वे क्या थे। पुनःस्थापन से सब लौटके आ जाएँगे।</string>
<string name="backup_review_private_wiped_title">आपने सभी निजी वस्तुओं को हटा दिया</string>
<string name="backup_review_receiving_tag">ग्रहण मध्य</string>
<string name="backup_review_renamed">पुनःनामकृत</string>
<string name="backup_review_renamed_tag">पुनःनामकृत</string>
<string name="backup_review_saved">इस यन्त्र पर</string>
<string name="backup_review_search_people">%1$s लोग खोजें</string>
<string name="backup_review_searching_tag">खोज मध्य</string>
<string name="backup_review_service">सेवा</string>
<string name="backup_review_service_followers">अनुचर</string>
<string name="backup_review_service_rank">श्रेणी</string>
<string name="backup_review_service_topics">विषयसूची</string>
<string name="backup_review_services_changed">%2$s में से %1$s सेवाएँ परिवर्तित</string>
<string name="backup_review_services_changed_explainer">वे अब अन्य व्यक्ति से आते है। अथवा किसी से नहीं</string>
<string name="backup_review_still_blocked">अभी भी बाधित</string>
<string name="backup_review_storing_tag">अभिलेखन मध्य</string>
<string name="backup_review_tab_dropped">परित्यक्त। %1$s</string>
<string name="backup_review_tab_edited">सम्पादित। %1$s</string>
<string name="backup_review_tab_new">नए। %1$s</string>
<string name="backup_review_title_cleared">%1$s पारित</string>
<string name="backup_review_title_updated">%1$s अद्यतनकृत</string>
<string name="backup_review_topics_gone">विषय हटाए गए</string>
<string name="backup_review_trust_warning">श्रेणीयाँ तथा अनुचर गिनती तथा कचरालेख छालन जो आप देखते हैं इन प्रदाताओं से आते हैं।</string>
<string name="backup_review_trusted_tag">विश्वसनीय</string>
<string name="backup_review_unblocked">%1$s अबाधित</string>
<string name="backup_review_unblocked_explainer">अमेथिस्ट पुनः उनसे जुडेगा</string>
<string name="backup_review_units_tag">इकाई</string>
<string name="backup_review_unmuted">%1$s मौनावस्था हटाए गए</string>
<string name="backup_review_unmuted_explainer">ये पुनः आपके सूचनावलियों तक अभिगम्य</string>
<string name="backup_review_untrusted_tag">अविश्वसनीय</string>
<string name="backup_review_wallet_body">आपके धनकोष के टकसाल तथा कुंचिकाएँ आपके लिए रहस्यीकृत हैं। इसलिए उनकी तुलना वस्तु प्रति वस्तु यहाँ नहीं की जा सकती। पुनःस्थापन उन्हीं को लौट डालेगा जिनका अभिलेखन इस यन्त्र ने किया।</string>
<string name="backup_review_was_named">पूर्वतः %1$s था</string>
<string name="backup_review_words_and_hashtags">शब्द तथा विषयसूचक</string>
<string name="backup_type_app_settings">समचरणीकृत क्रमक स्थापना विकल्प</string>
<string name="backup_type_app_settings_explainer">अमेथिस्ट स्थापना विकल्प समचरणीकृत आपके यन्त्रों में। वे रहस्यीकृत हैं। इसलिए केवल उनकी उपस्थिति की तुलना की जा सकती है।</string>
<string name="backup_type_blocked_relays">बाधित पुनःप्रसारक सूची</string>
<string name="backup_type_blocked_relays_explainer">पुनःप्रसारक जिनसे यह क्रमक कभी भी संयोजन नहीं करता।</string>
<string name="backup_type_bolt12_offers">बोल्ट॰बारह प्रस्ताव</string>
<string name="backup_type_bolt12_offers_explainer">लैटनिंग प्रस्ताव जिनका उपयोग करके अन्य लोग आपको भुगतान करते हैं।</string>
<string name="backup_type_cashu_wallet">काशयू धनकोष</string>
<string name="backup_type_cashu_wallet_explainer">आपके ईकाश धनकोष स्थापना विकल्प तथा टकसाल। वे रहस्यीकृत हैं। इसलिए केवल उनकी उपस्थिति की तुलना की जा सकती है।</string>
<string name="backup_type_communities">समुदाय सूची</string>
<string name="backup_type_communities_explainer">समुदाय जिनसे आप जुडे।</string>
<string name="backup_type_concord_communities">कोंकोर्ड समुदाय सूची</string>
<string name="backup_type_concord_communities_explainer">कोंकोर्ड समुदाय जिनसे आप जुडे। वे रहस्यीकृत हैं। इसलिए केवल उनकी उपस्थिति की तुलना की जा सकती है।</string>
<string name="backup_type_dm_relays">सीधासन्देश पुनःप्रसारक सूची</string>
<string name="backup_type_dm_relays_explainer">जहाँ अन्य आपके निजी सन्देश भेजते हैं।</string>
<string name="backup_type_ephemeral_chats">अस्थायी चर्चा सूची</string>
<string name="backup_type_ephemeral_chats_explainer">अस्थायी चर्चा शालाएँ जिनसे आप जुडे।</string>
<string name="backup_type_favorite_algo_feeds">इष्ट सूचनावली सूची</string>
<string name="backup_type_favorite_algo_feeds_explainer">कलनविध्यात्मक सूचनावलियाँ जिनको आपने अभिलेखित किया।</string>
<string name="backup_type_follow_list">अनुचरण सूची</string>
<string name="backup_type_follow_list_explainer">निर्णय करता है किसके पत्र आपकी मुख्य सूचनावली में प्रकट होंगे।</string>
<string name="backup_type_geohashes">अनुचरित स्थान</string>
<string name="backup_type_geohashes_explainer">स्थान जहाँ से पत्र आपकी मुख्य सूचनावली में प्रकट होंगे।</string>
<string name="backup_type_hashtags">अनुचरित विषयसूचक</string>
<string name="backup_type_hashtags_explainer">विषयसूचक जिससे युक्त पत्र आपकी मुख्य सूचनावली में प्रकट होंगे।</string>
<string name="backup_type_indexer_relays">अनुक्रमणीकार पुनःप्रसारक सूची</string>
<string name="backup_type_indexer_relays_explainer">पुनःप्रसारक उपयुक्त अन्य लोगों के परिचय तथा पुनःप्रसारक सूचियों को प्राप्त करने के लिए।</string>
<string name="backup_type_key_package_relays">समूहचर्चा कुंचिका पुनःप्रसारक</string>
<string name="backup_type_key_package_relays_explainer">जहाँ अन्य उन कुंचिकाओं को प्राप्त करते हैं आपको रहस्यीकृत समूहचर्चाओं में जोडने के लिए।</string>
<string name="backup_type_mute_list">मौन सूची</string>
<string name="backup_type_mute_list_explainer">छिपाता है लोग शब्द विषयसूचक तथा सूत्र उन सब में से जो आप देखते हैं।</string>
<string name="backup_type_nutzap_info">नटज्साप प्राप्ती स्थापना विकल्प</string>
<string name="backup_type_nutzap_info_explainer">टकसाल तथा कुंचिका जिनका उपयोग अन्य करते हैं आपको नटज्साप भेजने के लिए।</string>
<string name="backup_type_other">सूची</string>
<string name="backup_type_other_explainer">इस यन्त्र पर अभिलेखित सूची।</string>
<string name="backup_type_outbox_inbox_relays">सार्वजनिक पुनःप्रसारक सूची</string>
<string name="backup_type_outbox_inbox_relays_explainer">सबको बताता है आपके पत्र कहाँ प्राप्त करें तथा आपको उत्तर तथा उल्लेखों को कहाँ भेजें।</string>
<string name="backup_type_payment_targets">भुगतान पते</string>
<string name="backup_type_payment_targets_explainer">विधियाँ जिनसे अन्य लोग आपको भुगतान कर सके।</string>
<string name="backup_type_private_outbox_relays">निजी पुनःप्रसारक सूची</string>
<string name="backup_type_private_outbox_relays_explainer">जहाँ आपके पाण्डुलिपियाँ तथा निजी जानकारी रखे जाते हैं।</string>
<string name="backup_type_profile">परिचय</string>
<string name="backup_type_profile_explainer">आपका नाम चित्र चरित तथा भुगतान पते। जैसे सब को दिखते हैं वैसे।</string>
<string name="backup_type_public_chats">सार्वजनिक चर्चा सूची</string>
<string name="backup_type_public_chats_explainer">सार्वजनिक चर्चाएँ जिनसे आप जुडे।</string>
<string name="backup_type_relay_feeds">इष्ट पुनःप्रसारक सूचनावलियाँ</string>
<string name="backup_type_relay_feeds_explainer">पुनःप्रसारक जिनका आप वीक्षण करते हैं सूचनावलियों के रूप में।</string>
<string name="backup_type_relay_groups">समूह सूची</string>
<string name="backup_type_relay_groups_explainer">पुनःप्रसारक आधारित समूह जिनसे आप जुडे।</string>
<string name="backup_type_search_relays">खोज पुनःप्रसारक सूची</string>
<string name="backup_type_search_relays_explainer">उपयुक्त पुनःप्रसारक जब आप खोजते हैं।</string>
<string name="backup_type_trust_providers">विश्वाससेवा प्रदाता सूची</string>
<string name="backup_type_trust_providers_explainer">सेवाएँ जिनपर आप विश्वास करते है आपके लिए लोगों का सत्यापन तथा श्रेणीकरण करने के लिए।</string>
<string name="backup_type_trusted_relays">विश्वसनीय पुनःप्रसारक सूची</string>
<string name="backup_type_trusted_relays_explainer">पुनःप्रसारक जिनपर आप पर्याप्त विश्वास करते हैं बिना पूछे उनसे संयोजन करने के लिए।</string>
</resources>
@@ -5287,4 +5287,322 @@
<string name="cyberspace_bag_unsigned">Aláírás nélküli, így ez a szerző csak állítás</string>
<string name="cyberspace_bag_unknown_kind">Egy %1$d típusú elem, amelyet ez a kliens nem jelenít meg</string>
<string name="cyberspace_bag_opaque">%1$d bájt valamiből, amit ez a kliens nem olvas</string>
<string name="backup_action_block_again">%1$s újbóli letiltása</string>
<string name="backup_action_keep_count">%1$s megtartása</string>
<string name="backup_action_rejoin">Újbóli csatlakozás ehhez: %1$s</string>
<string name="backup_action_remute">%1$s újbóli némítása</string>
<string name="backup_action_restore_count">%1$s visszaállítása</string>
<string name="backup_action_restore_feeds">Hírfolyamok visszaállítása</string>
<string name="backup_action_restore_places">Helyek visszaállítása</string>
<string name="backup_action_restore_profile">Profil visszaállítása</string>
<string name="backup_action_restore_providers">Szolgáltatók visszaállítása</string>
<string name="backup_action_restore_relays">Átjátszók visszaállítása</string>
<string name="backup_action_restore_topics">Témák visszaállítása</string>
<string name="backup_action_restore_wallet">Pénztárca visszaállítása</string>
<string name="backup_card_accept">Elfogadás</string>
<plurals name="backup_card_and_added">
<item quantity="one">%1$s és %2$d hozzáadva</item>
<item quantity="other">%1$s és %2$d hozzáadva</item>
</plurals>
<plurals name="backup_card_and_blocked">
<item quantity="one">%1$s és %2$d letiltva</item>
<item quantity="other">%1$s és %2$d letiltva</item>
</plurals>
<plurals name="backup_card_and_followed">
<item quantity="one">%1$s és %2$d követve</item>
<item quantity="other">%1$s és %2$d követve</item>
</plurals>
<plurals name="backup_card_and_joined">
<item quantity="one">%1$s és %2$d csatlakozva</item>
<item quantity="other">%1$s és %2$d csatlakozva</item>
</plurals>
<plurals name="backup_card_and_muted">
<item quantity="one">%1$s és %2$d némítva</item>
<item quantity="other">%1$s és %2$d némítva</item>
</plurals>
<string name="backup_card_app_settings">A szinkronizált Amethyst alkalmazásbeállításai egy másik, nem Amethyst alkalmazásban módosultak. Ez problémákat okozhat.</string>
<string name="backup_card_concord_emptied">Ön kiürítette a Concord listáját</string>
<string name="backup_card_concord_rewritten">Ön módosította a Concord listáját</string>
<plurals name="backup_card_dm_relays_lost">
<item quantity="one">Ön %1$d privát üzenetátjátszót távolított el</item>
<item quantity="other">%1$d privát üzenet átjátszót távolított el</item>
</plurals>
<plurals name="backup_card_feeds_removed">
<item quantity="one">Ön %1$d kedvenc hírfolyamot távolított el</item>
<item quantity="other">%1$d kedvenc hírfolyamot távolított el</item>
</plurals>
<plurals name="backup_card_follows_dropped">
<item quantity="one">%1$d személyt nem követ többé</item>
<item quantity="other">%1$d személyt nem követ többé</item>
</plurals>
<plurals name="backup_card_hashtags_unfollowed">
<item quantity="one">Ön %1$d kulcsszót nem követ többé</item>
<item quantity="other">%1$d hashtaget nem követ többé</item>
</plurals>
<plurals name="backup_card_indexer_relays_lost">
<item quantity="one">Ön %1$d indexelő átjátszót távolított el</item>
<item quantity="other">%1$d indexelő átjátszót távolított el</item>
</plurals>
<plurals name="backup_card_key_package_relays_lost">
<item quantity="one">Ön %1$d csoportos csevegési kulcsátjátszót távolított el</item>
<item quantity="other">%1$d csoportcsevegés kulcs átjátszót távolított el</item>
</plurals>
<plurals name="backup_card_leave_chats">
<item quantity="one">Ön %1$d nyilvános csevegést hagyott el</item>
<item quantity="other">%1$d nyilvános csevegést hagyott el</item>
</plurals>
<plurals name="backup_card_leave_communities">
<item quantity="one">Ön %1$d közösséget hagyott el</item>
<item quantity="other">%1$d közösséget hagyott el</item>
</plurals>
<plurals name="backup_card_leave_groups">
<item quantity="one">Ön %1$d csoportot hagyott el</item>
<item quantity="other">%1$d csoportot hagyott el</item>
</plurals>
<plurals name="backup_card_leave_rooms">
<item quantity="one">Ön %1$d csevegőszobát hagyott el</item>
<item quantity="other">%1$d csevegőszobát hagyott el</item>
</plurals>
<plurals name="backup_card_mutes_visible">
<item quantity="one">Ön %1$d dolog némítását oldotta fel</item>
<item quantity="other">%1$d dolog némítását oldotta fel</item>
</plurals>
<plurals name="backup_card_nip65_lost">
<item quantity="one">Ön %1$d nyilvános átjátszót távolított el</item>
<item quantity="other">%1$d nyilvános átjátszót távolított el</item>
</plurals>
<string name="backup_card_nutzap_key_removed">Ön eltávolította a nutzap kulcsát</string>
<string name="backup_card_nutzap_key_replaced">Ön lecserélte a nutzap kulcsát</string>
<plurals name="backup_card_nutzap_mints_removed">
<item quantity="one">Ön %1$d nutzap pénzverdét távolított el</item>
<item quantity="other">%1$d nutzap pénzverdét távolított el</item>
</plurals>
<plurals name="backup_card_offers_removed">
<item quantity="one">Ön %1$d BOLT12 ajánlatot távolított el</item>
<item quantity="other">%1$d BOLT12 ajánlatot távolított el</item>
</plurals>
<plurals name="backup_card_payment_targets_removed">
<item quantity="one">Ön %1$d fizetési módot távolított el</item>
<item quantity="other">%1$d fizetési módot távolított el</item>
</plurals>
<plurals name="backup_card_places_unfollowed">
<item quantity="one">Ön %1$d helyet nem követ többé</item>
<item quantity="other">%1$d helyet nem követ többé</item>
</plurals>
<plurals name="backup_card_private_outbox_lost">
<item quantity="one">Ön %1$d privát átjátszót távolított el</item>
<item quantity="other">%1$d privát átjátszót távolított el</item>
</plurals>
<plurals name="backup_card_profile_lost_fields">
<item quantity="one">Ön %1$d profilmezőt távolított el</item>
<item quantity="other">%1$d profilmezőt távolított el</item>
</plurals>
<plurals name="backup_card_relay_feeds_lost">
<item quantity="one">Ön %1$d átjátszó-hírfolyamot távolított el</item>
<item quantity="other">%1$d átjátszó hírfolyamot távolított el</item>
</plurals>
<plurals name="backup_card_search_relays_lost">
<item quantity="one">Ön %1$d kereső átjátszót távolított el</item>
<item quantity="other">%1$d kereső átjátszót távolított el</item>
</plurals>
<plurals name="backup_card_trust_services_changed">
<item quantity="one">Ön %1$d bizalmi szolgáltatás szolgáltatóját módosította</item>
<item quantity="other">%1$d bizalmi szolgáltatás szolgáltatóját változtatta meg</item>
</plurals>
<plurals name="backup_card_trusted_lost">
<item quantity="one">Ön %1$d átjátszóban nem bízik többé</item>
<item quantity="other">%1$d átjátszóban nem bízik többé</item>
</plurals>
<plurals name="backup_card_unblocked">
<item quantity="one">Ön %1$d átjátszó letiltását oldotta fel</item>
<item quantity="other">%1$d átjátszó blokkolását oldotta fel</item>
</plurals>
<string name="backup_card_wallet_emptied">Ön kiürítette a Cashu pénztárcáját</string>
<string name="backup_card_wallet_rewritten">Ön módosította a Cashu pénztárcáját</string>
<string name="backup_conflict_added">Hozzáadva (%1$s)</string>
<string name="backup_conflict_changed">Módosítva (%1$s)</string>
<string name="backup_conflict_count_added">%1$s hozzáadva</string>
<string name="backup_conflict_count_changed">%1$s módosítva</string>
<string name="backup_conflict_count_removed">%1$s eltávolítva</string>
<string name="backup_conflict_keep_new">Új verzió használata</string>
<string name="backup_conflict_private_added">Ön privát (titkosított) elemeket adott hozzá.</string>
<string name="backup_conflict_private_changed">Ön privát (titkosított) elemeket is módosított. Ezek itt nem hasonlíthatók össze.</string>
<string name="backup_conflict_private_cleared">Ön eltávolította az összes privát (titkosított) elemet.</string>
<string name="backup_conflict_relay_read_only">csak olvasható</string>
<string name="backup_conflict_relay_read_write">olvasás és írás</string>
<string name="backup_conflict_relay_write_only">csak írható</string>
<string name="backup_conflict_removed">Eltávolítva (%1$s)</string>
<string name="backup_conflict_restore_mine">Mentett verzió visszaállítása</string>
<string name="backup_conflict_title">Ön módosította a következőt: %1$s, egy másik alkalmazásban</string>
<string name="backup_entry_algo_feed">Hírfolyamok</string>
<string name="backup_entry_chat_room">Csevegőszobák</string>
<string name="backup_entry_community">Közösségek</string>
<string name="backup_entry_hashtag">Kulcsszavak</string>
<string name="backup_entry_identity_claim">Kapcsolt identitások</string>
<string name="backup_entry_location">Helyek</string>
<string name="backup_entry_mint">Pénzverdék</string>
<string name="backup_entry_nutzap_key">Nutzap kulcsok</string>
<string name="backup_entry_offer">Ajánlatok</string>
<string name="backup_entry_other">Egyéb bejegyzések</string>
<string name="backup_entry_payment_target">Fizetési címek</string>
<string name="backup_entry_person">Személyek</string>
<string name="backup_entry_profile_field">Profilmezők</string>
<string name="backup_entry_public_chat">Nyilvános csevegések</string>
<string name="backup_entry_relay">Átjátszók</string>
<string name="backup_entry_relay_group">Csoportok</string>
<string name="backup_entry_thread">Beszélgetések</string>
<string name="backup_entry_trust_provider">Bizalmi szolgáltatók</string>
<string name="backup_entry_word">Szavak</string>
<string name="backup_profile_field_about">Névjegy</string>
<string name="backup_profile_field_banner">Borító</string>
<string name="backup_profile_field_birthday">Születésnap</string>
<string name="backup_profile_field_bot">Bot jelölő</string>
<string name="backup_profile_field_clink_offer">CLINK-ajánlat</string>
<string name="backup_profile_field_display_name">Megjelenített név</string>
<string name="backup_profile_field_lud06">LNURL</string>
<string name="backup_profile_field_lud16">Lightning-cím</string>
<string name="backup_profile_field_name">Felhasználónév</string>
<string name="backup_profile_field_nip05">Nostr-cím</string>
<string name="backup_profile_field_picture">Kép</string>
<string name="backup_profile_field_pronouns">Névmások</string>
<string name="backup_profile_field_website">Weboldal</string>
<string name="backup_review_allowed_tag">Engedélyezve</string>
<string name="backup_review_changed_by_other_app">Módosítva egy másik alkalmazásban · %1$s</string>
<string name="backup_review_concord_body">A Concord közösségei Önhöz vannak titkosítva, így itt nem hasonlíthatók össze. A visszaállítás pontosan azt hozza vissza, amit ez az eszköz mentett.</string>
<string name="backup_review_demoted_tag">Áthelyezve</string>
<string name="backup_review_dropped_count">%1$s elvetve</string>
<string name="backup_review_dropped_tag">Elvetve</string>
<string name="backup_review_encrypted_added">Ön hozta létre</string>
<string name="backup_review_encrypted_changed">Ön módosította</string>
<string name="backup_review_encrypted_cleared">Ön kiürítette</string>
<string name="backup_review_feed">Hírfolyam</string>
<string name="backup_review_gone">Eltűnt</string>
<string name="backup_review_hashtags_you_follow">Ön által követett kulcsszavak</string>
<string name="backup_review_inbox">Bejövő</string>
<string name="backup_review_inbox_explainer">ahol mások elérik Önt</string>
<string name="backup_review_joined">csatlakozott</string>
<string name="backup_review_kept">megtartva</string>
<string name="backup_review_kept_count">%1$s megtartva</string>
<string name="backup_review_key_removed">Fogadási kulcs eltávolítva</string>
<string name="backup_review_key_replaced">Fogadási kulcs lecserélve</string>
<string name="backup_review_key_warning">A nutzapok ehhez az új kulcshoz lesznek kötve. Ha ez az eszköz nem rendelkezik vele, lehet, hogy itt nem tudja beváltani őket.</string>
<string name="backup_review_left">elhagyta a csoportot</string>
<string name="backup_review_left_tag">Elhagyva</string>
<string name="backup_review_listed_tag">Listázva</string>
<string name="backup_review_moved_tag">Áthelyezve</string>
<string name="backup_review_names_and_more">%1$s és %2$s további</string>
<string name="backup_review_new">Új verzió</string>
<string name="backup_review_new_count">%1$s új</string>
<string name="backup_review_new_count_caption">új</string>
<string name="backup_review_new_tag">Új</string>
<string name="backup_review_newly_blocked">Újonnan letiltva</string>
<string name="backup_review_newly_muted">%1$s újonnan némítva</string>
<string name="backup_review_no_longer_blocked">Már nincs letiltva</string>
<string name="backup_review_not_set">Nincs beállítva</string>
<string name="backup_review_nothing_here">Itt nincs semmi</string>
<string name="backup_review_offer">BOLT12 ajánlat</string>
<plurals name="backup_review_offers_gone">
<item quantity="one">%1$d ajánlat eltávolítva</item>
<item quantity="other">%1$d ajánlat eltávolítva</item>
</plurals>
<string name="backup_review_outbox">Kimenő</string>
<string name="backup_review_outbox_explainer">ahol közzétesz</string>
<plurals name="backup_review_payment_targets_gone">
<item quantity="one">%1$d fizetési mód eltávolítva</item>
<item quantity="other">%1$d fizetési mód eltávolítva</item>
</plurals>
<string name="backup_review_payments_explainer">A fizető emberek csak azt látják, ami ebben a listában van.</string>
<string name="backup_review_places_gone">eltűnt helyek</string>
<string name="backup_review_places_you_follow">Ön által követett helyek</string>
<string name="backup_review_private_added_body">Most már titkosítva vannak, valószínűleg ide kerültek a fenti nyilvános elemek.</string>
<string name="backup_review_private_added_title">Ön priváttá tette az elemeket</string>
<string name="backup_review_private_changed_body">Titkosítva vannak, így itt nem hasonlíthatók össze.</string>
<string name="backup_review_private_changed_title">Ön módosította a privát elemeket</string>
<string name="backup_review_private_wiped_body">Titkosítva vannak, így csak Ön láthatja, mik voltak. A visszaállítás mindet visszahozza.</string>
<string name="backup_review_private_wiped_title">Ön eltávolította az összes privát elemet</string>
<string name="backup_review_receiving_tag">Fogadás</string>
<string name="backup_review_renamed">átnevezte</string>
<string name="backup_review_renamed_tag">Átnevezve</string>
<string name="backup_review_saved">Ezen az eszközön</string>
<string name="backup_review_search_people">%1$s személy keresése</string>
<string name="backup_review_searching_tag">Keresés</string>
<string name="backup_review_service">Szolgáltatás</string>
<string name="backup_review_service_followers">Követők</string>
<string name="backup_review_service_rank">Rang</string>
<string name="backup_review_service_topics">Témák</string>
<string name="backup_review_services_changed">%2$s szolgáltatásból %1$s módosult</string>
<string name="backup_review_services_changed_explainer">Most már valaki mástól vagy senkitől sem származnak</string>
<string name="backup_review_still_blocked">Továbbra is le van tiltva</string>
<string name="backup_review_storing_tag">Tárolás</string>
<string name="backup_review_tab_dropped">Elvetve · %1$s</string>
<string name="backup_review_tab_edited">Szerkesztve · %1$s</string>
<string name="backup_review_tab_new">Új · %1$s</string>
<string name="backup_review_title_cleared">%1$s törölve</string>
<string name="backup_review_title_updated">%1$s frissítve</string>
<string name="backup_review_topics_gone">eltűnt témák</string>
<string name="backup_review_trust_warning">A látott rangok, követőszámok és a kéretlen tartalomszűrés ezektől a szolgáltatóktól származnak.</string>
<string name="backup_review_trusted_tag">Megbízható</string>
<string name="backup_review_unblocked">%1$s letiltása feloldva</string>
<string name="backup_review_unblocked_explainer">Az Amethyst újra kapcsolódni fog hozzájuk</string>
<string name="backup_review_units_tag">Egységek</string>
<string name="backup_review_unmuted">%1$s némítása feloldva</string>
<string name="backup_review_unmuted_explainer">Ezek újra megjelenhetnek a hírfolyamaiban</string>
<string name="backup_review_untrusted_tag">Nem megbízható</string>
<string name="backup_review_wallet_body">A saját pénztárca pénzverdéi és kulcsai Önhöz vannak titkosítva, így itt nem hasonlíthatók össze tételesen. A visszaállítás pontosan azt hozza vissza, amit ez az eszköz mentett.</string>
<string name="backup_review_was_named">„%1$s” volt</string>
<string name="backup_review_words_and_hashtags">Szavak és kulcsszavak</string>
<string name="backup_type_app_settings">szinkronizált alkalmazásbeállítások</string>
<string name="backup_type_app_settings_explainer">Az eszközök között szinkronizált Amethyst-beállítások. Titkosítva vannak, így csak a jelenlétük hasonlítható össze.</string>
<string name="backup_type_blocked_relays">letiltott átjátszólista</string>
<string name="backup_type_blocked_relays_explainer">Átjátszók, amelyekhez ez az alkalmazás soha nem kapcsolódik.</string>
<string name="backup_type_bolt12_offers">BOLT12 ajánlatok</string>
<string name="backup_type_bolt12_offers_explainer">A lightning ajánlatok, amelyeket mások a fizetéshez használnak az Ön számára.</string>
<string name="backup_type_cashu_wallet">Cashu pénztárca</string>
<string name="backup_type_cashu_wallet_explainer">Az ecash pénztárca beállításai és pénzverdéi. Titkosítva vannak, így csak a jelenlétük hasonlítható össze.</string>
<string name="backup_type_communities">közösséglista</string>
<string name="backup_type_communities_explainer">Közösségek, amelyekhez csatlakozott.</string>
<string name="backup_type_concord_communities">Concord közösséglista</string>
<string name="backup_type_concord_communities_explainer">A Concord közösségek, amelyekhez csatlakozott. Titkosítva vannak, így csak a jelenlétük hasonlítható össze.</string>
<string name="backup_type_dm_relays">privát üzenet átjátszólista</string>
<string name="backup_type_dm_relays_explainer">Ahová mások kézbesítik a privát üzeneteit.</string>
<string name="backup_type_ephemeral_chats">rövid életű csevegési lista</string>
<string name="backup_type_ephemeral_chats_explainer">Rövid életű csevegőszobák, amelyekhez csatlakozott.</string>
<string name="backup_type_favorite_algo_feeds">kedvenc hírfolyamlista</string>
<string name="backup_type_favorite_algo_feeds_explainer">Mentett algoritmus-hírfolyamok.</string>
<string name="backup_type_follow_list">követési lista</string>
<string name="backup_type_follow_list_explainer">Meghatározza, kinek a bejegyzései jelenjenek meg a kezdőlapon.</string>
<string name="backup_type_geohashes">követett helyek</string>
<string name="backup_type_geohashes_explainer">Helyek, amelyek bejegyzései megjelennek a hírfolyamaiban.</string>
<string name="backup_type_hashtags">követett kulcsszavak</string>
<string name="backup_type_hashtags_explainer">Kulcsszavak, amelyek bejegyzései megjelennek a hírfolyamban.</string>
<string name="backup_type_indexer_relays">indexelő átjátszólista</string>
<string name="backup_type_indexer_relays_explainer">Azok az átjátszók, amelyeket mások profiljainak és átjátszólistáinak megtalálásához használnak.</string>
<string name="backup_type_key_package_relays">csoportos csevegési kulcsátjátszók</string>
<string name="backup_type_key_package_relays_explainer">Ahol mások megtalálják a kulcsokat, hogy titkosított csoportos csevegésekhez adhassák Önt.</string>
<string name="backup_type_mute_list">némítási lista</string>
<string name="backup_type_mute_list_explainer">Elrejti a személyeket, szavakat, kulcsszavakat és beszélgetéseket mindenből, amit lát.</string>
<string name="backup_type_nutzap_info">nutzap fogadási beállítások</string>
<string name="backup_type_nutzap_info_explainer">A pénzverdék és a kulcs, amelyeket mások a nutzapok küldéséhez használnak az Ön számára.</string>
<string name="backup_type_other">lista</string>
<string name="backup_type_other_explainer">Egy listaelem ezen az eszközön mentve.</string>
<string name="backup_type_outbox_inbox_relays">nyilvános átjátszólista</string>
<string name="backup_type_outbox_inbox_relays_explainer">Megmondja mindenkinek, hol találhatók a bejegyzései, és hová küldjék a válaszokat és említéseket.</string>
<string name="backup_type_payment_targets">fizetési címek</string>
<string name="backup_type_payment_targets_explainer">A módok, ahogy mások fizethetnek Önnek.</string>
<string name="backup_type_private_outbox_relays">privát átjátszólista</string>
<string name="backup_type_private_outbox_relays_explainer">Ahol a piszkozatai és privát adatai tárolódnak.</string>
<string name="backup_type_profile">profil</string>
<string name="backup_type_profile_explainer">Az Ön neve, képe, bemutatkozása és fizetési címei, ahogy mindenki látja őket.</string>
<string name="backup_type_public_chats">nyilvános csevegési lista</string>
<string name="backup_type_public_chats_explainer">Nyilvános csevegések, amelyekhez csatlakozott.</string>
<string name="backup_type_relay_feeds">kedvenc átjátszóhírfolyamok</string>
<string name="backup_type_relay_feeds_explainer">Olyan átjátszók, amelyeket hírfolyamként böngész.</string>
<string name="backup_type_relay_groups">csoportlista</string>
<string name="backup_type_relay_groups_explainer">Átjátszó-alapú csoportok, amelyekhez csatlakozott.</string>
<string name="backup_type_search_relays">kereső átjátszólista</string>
<string name="backup_type_search_relays_explainer">Kereséskor használt átjátszók.</string>
<string name="backup_type_trust_providers">bizalmi szolgáltatólista</string>
<string name="backup_type_trust_providers_explainer">A szolgáltatások, amelyekben megbízik, hogy rangsorolják és ellenőrizzék Önnek az embereket.</string>
<string name="backup_type_trusted_relays">megbízható átjátszólista</string>
<string name="backup_type_trusted_relays_explainer">Átjátszók, amelyekhez kérdezés nélkül kapcsolódhat.</string>
</resources>
@@ -5507,4 +5507,382 @@ Zaplanowane posty z innych kont nie zostaną opublikowane, dopóki to konto jest
<string name="cyberspace_bag_unsigned">Brak podpisu, więc autor to tylko domysł</string>
<string name="cyberspace_bag_unknown_kind">Element typu %1$d, którego ten klient nie wyświetla</string>
<string name="cyberspace_bag_opaque">%1$d bajtów danych, których ten klient nie odczytuje</string>
<string name="backup_action_block_again">Zablokuj %1$s ponownie</string>
<string name="backup_action_keep_count">Zachowaj %1$s</string>
<string name="backup_action_rejoin">Ponów %1$s</string>
<string name="backup_action_remute">Ponownie wycisz %1$s</string>
<string name="backup_action_restore_count">Przywróć %1$s</string>
<string name="backup_action_restore_feeds">Przywróć kanały</string>
<string name="backup_action_restore_places">Przywróć miejsca</string>
<string name="backup_action_restore_profile">Przywróć profil</string>
<string name="backup_action_restore_providers">Przywróć dostawców</string>
<string name="backup_action_restore_relays">Przywróć transmitery</string>
<string name="backup_action_restore_topics">Przywróć tematy</string>
<string name="backup_action_restore_wallet">Przywróć portfel</string>
<string name="backup_card_accept">Zaakceptuj</string>
<plurals name="backup_card_and_added">
<item quantity="one">%1$s i dodano %2$d</item>
<item quantity="few">%1$s i dodano %2$d</item>
<item quantity="many">%1$s i dodano %2$d</item>
<item quantity="other">%1$s i dodano %2$d</item>
</plurals>
<plurals name="backup_card_and_blocked">
<item quantity="one">%1$s i zablokowano %2$d</item>
<item quantity="few">%1$s i zablokowano %2$d</item>
<item quantity="many">%1$s i zablokowano %2$d</item>
<item quantity="other">%1$s i zablokowano %2$d</item>
</plurals>
<plurals name="backup_card_and_followed">
<item quantity="one">%1$s i obserwowany %2$d</item>
<item quantity="few">%1$s i obserwowanych %2$d</item>
<item quantity="many">%1$s i obserwowanych %2$d</item>
<item quantity="other">%1$s i obserwowane %2$d</item>
</plurals>
<plurals name="backup_card_and_joined">
<item quantity="one">%1$s i dołączono do %2$d</item>
<item quantity="few">%1$s i dołączono do %2$d</item>
<item quantity="many">%1$s i dołączono do %2$d</item>
<item quantity="other">%1$s i dołączono do %2$d</item>
</plurals>
<plurals name="backup_card_and_muted">
<item quantity="one">%1$s i wyciszony %2$d</item>
<item quantity="few">%1$s i wyciszono %2$d</item>
<item quantity="many">%1$s i wyciszono %2$d</item>
<item quantity="other">%1$s i wyciszone %2$d</item>
</plurals>
<string name="backup_card_app_settings">Twoje zsynchronizowane ustawienia aplikacji Amethyst zostały zmienione w innej aplikacji, która nie jest Amethystem. To może powodować problemy.</string>
<string name="backup_card_concord_emptied">Opróżniłeś swoją listę Concorda</string>
<string name="backup_card_concord_rewritten">Zmieniłeś swoją listę Concorda</string>
<plurals name="backup_card_dm_relays_lost">
<item quantity="one">Usunąłeś %1$d transmiter DM</item>
<item quantity="few">Usunąłeś %1$d transmiterów DM</item>
<item quantity="many">Usunąłeś %1$d transmiterów DM</item>
<item quantity="other">Usunąłeś %1$d transmitery DM</item>
</plurals>
<plurals name="backup_card_feeds_removed">
<item quantity="one">Usunąłeś %1$d ulubiony kanał</item>
<item quantity="few">Usunąłeś %1$d ulubionych kanałów</item>
<item quantity="many">Usunąłeś %1$d ulubionych kanałów</item>
<item quantity="other">Usunięto %1$d ulubione kanały</item>
</plurals>
<plurals name="backup_card_follows_dropped">
<item quantity="one">Przestałeś obserwować %1$d osobę</item>
<item quantity="few">Przestałeś obserwować %1$d osób</item>
<item quantity="many">Przestałeś obserwować %1$d osób</item>
<item quantity="other">Przestałeś obserwować %1$d osoby</item>
</plurals>
<plurals name="backup_card_hashtags_unfollowed">
<item quantity="one">Przestałeś obserwować %1$d hashtag</item>
<item quantity="few">Przestałeś obserwować %1$d hashtagów</item>
<item quantity="many">Przestałeś obserwować %1$d hashtagów</item>
<item quantity="other">Przestałeś obserwować %1$d hashtagi</item>
</plurals>
<plurals name="backup_card_indexer_relays_lost">
<item quantity="one">Usunięto %1$d transmiter indeksujący</item>
<item quantity="few">Usunięto %1$d transmiterów indeksujących</item>
<item quantity="many">Usunięto %1$d transmiterów indeksujących</item>
<item quantity="other">Usunięto %1$d transmitery indeksujące</item>
</plurals>
<plurals name="backup_card_key_package_relays_lost">
<item quantity="one">Usunięto %1$d transmiter klucza grupowego czatu</item>
<item quantity="few">Usunięto %1$d transmiterów klucza grupowego czatu</item>
<item quantity="many">Usunięto %1$d transmiterów klucza grupowego czatu</item>
<item quantity="other">Usunięto %1$d transmitery klucza grupowego czatu</item>
</plurals>
<plurals name="backup_card_leave_chats">
<item quantity="one">Opuściłeś %1$d czat publiczny</item>
<item quantity="few">Opuściłeś %1$d czatów publicznych</item>
<item quantity="many">Opuściłeś %1$d czatów publicznych</item>
<item quantity="other">Opuściłeś %1$d czaty publiczne</item>
</plurals>
<plurals name="backup_card_leave_communities">
<item quantity="one">Opuściłeś %1$d społeczność</item>
<item quantity="few">Opuściłeś %1$d społeczności</item>
<item quantity="many">Opuściłeś %1$d społeczności</item>
<item quantity="other">Opuściłeś %1$d społeczności</item>
</plurals>
<plurals name="backup_card_leave_groups">
<item quantity="one">Opuściłeś %1$d grupę</item>
<item quantity="few">Opuściłeś %1$d grup</item>
<item quantity="many">Opuściłeś %1$d grup</item>
<item quantity="other">Opuszczono %1$d grupy</item>
</plurals>
<plurals name="backup_card_leave_rooms">
<item quantity="one">Opuściłeś %1$d pokój czatu</item>
<item quantity="few">Opuściłeś %1$d pokoi czatu</item>
<item quantity="many">Opuściłeś %1$d pokoi czatu</item>
<item quantity="other">Opuściłeś %1$d pokoje czatu</item>
</plurals>
<plurals name="backup_card_mutes_visible">
<item quantity="one">Włączyłeś dźwięk dla %1$d elementu</item>
<item quantity="few">Włączyłeś dźwięk dla %1$d elementów</item>
<item quantity="many">Włączyłeś dźwięk dla %1$d elementów</item>
<item quantity="other">%1$d elementy mają włączony dźwięk</item>
</plurals>
<plurals name="backup_card_nip65_lost">
<item quantity="one">Usunąłeś %1$d publiczny transmiter</item>
<item quantity="few">Usunąłeś %1$d publicznych transmiterów</item>
<item quantity="many">Usunąłeś %1$d publicznych transmiterów</item>
<item quantity="other">Usunąłeś %1$d publiczne transmitery</item>
</plurals>
<string name="backup_card_nutzap_key_removed">Usunąłeś swój klucz nutzap</string>
<string name="backup_card_nutzap_key_replaced">Wymieniłeś swój klucz Nutzap</string>
<plurals name="backup_card_nutzap_mints_removed">
<item quantity="one">Usunięto %1$d nutzap mint</item>
<item quantity="few">Usunięto %1$d nutzap mintów</item>
<item quantity="many">Usunięto %1$d nutzap mintów</item>
<item quantity="other">Usunięto %1$d nutzap minty</item>
</plurals>
<plurals name="backup_card_offers_removed">
<item quantity="one">Usunięto %1$d ofertę BOLT12</item>
<item quantity="few">Usunięto %1$d ofert BOLT12</item>
<item quantity="many">Usunięto %1$d ofert BOLT12</item>
<item quantity="other">Usunięto %1$d oferty BOLT12</item>
</plurals>
<plurals name="backup_card_payment_targets_removed">
<item quantity="one">Usunięto %1$d sposób na otrzymanie zapłaty</item>
<item quantity="few">Usunięto %1$d sposobów otrzymywania zapłaty</item>
<item quantity="many">Usunięto %1$d sposobów otrzymywania zapłaty</item>
<item quantity="other">Usunięto %1$d sposoby otrzymywania zapłaty</item>
</plurals>
<plurals name="backup_card_places_unfollowed">
<item quantity="one">Przestałeś obserwować %1$d miejsce</item>
<item quantity="few">Przestałeś obserwować %1$d miejsc</item>
<item quantity="many">Przestałeś obserwować %1$d miejsc</item>
<item quantity="other">Przestałeś obserwować %1$d miejsca</item>
</plurals>
<plurals name="backup_card_private_outbox_lost">
<item quantity="one">Usunięto %1$d prywatny transmiter</item>
<item quantity="few">Usunięto %1$d prywatnych transmiterów</item>
<item quantity="many">Usunięto %1$d prywatnych transmiterów</item>
<item quantity="other">Usunięto %1$d prywatne transmitery</item>
</plurals>
<plurals name="backup_card_profile_lost_fields">
<item quantity="one">Usunąłeś %1$d pole profilu</item>
<item quantity="few">Usunąłeś %1$d pól profilowych</item>
<item quantity="many">Usunąłeś %1$d pól profilowych</item>
<item quantity="other">Usunąłeś %1$d pola profilowe</item>
</plurals>
<plurals name="backup_card_relay_feeds_lost">
<item quantity="one">Usunięto %1$d kanał transmitera</item>
<item quantity="few">Usunięto %1$d kanałów transmitera</item>
<item quantity="many">Usunięto %1$d kanałów transmitera</item>
<item quantity="other">Usunięto %1$d kanały transmitera</item>
</plurals>
<plurals name="backup_card_search_relays_lost">
<item quantity="one">Usunięto %1$d transmiter wyszukiwania</item>
<item quantity="few">Usunięto %1$d transmiterów wyszukiwania</item>
<item quantity="many">Usunięto %1$d transmiterów wyszukiwania</item>
<item quantity="other">Usunięto %1$d transmitery wyszukiwania</item>
</plurals>
<plurals name="backup_card_trust_services_changed">
<item quantity="one">Zmieniłeś dostawcę %1$d zaufanego serwisu</item>
<item quantity="few">Zmieniłeś dostawcę %1$d zaufanych serwisów</item>
<item quantity="many">Zmieniłeś dostawcę %1$d zaufanych serwisów</item>
<item quantity="other">Zmieniłeś dostawcę %1$d zaufanych serwisów</item>
</plurals>
<plurals name="backup_card_trusted_lost">
<item quantity="one">Przestałeś ufać %1$d transmiterowi</item>
<item quantity="few">Przestałeś ufać %1$d transmiterom</item>
<item quantity="many">Przestałeś ufać %1$d transmiterom</item>
<item quantity="other">Przestałeś ufać %1$d transmiterom</item>
</plurals>
<plurals name="backup_card_unblocked">
<item quantity="one">Odblokowałeś %1$d transmiter</item>
<item quantity="few">Odblokowałeś %1$d transmiterów</item>
<item quantity="many">Odblokowałeś %1$d transmiterów</item>
<item quantity="other">Odblokowałeś %1$d transmitery</item>
</plurals>
<string name="backup_card_wallet_emptied">Opróżniłeś swój portfel Cashu</string>
<string name="backup_card_wallet_rewritten">Zmieniłeś swój portfel Cashu</string>
<string name="backup_conflict_added">Dodano (%1$s)</string>
<string name="backup_conflict_changed">Zmieniono (%1$s)</string>
<string name="backup_conflict_count_added">Dodano %1$s</string>
<string name="backup_conflict_count_changed">%1$s zmieniono</string>
<string name="backup_conflict_count_removed">Usunięto %1$s</string>
<string name="backup_conflict_keep_new">Użyj nowej wersji</string>
<string name="backup_conflict_private_added">Dodano prywatne (zaszyfrowane) pozycje.</string>
<string name="backup_conflict_private_changed">Zmieniłeś również elementy prywatne (zaszyfrowane). Nie można ich tutaj porównać.</string>
<string name="backup_conflict_private_cleared">Usunąłeś wszystkie prywatne (zaszyfrowane) elementy.</string>
<string name="backup_conflict_relay_read_only">tylko do odczytu</string>
<string name="backup_conflict_relay_read_write">odczyt i zapis</string>
<string name="backup_conflict_relay_write_only">tylko do zapisu</string>
<string name="backup_conflict_removed">Usunięto (%1$s)</string>
<string name="backup_conflict_restore_mine">Przywróć zapisaną wersję</string>
<string name="backup_conflict_title">Zmieniłeś %1$s w innej aplikacji</string>
<string name="backup_entry_algo_feed">Kanały</string>
<string name="backup_entry_chat_room">Czat pokoje</string>
<string name="backup_entry_community">Społeczności</string>
<string name="backup_entry_hashtag">Hashtagi</string>
<string name="backup_entry_identity_claim">Powiązane tożsamości</string>
<string name="backup_entry_location">Lokalizacje</string>
<string name="backup_entry_mint">Minty</string>
<string name="backup_entry_nutzap_key">Klucze Nutzap</string>
<string name="backup_entry_offer">Oferty</string>
<string name="backup_entry_other">Inne wpisy</string>
<string name="backup_entry_payment_target">Adresy płatności</string>
<string name="backup_entry_person">Osoby</string>
<string name="backup_entry_profile_field">Pola profilu</string>
<string name="backup_entry_public_chat">Czaty publiczne</string>
<string name="backup_entry_relay">Transmitery</string>
<string name="backup_entry_relay_group">Grupy</string>
<string name="backup_entry_thread">Wątki</string>
<string name="backup_entry_trust_provider">Zaufani dostawcy</string>
<string name="backup_entry_word">Słowa</string>
<string name="backup_profile_field_about">O profilu</string>
<string name="backup_profile_field_banner">Baner</string>
<string name="backup_profile_field_birthday">Data urodzin</string>
<string name="backup_profile_field_bot">Flaga bota</string>
<string name="backup_profile_field_clink_offer">Oferta CLINK</string>
<string name="backup_profile_field_display_name">Nazwa wyświetlana</string>
<string name="backup_profile_field_lud06">LNURL</string>
<string name="backup_profile_field_lud16">Lightning Adres</string>
<string name="backup_profile_field_name">Nazwa użytkownika</string>
<string name="backup_profile_field_nip05">Adres Nostr</string>
<string name="backup_profile_field_picture">Zdjęcie</string>
<string name="backup_profile_field_pronouns">Zaimki</string>
<string name="backup_profile_field_website">Strona www</string>
<string name="backup_review_allowed_tag">Dozwolona</string>
<string name="backup_review_changed_by_other_app">Zmieniono w innej aplikacji · %1$s</string>
<string name="backup_review_concord_body">Twoje społeczności w Concord są dla Ciebie zaszyfrowane, więc nie można ich tutaj porównać. Przywrócenie danych powoduje przywrócenie dokładnie tego, co zostało zapisane na tym urządzeniu.</string>
<string name="backup_review_demoted_tag">Przeniesiona</string>
<string name="backup_review_dropped_count">%1$s odrzuconych</string>
<string name="backup_review_dropped_tag">Usunięta</string>
<string name="backup_review_encrypted_added">Utworzyłeś to</string>
<string name="backup_review_encrypted_changed">Zmieniłeś to</string>
<string name="backup_review_encrypted_cleared">Opróżniłeś to</string>
<string name="backup_review_feed">Kanał</string>
<string name="backup_review_gone">Zniknęła</string>
<string name="backup_review_hashtags_you_follow">Hashtagi, które obserwujesz</string>
<string name="backup_review_inbox">Skrzynka odbiorcza</string>
<string name="backup_review_inbox_explainer">gdzie inni docierają do Ciebie</string>
<string name="backup_review_joined">dołączył(a)</string>
<string name="backup_review_kept">zachowane</string>
<string name="backup_review_kept_count">Zachowano %1$s</string>
<string name="backup_review_key_removed">Usunięto klucz odbierający</string>
<string name="backup_review_key_replaced">Wymieniono klucz odbierający</string>
<string name="backup_review_key_warning">Nutzapy zostaną przypisane do tego nowego klucza. Jeśli urządzenie nie posiada tego klucza, może się okazać, że nie będzie można ich tutaj zrealizować.</string>
<string name="backup_review_left">zostało</string>
<string name="backup_review_left_tag">Zostało</string>
<string name="backup_review_listed_tag">Na Liście</string>
<string name="backup_review_moved_tag">Przeniesiona</string>
<string name="backup_review_names_and_more">%1$s i %2$s więcej</string>
<string name="backup_review_new">Nowa wersja</string>
<string name="backup_review_new_count">%1$s nowych</string>
<string name="backup_review_new_count_caption">nowe</string>
<string name="backup_review_new_tag">Nowa</string>
<string name="backup_review_newly_blocked">Nowo zablokowana</string>
<string name="backup_review_newly_muted">%1$s nowo wyciszonych</string>
<string name="backup_review_no_longer_blocked">Nie jest już zablokowana</string>
<string name="backup_review_not_set">Nie ustawiono</string>
<string name="backup_review_nothing_here">Nic tu nie ma!</string>
<string name="backup_review_offer">Oferta BOLT12</string>
<plurals name="backup_review_offers_gone">
<item quantity="one">%1$d oferta usunięta</item>
<item quantity="few">%1$d ofert usunięto</item>
<item quantity="many">%1$d ofert usunięto</item>
<item quantity="other">%1$d oferty usunięte</item>
</plurals>
<string name="backup_review_outbox">Skrzynka nadawcza</string>
<string name="backup_review_outbox_explainer">gdzie publikujesz</string>
<plurals name="backup_review_payment_targets_gone">
<item quantity="one">%1$d sposób płatności został usunięty</item>
<item quantity="few">%1$d sposobów płatności zostało usuniętych</item>
<item quantity="many">%1$d sposobów płatności zostało usuniętych</item>
<item quantity="other">%1$d sposoby płatności zostały usunięte</item>
</plurals>
<string name="backup_review_payments_explainer">Osoby, które Ci płacą, widzą tylko to, co znajduje się na tej liście.</string>
<string name="backup_review_places_gone"> miejsca zniknęły</string>
<string name="backup_review_places_you_follow">Miejsca które obserwujesz</string>
<string name="backup_review_private_added_body">Są teraz zaszyfrowane, co prawdopodobnie wyjaśnia, gdzie podziały się powyższe elementy publiczne.</string>
<string name="backup_review_private_added_title">Ustawiłeś te pozycje jako prywatne</string>
<string name="backup_review_private_changed_body">Są zaszyfrowane, więc nie można ich tu porównać.</string>
<string name="backup_review_private_changed_title">Zmieniłeś prywatne elementy</string>
<string name="backup_review_private_wiped_body">Są zaszyfrowane, więc tylko Ty możesz zobaczyć, co to było. Odtworzenie przywróci je wszystkie.</string>
<string name="backup_review_private_wiped_title">Usunąłeś wszystkie prywatne elementy</string>
<string name="backup_review_receiving_tag">Pobieranie</string>
<string name="backup_review_renamed">zmieniono nazwę</string>
<string name="backup_review_renamed_tag">Zmieniono nazwę</string>
<string name="backup_review_saved">Na tym urządzeniu</string>
<string name="backup_review_search_people">Szukaj %1$s osób</string>
<string name="backup_review_searching_tag">Szukam</string>
<string name="backup_review_service">Serwis</string>
<string name="backup_review_service_followers">Obserwujący</string>
<string name="backup_review_service_rank">Ranking</string>
<string name="backup_review_service_topics">Tematy</string>
<string name="backup_review_services_changed">Zmieniono %1$s z %2$s serwisów</string>
<string name="backup_review_services_changed_explainer">Teraz pochodzą od kogoś innego albo od nikogo</string>
<string name="backup_review_still_blocked">Nadal zablokowana</string>
<string name="backup_review_storing_tag">Przechowywanie</string>
<string name="backup_review_tab_dropped">Usunięte · %1$s</string>
<string name="backup_review_tab_edited">Edytowane · %1$s</string>
<string name="backup_review_tab_new">Nowe · %1$s</string>
<string name="backup_review_title_cleared">%1$s wyczyszczono</string>
<string name="backup_review_title_updated">%1$s zaktualizowano</string>
<string name="backup_review_topics_gone">tematy zniknęły</string>
<string name="backup_review_trust_warning">Rankingi, liczba obserwujących i filtrowanie spamu, które widzisz, pochodzą od tych dostawców.</string>
<string name="backup_review_trusted_tag">Zaufane</string>
<string name="backup_review_unblocked">%1$s odblokowane</string>
<string name="backup_review_unblocked_explainer">Ametyst ponownie połączy się z nimi</string>
<string name="backup_review_units_tag">Jednostek</string>
<string name="backup_review_unmuted">%1$s odblokowanych</string>
<string name="backup_review_unmuted_explainer">Mogą one ponownie pojawić się w Twoich informacjach</string>
<string name="backup_review_untrusted_tag">Niezaufane</string>
<string name="backup_review_wallet_body">Minty i klucze w Twoim portfelu są zaszyfrowane dla Ciebie, więc nie można ich tutaj porównać pozycja po pozycji. Przywrócenie danych powoduje przywrócenie dokładnie tego, co zostało zapisane na tym urządzeniu.</string>
<string name="backup_review_was_named">było „%1$s”</string>
<string name="backup_review_words_and_hashtags">Wyrazy &amp; hasztagi</string>
<string name="backup_type_app_settings">zsynchronizowane ustawienia aplikacji</string>
<string name="backup_type_app_settings_explainer">Ustawienia Amethyst są synchronizowane na wszystkich Twoich urządzeniach. Są one zaszyfrowane, więc można porównać jedynie sam fakt ich obecności.</string>
<string name="backup_type_blocked_relays">lista zablokowanych przekaźników</string>
<string name="backup_type_blocked_relays_explainer">Transmitery, z którymi ta aplikacja nigdy się nie łączy.</string>
<string name="backup_type_bolt12_offers">Oferta BOLT12</string>
<string name="backup_type_bolt12_offers_explainer">Ta możliwość pozwala innym płacić Ci za pomocą Lightning.</string>
<string name="backup_type_cashu_wallet">Portfel Cashu</string>
<string name="backup_type_cashu_wallet_explainer">Ustawienia portfela ecash oraz minty. Są one zaszyfrowane, więc można porównać jedynie sam fakt ich istnienia.</string>
<string name="backup_type_communities">lista społeczności</string>
<string name="backup_type_communities_explainer">Społeczności, do których dołączyłeś.</string>
<string name="backup_type_concord_communities">Lista społeczności Concord</string>
<string name="backup_type_concord_communities_explainer">Społeczności Concord, do których dołączyłeś. Są one zaszyfrowane, więc można porównać jedynie fakt ich istnienia.</string>
<string name="backup_type_dm_relays">Lista transmiterów DM</string>
<string name="backup_type_dm_relays_explainer">Gdzie inni dostarczają Twoje prywatne wiadomości.</string>
<string name="backup_type_ephemeral_chats">lista czatów efemerycznych</string>
<string name="backup_type_ephemeral_chats_explainer">Tymczasowe czaty, do których dołączyłeś.</string>
<string name="backup_type_favorite_algo_feeds">lista ulubionych kanałów</string>
<string name="backup_type_favorite_algo_feeds_explainer">Zapisane przez Ciebie kanały algorytmiczne.</string>
<string name="backup_type_follow_list">lista obserwowanych</string>
<string name="backup_type_follow_list_explainer">Określa, czyje posty pojawiają się w Twoim kanale głównym.</string>
<string name="backup_type_geohashes">obserwowane lokalizacje</string>
<string name="backup_type_geohashes_explainer">Miejsca, z których posty pojawiają się na Twoich kanałach.</string>
<string name="backup_type_hashtags">obserwowane hashtagi</string>
<string name="backup_type_hashtags_explainer">Hashtagi, z których pochodzą posty wyświetlane w Twoich kanałach.</string>
<string name="backup_type_indexer_relays">lista transmiterów indeksujących</string>
<string name="backup_type_indexer_relays_explainer">Transmitery służące do wyszukiwania profili innych osób oraz list transmiterów.</string>
<string name="backup_type_key_package_relays">transmitery kluczy czatu grupowego</string>
<string name="backup_type_key_package_relays_explainer">Gdzie inni znajdą klucze do dodawania cię do zaszyfrowanych czatów grupowych.</string>
<string name="backup_type_mute_list">lista zablokowanych</string>
<string name="backup_type_mute_list_explainer">Ukrywa osoby, słowa, hashtagi i wątki we wszystkich wyświetlanych treściach.</string>
<string name="backup_type_nutzap_info">ustawienia odbioru nutzap</string>
<string name="backup_type_nutzap_info_explainer">Minty i klucz, które inni używają do wysyłania ci „nutzapów”.</string>
<string name="backup_type_other">lista</string>
<string name="backup_type_other_explainer">Lista zapisana na tym urządzeniu.</string>
<string name="backup_type_outbox_inbox_relays">lista publicznych transmiterów</string>
<string name="backup_type_outbox_inbox_relays_explainer">Informuje wszystkich, gdzie mogą znaleźć Twoje posty oraz gdzie mają wysyłać Ci odpowiedzi i wzmianki.</string>
<string name="backup_type_payment_targets">adresy płatności</string>
<string name="backup_type_payment_targets_explainer">Sposoby, w jakie inni mogą Ci zapłacić.</string>
<string name="backup_type_private_outbox_relays">lista prywatnych transmiterów</string>
<string name="backup_type_private_outbox_relays_explainer">Gdzie przechowywane są Twoje projekty i prywatne dane.</string>
<string name="backup_type_profile">profil</string>
<string name="backup_type_profile_explainer">Twoje imię i nazwisko, zdjęcie, biografia oraz adresy do płatności – tak, jak widzą je wszyscy.</string>
<string name="backup_type_public_chats">lista czatów publicznych</string>
<string name="backup_type_public_chats_explainer">Publiczne czaty, do których dołączyłeś.</string>
<string name="backup_type_relay_feeds">ulubione kanały transmitera</string>
<string name="backup_type_relay_feeds_explainer">Transmitery, które przeglądasz jako kanały informacyjne.</string>
<string name="backup_type_relay_groups">lista grup</string>
<string name="backup_type_relay_groups_explainer">Grupy oparte na transmiterach, do których dołączyłeś.</string>
<string name="backup_type_search_relays">lista transmiterów wyszukiwania</string>
<string name="backup_type_search_relays_explainer">Transmitery używane podczas wyszukiwania.</string>
<string name="backup_type_trust_providers">lista zaufanych dostawców</string>
<string name="backup_type_trust_providers_explainer">Serwisy, którym ufasz, a które zajmują się klasyfikowaniem i weryfikacją osób w Twoim imieniu.</string>
<string name="backup_type_trusted_relays">lista zaufanych transmiterów</string>
<string name="backup_type_trusted_relays_explainer">Zaufane transmitery, z którymi bez wahania nawiązujesz połączenie.</string>
</resources>
@@ -5482,4 +5482,574 @@
<string name="cyberspace_bag_unsigned">Unsigned, so this author is a claim</string>
<string name="cyberspace_bag_unknown_kind">An item of kind %1$d, which this client does not draw</string>
<string name="cyberspace_bag_opaque">%1$d bytes of something this client does not read</string>
<string name="backup_action_block_again">Block %1$s again</string>
<string name="backup_action_keep_count">Keep %1$s</string>
<string name="backup_action_rejoin">Rejoin %1$s</string>
<string name="backup_action_remute">Re-mute %1$s</string>
<string name="backup_action_restore_count">Restore %1$s</string>
<string name="backup_action_restore_feeds">Restore feeds</string>
<string name="backup_action_restore_places">Restore places</string>
<string name="backup_action_restore_profile">Restore profile</string>
<string name="backup_action_restore_providers">Restore providers</string>
<string name="backup_action_restore_relays">Restore relays</string>
<string name="backup_action_restore_topics">Restore topics</string>
<string name="backup_action_restore_wallet">Restore wallet</string>
<string name="backup_card_accept">Accept</string>
<plurals name="backup_card_and_added">
<item quantity="one">%1$s and added %2$d</item>
<item quantity="other">%1$s and added %2$d</item>
</plurals>
<plurals name="backup_card_and_blocked">
<item quantity="one">%1$s and blocked %2$d</item>
<item quantity="other">%1$s and blocked %2$d</item>
</plurals>
<plurals name="backup_card_and_followed">
<item quantity="one">%1$s and followed %2$d</item>
<item quantity="other">%1$s and followed %2$d</item>
</plurals>
<plurals name="backup_card_and_joined">
<item quantity="one">%1$s and joined %2$d</item>
<item quantity="other">%1$s and joined %2$d</item>
</plurals>
<plurals name="backup_card_and_muted">
<item quantity="one">%1$s and muted %2$d</item>
<item quantity="other">%1$s and muted %2$d</item>
</plurals>
<string name="backup_card_app_settings">Your Amethyst synced app settings changed in another app that is not Amethyst. This can create problems.</string>
<string name="backup_card_concord_emptied">You emptied your Concord list</string>
<string name="backup_card_concord_rewritten">You changed your Concord list</string>
<plurals name="backup_card_dm_relays_lost">
<item quantity="one">You removed %1$d DM relay</item>
<item quantity="other">You removed %1$d DM relays</item>
</plurals>
<plurals name="backup_card_feeds_removed">
<item quantity="one">You removed %1$d favorite feed</item>
<item quantity="other">You removed %1$d favorite feeds</item>
</plurals>
<plurals name="backup_card_follows_dropped">
<item quantity="one">You unfollowed %1$d person</item>
<item quantity="other">You unfollowed %1$d people</item>
</plurals>
<plurals name="backup_card_hashtags_unfollowed">
<item quantity="one">You unfollowed %1$d hashtag</item>
<item quantity="other">You unfollowed %1$d hashtags</item>
</plurals>
<plurals name="backup_card_indexer_relays_lost">
<item quantity="one">You removed %1$d indexer relay</item>
<item quantity="other">You removed %1$d indexer relays</item>
</plurals>
<plurals name="backup_card_key_package_relays_lost">
<item quantity="one">You removed %1$d group-chat key relay</item>
<item quantity="other">You removed %1$d group-chat key relays</item>
</plurals>
<plurals name="backup_card_leave_chats">
<item quantity="one">You left %1$d public chat</item>
<item quantity="other">You left %1$d public chats</item>
</plurals>
<plurals name="backup_card_leave_communities">
<item quantity="one">You left %1$d community</item>
<item quantity="other">You left %1$d communities</item>
</plurals>
<plurals name="backup_card_leave_groups">
<item quantity="one">You left %1$d group</item>
<item quantity="other">You left %1$d groups</item>
</plurals>
<plurals name="backup_card_leave_rooms">
<item quantity="one">You left %1$d chat room</item>
<item quantity="other">You left %1$d chat rooms</item>
</plurals>
<plurals name="backup_card_mutes_visible">
<item quantity="one">You unmuted %1$d thing</item>
<item quantity="other">You unmuted %1$d things</item>
</plurals>
<plurals name="backup_card_nip65_lost">
<item quantity="one">You removed %1$d public relay</item>
<item quantity="other">You removed %1$d public relays</item>
</plurals>
<string name="backup_card_nutzap_key_removed">You removed your nutzap key</string>
<string name="backup_card_nutzap_key_replaced">You replaced your nutzap key</string>
<plurals name="backup_card_nutzap_mints_removed">
<item quantity="one">You removed %1$d nutzap mint</item>
<item quantity="other">You removed %1$d nutzap mints</item>
</plurals>
<plurals name="backup_card_offers_removed">
<item quantity="one">You removed %1$d BOLT12 offer</item>
<item quantity="other">You removed %1$d BOLT12 offers</item>
</plurals>
<plurals name="backup_card_payment_targets_removed">
<item quantity="one">You removed %1$d way to get paid</item>
<item quantity="other">You removed %1$d ways to get paid</item>
</plurals>
<plurals name="backup_card_places_unfollowed">
<item quantity="one">You unfollowed %1$d place</item>
<item quantity="other">You unfollowed %1$d places</item>
</plurals>
<plurals name="backup_card_private_outbox_lost">
<item quantity="one">You removed %1$d private relay</item>
<item quantity="other">You removed %1$d private relays</item>
</plurals>
<plurals name="backup_card_profile_lost_fields">
<item quantity="one">You removed %1$d profile field</item>
<item quantity="other">You removed %1$d profile fields</item>
</plurals>
<plurals name="backup_card_relay_feeds_lost">
<item quantity="one">You removed %1$d relay feed</item>
<item quantity="other">You removed %1$d relay feeds</item>
</plurals>
<plurals name="backup_card_search_relays_lost">
<item quantity="one">You removed %1$d search relay</item>
<item quantity="other">You removed %1$d search relays</item>
</plurals>
<plurals name="backup_card_trust_services_changed">
<item quantity="one">You changed the provider of %1$d trust service</item>
<item quantity="other">You changed the provider of %1$d trust services</item>
</plurals>
<plurals name="backup_card_trusted_lost">
<item quantity="one">You stopped trusting %1$d relay</item>
<item quantity="other">You stopped trusting %1$d relays</item>
</plurals>
<plurals name="backup_card_unblocked">
<item quantity="one">You unblocked %1$d relay</item>
<item quantity="other">You unblocked %1$d relays</item>
</plurals>
<string name="backup_card_wallet_emptied">You emptied your Cashu wallet</string>
<string name="backup_card_wallet_rewritten">You changed your Cashu wallet</string>
<string name="backup_conflict_added">Added (%1$s)</string>
<string name="backup_conflict_changed">Changed (%1$s)</string>
<string name="backup_conflict_count_added">%1$s added</string>
<string name="backup_conflict_count_changed">%1$s changed</string>
<string name="backup_conflict_count_removed">%1$s removed</string>
<string name="backup_conflict_keep_new">Use new version</string>
<string name="backup_conflict_private_added">You added private (encrypted) items.</string>
<string name="backup_conflict_private_changed">You also changed private (encrypted) items. They can't be compared here.</string>
<string name="backup_conflict_private_cleared">You removed all private (encrypted) items.</string>
<string name="backup_conflict_relay_read_only">read only</string>
<string name="backup_conflict_relay_read_write">read and write</string>
<string name="backup_conflict_relay_write_only">write only</string>
<string name="backup_conflict_removed">Removed (%1$s)</string>
<string name="backup_conflict_restore_mine">Restore saved version</string>
<string name="backup_conflict_title">You changed your %1$s in another app</string>
<string name="backup_entry_algo_feed">Feeds</string>
<string name="backup_entry_chat_room">Chat rooms</string>
<string name="backup_entry_community">Communities</string>
<string name="backup_entry_hashtag">Hashtags</string>
<string name="backup_entry_identity_claim">Linked identities</string>
<string name="backup_entry_location">Locations</string>
<string name="backup_entry_mint">Mints</string>
<string name="backup_entry_nutzap_key">Nutzap keys</string>
<string name="backup_entry_offer">Offers</string>
<string name="backup_entry_other">Other entries</string>
<string name="backup_entry_payment_target">Payment addresses</string>
<string name="backup_entry_person">People</string>
<string name="backup_entry_profile_field">Profile fields</string>
<string name="backup_entry_public_chat">Public chats</string>
<string name="backup_entry_relay">Relays</string>
<string name="backup_entry_relay_group">Groups</string>
<string name="backup_entry_thread">Threads</string>
<string name="backup_entry_trust_provider">Trust providers</string>
<string name="backup_entry_word">Words</string>
<string name="backup_profile_field_about">About</string>
<string name="backup_profile_field_banner">Banner</string>
<string name="backup_profile_field_birthday">Birthday</string>
<string name="backup_profile_field_bot">Bot flag</string>
<string name="backup_profile_field_clink_offer">CLINK offer</string>
<string name="backup_profile_field_display_name">Display name</string>
<string name="backup_profile_field_lud06">LNURL</string>
<string name="backup_profile_field_lud16">Lightning address</string>
<string name="backup_profile_field_name">Username</string>
<string name="backup_profile_field_nip05">Nostr address</string>
<string name="backup_profile_field_picture">Picture</string>
<string name="backup_profile_field_pronouns">Pronouns</string>
<string name="backup_profile_field_website">Website</string>
<string name="backup_review_allowed_tag">Allowed</string>
<string name="backup_review_changed_by_other_app">Changed in another app · %1$s</string>
<string name="backup_review_concord_body">Your Concord communities are encrypted to you, so they can't be compared here. Restoring puts back exactly what this device saved.</string>
<string name="backup_review_demoted_tag">Moved</string>
<string name="backup_review_dropped_count">%1$s dropped</string>
<string name="backup_review_dropped_tag">Dropped</string>
<string name="backup_review_encrypted_added">You created it</string>
<string name="backup_review_encrypted_changed">You changed it</string>
<string name="backup_review_encrypted_cleared">You emptied it</string>
<string name="backup_review_feed">Feed</string>
<string name="backup_review_gone">Gone</string>
<string name="backup_review_hashtags_you_follow">Hashtags you follow</string>
<string name="backup_review_inbox">Inbox</string>
<string name="backup_review_inbox_explainer">where others reach you</string>
<string name="backup_review_joined">joined</string>
<string name="backup_review_kept">kept</string>
<string name="backup_review_kept_count">%1$s kept</string>
<string name="backup_review_key_removed">Receiving key removed</string>
<string name="backup_review_key_replaced">Receiving key replaced</string>
<string name="backup_review_key_warning">Nutzaps will be locked to this new key. If this device doesn't have it, you may not be able to redeem them here.</string>
<string name="backup_review_left">left</string>
<string name="backup_review_left_tag">Left</string>
<string name="backup_review_listed_tag">Listed</string>
<string name="backup_review_moved_tag">Moved</string>
<string name="backup_review_names_and_more">%1$s and %2$s more</string>
<string name="backup_review_new">New version</string>
<string name="backup_review_new_count">%1$s new</string>
<string name="backup_review_new_count_caption">new</string>
<string name="backup_review_new_tag">New</string>
<string name="backup_review_newly_blocked">Newly blocked</string>
<string name="backup_review_newly_muted">%1$s newly muted</string>
<string name="backup_review_no_longer_blocked">No longer blocked</string>
<string name="backup_review_not_set">Not set</string>
<string name="backup_review_nothing_here">Nothing here</string>
<string name="backup_review_offer">BOLT12 offer</string>
<plurals name="backup_review_offers_gone">
<item quantity="one">%1$d offer removed</item>
<item quantity="other">%1$d offers removed</item>
</plurals>
<string name="backup_review_outbox">Outbox</string>
<string name="backup_review_outbox_explainer">where you publish</string>
<plurals name="backup_review_payment_targets_gone">
<item quantity="one">%1$d way to pay you removed</item>
<item quantity="other">%1$d ways to pay you removed</item>
</plurals>
<string name="backup_review_payments_explainer">People paying you only see what's in this list.</string>
<string name="backup_review_places_gone">places gone</string>
<string name="backup_review_places_you_follow">Places you follow</string>
<string name="backup_review_private_added_body">They're encrypted now, which is likely where the public items above went.</string>
<string name="backup_review_private_added_title">You made items private</string>
<string name="backup_review_private_changed_body">They're encrypted, so they can't be compared here.</string>
<string name="backup_review_private_changed_title">You changed private items</string>
<string name="backup_review_private_wiped_body">They're encrypted, so only you can see what they were. Restoring brings them all back.</string>
<string name="backup_review_private_wiped_title">You removed all private items</string>
<string name="backup_review_receiving_tag">Receiving</string>
<string name="backup_review_renamed">renamed</string>
<string name="backup_review_renamed_tag">Renamed</string>
<string name="backup_review_saved">On this device</string>
<string name="backup_review_search_people">Search %1$s people</string>
<string name="backup_review_searching_tag">Searching</string>
<string name="backup_review_service">Service</string>
<string name="backup_review_service_followers">Followers</string>
<string name="backup_review_service_rank">Rank</string>
<string name="backup_review_service_topics">Topics</string>
<string name="backup_review_services_changed">%1$s of %2$s services changed</string>
<string name="backup_review_services_changed_explainer">They now come from someone else, or no one</string>
<string name="backup_review_still_blocked">Still blocked</string>
<string name="backup_review_storing_tag">Storing</string>
<string name="backup_review_tab_dropped">Dropped · %1$s</string>
<string name="backup_review_tab_edited">Edited · %1$s</string>
<string name="backup_review_tab_new">New · %1$s</string>
<string name="backup_review_title_cleared">%1$s cleared</string>
<string name="backup_review_title_updated">%1$s updated</string>
<string name="backup_review_topics_gone">topics gone</string>
<string name="backup_review_trust_warning">Ranks, follower counts and spam filtering you see come from these providers.</string>
<string name="backup_review_trusted_tag">Trusted</string>
<string name="backup_review_unblocked">%1$s unblocked</string>
<string name="backup_review_unblocked_explainer">Amethyst will connect to them again</string>
<string name="backup_review_units_tag">Units</string>
<string name="backup_review_unmuted">%1$s unmuted</string>
<string name="backup_review_unmuted_explainer">These can reach your feeds again</string>
<string name="backup_review_untrusted_tag">Untrusted</string>
<string name="backup_review_wallet_body">Your wallet's mints and keys are encrypted to you, so they can't be compared item by item here. Restoring puts back exactly what this device saved.</string>
<string name="backup_review_was_named">was “%1$s”</string>
<string name="backup_review_words_and_hashtags">Words &amp; hashtags</string>
<string name="backup_type_app_settings">synced app settings</string>
<string name="backup_type_app_settings_explainer">Amethyst settings synced across your devices. They are encrypted, so only their presence can be compared.</string>
<string name="backup_type_blocked_relays">blocked relay list</string>
<string name="backup_type_blocked_relays_explainer">Relays this app never connects to.</string>
<string name="backup_type_bolt12_offers">BOLT12 offers</string>
<string name="backup_type_bolt12_offers_explainer">The lightning offers others use to pay you.</string>
<string name="backup_type_cashu_wallet">Cashu wallet</string>
<string name="backup_type_cashu_wallet_explainer">Your ecash wallet settings and mints. They are encrypted, so only their presence can be compared.</string>
<string name="backup_type_communities">community list</string>
<string name="backup_type_communities_explainer">The communities you joined.</string>
<string name="backup_type_concord_communities">Concord community list</string>
<string name="backup_type_concord_communities_explainer">The Concord communities you joined. They are encrypted, so only their presence can be compared.</string>
<string name="backup_type_dm_relays">DM relay list</string>
<string name="backup_type_dm_relays_explainer">Where others deliver your private messages.</string>
<string name="backup_type_ephemeral_chats">ephemeral chat list</string>
<string name="backup_type_ephemeral_chats_explainer">The ephemeral chat rooms you joined.</string>
<string name="backup_type_favorite_algo_feeds">favorite feeds list</string>
<string name="backup_type_favorite_algo_feeds_explainer">The algorithmic feeds you saved.</string>
<string name="backup_type_follow_list">follow list</string>
<string name="backup_type_follow_list_explainer">Decides whose posts appear in your home feed.</string>
<string name="backup_type_geohashes">followed locations</string>
<string name="backup_type_geohashes_explainer">Locations whose posts appear in your feeds.</string>
<string name="backup_type_hashtags">followed hashtags</string>
<string name="backup_type_hashtags_explainer">Hashtags whose posts appear in your feeds.</string>
<string name="backup_type_indexer_relays">indexer relay list</string>
<string name="backup_type_indexer_relays_explainer">The relays used to find other people's profiles and relay lists.</string>
<string name="backup_type_key_package_relays">group-chat key relays</string>
<string name="backup_type_key_package_relays_explainer">Where others find the keys to add you to encrypted group chats.</string>
<string name="backup_type_mute_list">mute list</string>
<string name="backup_type_mute_list_explainer">Hides people, words, hashtags and threads from everything you see.</string>
<string name="backup_type_nutzap_info">nutzap receiving settings</string>
<string name="backup_type_nutzap_info_explainer">The mints and key others use to send you nutzaps.</string>
<string name="backup_type_other">list</string>
<string name="backup_type_other_explainer">A list saved on this device.</string>
<string name="backup_type_outbox_inbox_relays">public relay list</string>
<string name="backup_type_outbox_inbox_relays_explainer">Tells everyone where to find your posts and where to send you replies and mentions.</string>
<string name="backup_type_payment_targets">payment addresses</string>
<string name="backup_type_payment_targets_explainer">The ways other people can pay you.</string>
<string name="backup_type_private_outbox_relays">private relay list</string>
<string name="backup_type_private_outbox_relays_explainer">Where your drafts and private data are stored.</string>
<string name="backup_type_profile">profile</string>
<string name="backup_type_profile_explainer">Your name, picture, bio and payment addresses, as everyone sees them.</string>
<string name="backup_type_public_chats">public chat list</string>
<string name="backup_type_public_chats_explainer">The public chats you joined.</string>
<string name="backup_type_relay_feeds">favorite relay feeds</string>
<string name="backup_type_relay_feeds_explainer">The relays you browse as feeds.</string>
<string name="backup_type_relay_groups">group list</string>
<string name="backup_type_relay_groups_explainer">The relay-based groups you joined.</string>
<string name="backup_type_search_relays">search relay list</string>
<string name="backup_type_search_relays_explainer">The relays used when you search.</string>
<string name="backup_type_trust_providers">trust provider list</string>
<string name="backup_type_trust_providers_explainer">The services you trust to rank and verify people for you.</string>
<string name="backup_type_trusted_relays">trusted relay list</string>
<string name="backup_type_trusted_relays_explainer">Relays you trust to connect to without asking.</string>
<string name="cordn_action_delete">Delete</string>
<string name="cordn_action_edit">Edit</string>
<string name="cordn_action_editing">Editing your message</string>
<string name="cordn_action_go_to_message">Go to message</string>
<string name="cordn_action_pin">Pin</string>
<string name="cordn_action_reply">Reply</string>
<string name="cordn_action_unpin">Unpin</string>
<string name="cordn_admin_action_failed">That change could not be made.</string>
<string name="cordn_admin_coordinator_silent">The coordinator did not answer, so nothing was changed. Try again in a moment.</string>
<string name="cordn_admin_no_key_package">%1$s has not published a key package to this coordinator, so they cannot be added yet. Ask them to open cordn on this coordinator first.</string>
<string name="cordn_admin_no_self_remove">You cannot remove yourself from a cordn group.</string>
<string name="cordn_admin_no_welcome">%1$s could not be given a way into the group, so the add was abandoned.</string>
<string name="cordn_admin_not_a_member">%1$s is not in this group.</string>
<string name="cordn_admin_someone">That person</string>
<string name="cordn_admin_wrong_key_package">This coordinator served a key package that belongs to someone else, so %1$s was not added. Nothing was changed.</string>
<string name="cordn_backup_contents_body">Your group encryption keys, your key packages, and every message in those groups. Anyone who opens it can read the conversations. The passphrase is the only thing protecting it once it leaves Amethyst.</string>
<string name="cordn_backup_contents_title">What is in the file</string>
<string name="cordn_backup_explainer">Your nsec cannot bring cordn groups back. A cordn group lives as encryption state on this device and an ordered stream on a coordinator that cannot read it — lose the device without a backup and the group is gone, including everything already said in it.</string>
<string name="cordn_backup_export">Export</string>
<string name="cordn_backup_exported">Backup saved.</string>
<string name="cordn_backup_failed">That did not work. Check the passphrase and the file.</string>
<string name="cordn_backup_passphrase">Passphrase</string>
<string name="cordn_backup_restore">Restore</string>
<string name="cordn_backup_restore_body">Restoring replaces the cordn groups on this device with the ones in the file. It is for a phone that has taken over from another — not a way to use the same groups on two devices at once.</string>
<string name="cordn_backup_restore_confirm_body">Every cordn group now on this device will be removed and replaced by the ones in the file.\n\nDo not restore onto a second phone while the first is still in use. Both would hold the same group keys, and once both have sent a message the group breaks for everyone — silently, and with no way back.</string>
<string name="cordn_backup_restore_confirm_title">Replace this device's cordn groups?</string>
<string name="cordn_backup_restore_title">Restore</string>
<string name="cordn_backup_restored">Restored.</string>
<string name="cordn_chat_empty_description">Anything sent here is encrypted for this group. The coordinator relays it without being able to read it.</string>
<string name="cordn_chat_empty_title">No messages yet</string>
<string name="cordn_chat_unread_divider">New messages</string>
<string name="cordn_chat_yesterday">Yesterday</string>
<string name="cordn_composer_hint">Message</string>
<string name="cordn_coordinators_add_action">Add</string>
<string name="cordn_coordinators_add_failed">Could not open that coordinator.</string>
<string name="cordn_coordinators_copy_key">Copy the coordinator's npub</string>
<string name="cordn_coordinators_discover_action">Look for coordinators</string>
<string name="cordn_coordinators_discover_add">Add</string>
<string name="cordn_coordinators_discover_explainer">Reads announcements your relays already carry. No coordinator is contacted, so none of them learns you looked.</string>
<string name="cordn_coordinators_discover_failed">Could not read announcements</string>
<string name="cordn_coordinators_discover_none">Nobody is announcing on your relays.</string>
<string name="cordn_coordinators_discover_seen">Announced %1$s ago</string>
<string name="cordn_coordinators_discover_seen_on">Last announced %1$s</string>
<string name="cordn_coordinators_discover_unheard">Nothing found, and %1$d of your relays did not answer.</string>
<string name="cordn_coordinators_explainer">A coordinator orders one group's messages and is the only thing that can. Losing it loses the ordering, and a second one does not mirror the first — so these are named things you choose, not interchangeable relays.</string>
<string name="cordn_coordinators_health_down">Not answering — %1$d calls in a row have failed. Groups on it are not syncing.</string>
<string name="cordn_coordinators_health_ok">Answering.</string>
<string name="cordn_coordinators_health_retrying">A call failed; retrying.</string>
<string name="cordn_coordinators_health_unknown">Nothing asked of it yet.</string>
<string name="cordn_coordinators_hide_older">Hide the ones that stopped announcing</string>
<string name="cordn_coordinators_identify">Ask who it is</string>
<string name="cordn_coordinators_key">Coordinator key</string>
<string name="cordn_coordinators_label">A name for it (yours, optional)</string>
<string name="cordn_coordinators_label_note">This name is yours and stays on this device. A coordinator cannot prove a name, so it is never asked for one.</string>
<string name="cordn_coordinators_more">More actions</string>
<string name="cordn_coordinators_none">No coordinators yet.</string>
<string name="cordn_coordinators_purge">Purge</string>
<string name="cordn_coordinators_purge_body">Removing keeps the groups on this device, so adding the coordinator back brings them with it. Purging deletes them: the group keys, the read positions and your key packages all go, and those groups can only be re-entered by a fresh invitation.\n\nThe coordinator is not told and keeps whatever it already had.</string>
<string name="cordn_coordinators_purge_title">Purge this coordinator?</string>
<string name="cordn_coordinators_relays">%1$s</string>
<string name="cordn_coordinators_relays_label">Answers on</string>
<string name="cordn_coordinators_relays_more">%1$s +%2$d more</string>
<string name="cordn_coordinators_remove">Remove</string>
<string name="cordn_coordinators_rename">Rename</string>
<string name="cordn_coordinators_server">Says it is: %1$s</string>
<string name="cordn_coordinators_server_claim">A coordinator's name and version are strings it chose. Its public key is the only thing that identifies it.</string>
<string name="cordn_coordinators_server_silent">Answered, but named nothing.</string>
<string name="cordn_coordinators_show_all">Show all %1$d</string>
<string name="cordn_coordinators_show_fewer">Show fewer</string>
<string name="cordn_coordinators_show_older">Show %1$d that stopped announcing</string>
<string name="cordn_coordinators_stale_note">These have not announced in over a month. Creating a group on one that has gone away will fail.</string>
<string name="cordn_create_action">Create group</string>
<plurals name="cordn_create_action_invite">
<item quantity="one">Create and invite %1$d person</item>
<item quantity="other">Create and invite %1$d people</item>
</plurals>
<string name="cordn_create_add_member">Add someone</string>
<string name="cordn_create_admin">Admin</string>
<plurals name="cordn_create_admin_count">
<item quantity="one">%1$d person can add and remove</item>
<item quantity="other">%1$d people can add and remove</item>
</plurals>
<string name="cordn_create_admin_only_me">Only I manage this group</string>
<string name="cordn_create_admin_only_me_on">Only you can invite, remove members or change the group's details. You can hand that to someone else later.</string>
<string name="cordn_create_coordinator">Coordinator</string>
<string name="cordn_create_coordinator_change">Change</string>
<string name="cordn_create_coordinator_discover">Find coordinators</string>
<string name="cordn_create_coordinator_hide">Done</string>
<string name="cordn_create_coordinator_new">Another coordinator</string>
<string name="cordn_create_coordinator_pubkey">Coordinator public key (hex or npub)</string>
<string name="cordn_create_coordinator_relays">Relays it answers on, one per line</string>
<string name="cordn_create_coverage_all">Reaches all %1$d of your people</string>
<string name="cordn_create_coverage_partial">Reaches %1$d of %2$d &#8212; the rest would be left out</string>
<string name="cordn_create_coverage_unknown">Not checked &#8212; picking it will ask</string>
<string name="cordn_create_description">Description (optional)</string>
<string name="cordn_create_egalitarian">Everyone can add and remove</string>
<string name="cordn_create_egalitarian_note">Everyone in the group can invite, remove members and change its details. Any member can name admins later, which ends that.</string>
<string name="cordn_create_explainer">A cordn group is ordered by one coordinator you choose. Creating the group happens on this device — the coordinator only learns the group exists once you invite someone or send a message.</string>
<string name="cordn_create_failed">The group could not be created.</string>
<string name="cordn_create_name">Group name</string>
<string name="cordn_create_no_coordinator">No coordinator chosen</string>
<string name="cordn_create_outcome_explainer">Each invitation is a Welcome left on the coordinator, so they can fail one at a time. Nobody has been notified: they see it next time they open their app.</string>
<string name="cordn_create_outcome_failed">The coordinator refused the invitation</string>
<string name="cordn_create_outcome_link">Send a link</string>
<string name="cordn_create_outcome_no_welcome">Added to the group, but the invitation did not reach them &#8212; try again from the group screen</string>
<string name="cordn_create_outcome_open">Open the chat</string>
<string name="cordn_create_outcome_skipped">No key here, so no Welcome could be left</string>
<string name="cordn_create_outcome_title">The group exists</string>
<string name="cordn_create_outcome_waiting">Waiting for them</string>
<string name="cordn_create_people_done">Done</string>
<string name="cordn_create_people_empty">Nobody added yet. A group can be created empty and invitations sent later, but naming people now is what shows which coordinator can reach them.</string>
<string name="cordn_create_people_none">Nobody yet &#8212; tap to add people</string>
<string name="cordn_create_people_note">Naming people now is what tells us which coordinator can reach them. Each one gets a Welcome left on that coordinator, which they pick up next time they open their app.</string>
<plurals name="cordn_create_reach_count">
<item quantity="one">Reachable on %1$d coordinator</item>
<item quantity="other">Reachable on %1$d coordinators</item>
</plurals>
<string name="cordn_create_reach_none">No key on any coordinator you use</string>
<string name="cordn_create_reach_unknown">Not checked yet</string>
<string name="cordn_create_step_admins">Who can add and remove</string>
<string name="cordn_create_step_name">Name it</string>
<string name="cordn_create_step_people">Who is in it</string>
<string name="cordn_create_step_where">Where it lives</string>
<string name="cordn_create_title">New Cordn group</string>
<plurals name="cordn_create_unreachable_count">
<item quantity="one">%1$d person cannot be reached by any coordinator you use</item>
<item quantity="other">%1$d people cannot be reached by any coordinator you use</item>
</plurals>
<string name="cordn_create_you">You, always an admin</string>
<string name="cordn_delete_confirm_body">Everyone in the group will see this message as withdrawn, and Amethyst will stop showing its text. The coordinator still holds the encrypted copy it was sent as, and anyone who already read it has already read it.</string>
<string name="cordn_delete_confirm_title">Withdraw message</string>
<string name="cordn_delivery_accepted">Accepted by the coordinator</string>
<string name="cordn_exposure_close">Got it</string>
<string name="cordn_exposure_open">What this coordinator can see</string>
<string name="cordn_group_info">Group info</string>
<string name="cordn_group_unavailable">This group is not available. Its coordinator may have been removed.</string>
<string name="cordn_group_unavailable_detail">Add a coordinator in Settings to start using cordn groups.</string>
<plurals name="cordn_groups_count">
<item quantity="one">%1$d group</item>
<item quantity="other">%1$d groups</item>
</plurals>
<string name="cordn_groups_down_note">Nothing is arriving from these groups. Amethyst keeps retrying.</string>
<string name="cordn_groups_manage">Coordinators</string>
<string name="cordn_groups_none">No cordn groups yet</string>
<string name="cordn_groups_none_detail">A cordn group lives on one coordinator that holds its membership and passes messages along, without ever seeing what anyone writes.</string>
<string name="cordn_groups_start">Start one</string>
<string name="cordn_info_add_member">Add someone</string>
<string name="cordn_info_add_member_none">Nobody found by that name.</string>
<string name="cordn_info_add_member_note">They can only be added if they published a key package to this coordinator.</string>
<string name="cordn_info_add_member_placeholder">Name, npub or name@domain</string>
<string name="cordn_info_admin_badge">Admin</string>
<plurals name="cordn_info_admins_kept">
<item quantity="one">The group's %1$d admin stays as it is.</item>
<item quantity="other">The group's %1$d admins stay as they are.</item>
</plurals>
<string name="cordn_info_coordinator">Coordinator</string>
<string name="cordn_info_coordinator_key">Coordinator key</string>
<string name="cordn_info_coordinator_nprofile">Coordinator link</string>
<string name="cordn_info_copy_nprofile">Copy the coordinator's nprofile</string>
<string name="cordn_info_edit_details">Edit details</string>
<string name="cordn_info_egalitarian">This group has no admins: everyone can add, remove and rename, permanently.</string>
<string name="cordn_info_epoch">Epoch</string>
<string name="cordn_info_gid">Group id</string>
<string name="cordn_info_has_key_package">Can be added on this coordinator</string>
<string name="cordn_info_members">Members</string>
<string name="cordn_info_remove_confirm_body">%1$s stops receiving messages from here. They keep what they have already read, and this cannot be undone — they would have to be invited again.</string>
<string name="cordn_info_remove_confirm_title">Remove from group?</string>
<string name="cordn_info_remove_member">Remove</string>
<string name="cordn_info_save">Save</string>
<string name="cordn_info_technical">Technical details</string>
<string name="cordn_invitations_accept">Join</string>
<string name="cordn_invitations_decline">Decline</string>
<string name="cordn_invitations_decline_warning">Declining is permanent. Getting back in means being invited again.</string>
<string name="cordn_invitations_explainer">Checked when you open this screen, and at no other time — asking a coordinator whether anyone invited you tells it you are here, so Amethyst does not do it in the background.</string>
<string name="cordn_invitations_load_failed">Could not read invitations.</string>
<plurals name="cordn_invitations_members">
<item quantity="one">%1$d person is already in this group</item>
<item quantity="other">%1$d people are already in this group</item>
</plurals>
<string name="cordn_invitations_members_more">+%1$d more</string>
<string name="cordn_invitations_no_coordinators">You have no coordinator open. cordn invitations arrive through a coordinator, so there is nowhere to look yet.</string>
<string name="cordn_invitations_none">No invitations waiting.</string>
<string name="cordn_invitations_refresh">Check again</string>
<string name="cordn_invitations_skipped">An invitation was left for another device</string>
<string name="cordn_invitations_title">Cordn invitations</string>
<string name="cordn_invitations_unreachable">%1$s did not answer</string>
<string name="cordn_invitations_via">Through %1$s</string>
<string name="cordn_keypackages_disclosure">Publishing signs a record under your own account key. That coordinator then knows this account exists and is invitable — permanently, and whether or not anyone invites you.</string>
<string name="cordn_keypackages_explainer">Others can only add you to a cordn group by taking a key package you published to that coordinator. cordn has no other place to keep one, so with none published nobody can invite you — and nothing tells them why.</string>
<string name="cordn_keypackages_failed">Could not read the key packages.</string>
<string name="cordn_keypackages_last_resort_no">No last-resort package. Once the single-use ones run out, invitations fail.</string>
<string name="cordn_keypackages_last_resort_yes">A last-resort package is published, so an invitation can still be made once the single-use ones run out.</string>
<plurals name="cordn_keypackages_orphans">
<item quantity="one">%1$d package this device cannot open</item>
<item quantity="other">%1$d packages this device cannot open</item>
</plurals>
<string name="cordn_keypackages_orphans_body">The coordinator will hand these to anyone inviting you, but the private half is not on this device — it belongs to another install. If that install is gone, an invitation using one of these produces a welcome nobody can ever open.</string>
<string name="cordn_keypackages_publish">Publish one</string>
<string name="cordn_keypackages_publish_last_resort">Publish last-resort</string>
<plurals name="cordn_keypackages_summary">
<item quantity="one">%1$d single-use package available.</item>
<item quantity="other">%1$d single-use packages available.</item>
</plurals>
<string name="cordn_keypackages_topup_note">Once you have published here, Amethyst quietly replaces packages as they are used up. It never publishes the first one for you.</string>
<string name="cordn_keypackages_withdraw_all">Withdraw all</string>
<string name="cordn_keypackages_withdraw_orphans">Withdraw those</string>
<string name="cordn_link_request">Ask to join this group</string>
<string name="cordn_link_request_disclosure">This publishes a key package under your own account key and tells the coordinator you want into this group — whether or not anyone answers.</string>
<string name="cordn_link_request_failed">Could not ask to join.</string>
<string name="cordn_link_request_sent">Asked. A member of the group has to add you; the invitation will show up under cordn invitations.</string>
<string name="cordn_link_scan">Scan</string>
<string name="cordn_media_download_failed">Could not open that file.</string>
<string name="cordn_media_no_server">No media server is set for this account, so there is nowhere to put the file. Pick one in Settings.</string>
<string name="cordn_media_unreadable">That file could not be read.</string>
<string name="cordn_media_upload_failed">Could not send that file.</string>
<plurals name="cordn_member_count">
<item quantity="one">%1$d member</item>
<item quantity="other">%1$d members</item>
</plurals>
<string name="cordn_message_deleted">Message deleted</string>
<string name="cordn_message_details_coordinator">Coordinator</string>
<string name="cordn_message_details_cursor">Cursor</string>
<string name="cordn_message_details_sent_at">Sent</string>
<string name="cordn_message_details_title">Message details</string>
<string name="cordn_message_edited">edited</string>
<string name="cordn_pinned_by">Pinned by %1$s</string>
<plurals name="cordn_pinned_count">
<item quantity="one">%1$d pinned message in this group</item>
<item quantity="other">%1$d pinned messages in this group</item>
</plurals>
<string name="cordn_pinned_next">Next pinned message</string>
<string name="cordn_pinned_previous">Previous pinned message</string>
<string name="cordn_pinned_show_all">Show all pinned messages</string>
<string name="cordn_pinned_title">Pinned messages</string>
<string name="cordn_reactions_title">Reactions</string>
<string name="cordn_requests_accept">Add</string>
<string name="cordn_requests_admin_only">Only an admin can accept. In a group with no admins, that is everyone.</string>
<string name="cordn_requests_check">Check for requests</string>
<string name="cordn_requests_decline">Dismiss</string>
<string name="cordn_requests_failed">Could not read the requests.</string>
<string name="cordn_requests_none">Nobody is waiting to join.</string>
<string name="cordn_requests_title">Requests to join</string>
<string name="cordn_send">Send</string>
<string name="cordn_send_failed">That did not send.</string>
<string name="cordn_send_no_session">This group's coordinator is not open, so nothing can be sent yet.</string>
<string name="cordn_share_copied">Copied</string>
<string name="cordn_share_copy">Copy link</string>
<string name="cordn_share_explainer">Anyone with this link can ask to join. An admin still has to let them in.</string>
<string name="cordn_share_title">Share this group</string>
<string name="cordn_voice_play">Play voice note</string>
<string name="cordn_voice_record">Record a voice note</string>
<string name="cordn_voice_stop">Stop and send</string>
</resources>